当数据库内的数据达到百万甚至千万条数的时候,直接DELETE FROM tablename 可能会耗费非常久的时间。 所以这里提供我实际使用的方法,可能不是最合适的方法,后续有更好的方法会进行补充。
方法一 TRUNCATE TABLE
通过TRUNCATE TABLE 来删除表中百万条数据的情况
- 创建临时表备份数据
比如我只想保留2022.05.13号的数据,其他之前的数据都删除。
SELECT id FROM tablename WHERE report_time > '2022-05-13' LIMIT 1;
得到ID = 4109823;
CREATE TABLE tablename_old SELECT * FROM tablename WHERE id > 4109823;
创建临时表tablename_old 存放’2022-05-13’的数据。
- 截断表
TRUNCATE TABLE tablename ;
- 备份的数据插入到表中
INSERT INTO tablename SELECT * FROM tablename_old ;
可能出现的问题: 执行 TRUNCATE TABLE 可能会出现Waiting for table metadata lock 锁表解决方案 解决方法: 1.登录数据库 执行 SHOW PROCESSLIST; 2.select * from information_schema.innodb_trx\G; 查询当前事务,里面会出现:trx_mysql_thread_id 进程ID。 3.kill 进程ID; 杀死当前进程
注: 1、此方法的表内没有设置“索引”,如果是有索引的表,建议先删除索引,再开始删除表中数据。 2、mysql临时表,属于session级别,当session退出时,临时表被删除。也就是说,临时表将在你连接mysql期间存在。当断开时,mysql将自动删除表并释放所用的空间。当然你可以在仍然连接的时候删除表并释放空间。 3、TRUNCATE TABLE 不能用于参与了索引视图的表。
方法二 循环遍历删除
创建存储过程循环遍历删除,此处按日期举例:
DROP PROCEDURE IF EXISTS `_DELETE_TABLE_BY_DAY_`;
CREATE PROCEDURE `_DELETE_TABLE_BY_DAY_`(IN `_Sql_name` varchar(64),IN `_Begin_date` date,IN `_End_date` date)
BEGIN
DECLARE _datetmp DATE;
DECLARE _sql_for_delete varchar(500);
SET _datetmp = _Begin_date;
SET @_dates = _Begin_date;
SET @_EndT = (DATE_ADD(_End_date, INTERVAL 1 DAY));
SET _sql_for_delete = (CONCAT("SELECT COUNT(*) FROM ",_Sql_name," WHERE report_time BETWEEN ? AND ?"));
SET @sqls = _sql_for_delete;
PREPARE stmt FROM @sqls;
EXECUTE stmt USING @_dates, @_EndT;
DEALLOCATE PREPARE stmt;
WHILE @_dates <= _End_date DO
SET _sql_for_delete = (CONCAT("DELETE FROM ",_Sql_name," WHERE TO_DAYS(report_time) = TO_DAYS(?)"));
SET @sqls = _sql_for_delete;
PREPARE stmt FROM @sqls;
EXECUTE stmt USING @_dates;
DEALLOCATE PREPARE stmt;
SET @_dates = (DATE_ADD(_datetmp, INTERVAL 1 DAY));
SET _datetmp = @_dates;
END WHILE;
END;
此过程为按照日期删除指定表内的数据,可以以此为参照,写出按月删除等。 这边再附上一个按日期查询的相关语句: 查询当天的数据
select * from 表名 where TO_DAYS(时间字段)=TO_DAYS(NOW());
查询当周的数据
select * from 表名 where YEARWEEK(DATE_FORMAT(时间字段,’%Y-%m-%d’))=YEARWEEK(NOW());
查询当月的数据
select * from 表名 where DATE_FORMAT(时间字段,’%Y-%m’)=DATE_FORMAT(CURDATE(),’%Y-%m’);
查询昨天的数据
select * from 表名 where TO_DAYS(NOW())-TO_DAYS(时间字段)=1;
查询最近7天的数据
select * from 表名 where DATE_SUB(CURDATE(),INTERVAL 7 DAY)<=DATE(时间字段);
查询当年的数据
select * from 表名 where YEAR(时间字段) =YEAR(NOW());
查询上周的数据
select * from 表名 whereYEARWEEK(DATE_FORMAT(时间字段,’%Y-%m-%d’))=YEARWEEK(NOW())-1;
查询上月的数据
select *from 表名 where PERIOD_DIFF(DATE_FORMAT(NOW(),’%Y-%m’),DATE_FORMAT(时间字段,’%Y-%m’))=1;
注意:
- 存储动态SQL的值的变量不能是自定义变量,必须是用户变量或者全局变量 如:
set sql = 'xxx'; prepare stmt from sql ;是错的,正确为: set @sql = 'xxx'; prepare stmt from @sql ; - 即使 preparable_stmt 语句中的 ? 所代表的是一个字符串,你也不需要将 ? 用引号包含起来。
- 如果动态语句中用到了 in ,正常写法应该这样:
select * from table_name t where t.field1 in (1,2,3,4,...) ;则sql语句应该这样写:set @sql = "select * from user where user_id in (?,?,?) " - 如果不确定有几个
? ,可以用find_in_set函数 ,例如:
select * from table_name t where find_in_set(t.field1,'1,2,3,4');
参考文档1:mysql数据库快速删除千万级数据 参考文档2:MySQL根据日期查询
|