当数据库内的数据达到百万甚至千万条数的时候,直接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
#Routine body goes here...
DECLARE _datetmp DATE;
#DECLARE _dates DATE;
DECLARE _sql_for_delete varchar(500); -- 定义预处理sql语句
SET _datetmp = _Begin_date;
SET @_dates = _Begin_date;
SET @_EndT = (DATE_ADD(_End_date, INTERVAL 1 DAY)); -- BETWEEN AND -> 范围 [_Begin_date, _End_date)
-- 查询符合条件的个数
SET _sql_for_delete = (CONCAT("SELECT COUNT(*) FROM ",_Sql_name," WHERE report_time BETWEEN ? AND ?")); -- 拼接查询sql语句
SET @sqls = _sql_for_delete;
PREPARE stmt FROM @sqls; -- 预处理动态sql语句
EXECUTE stmt USING @_dates, @_EndT; -- 执行sql语句
DEALLOCATE PREPARE stmt; -- 释放prepare
WHILE @_dates <= _End_date DO
SET _sql_for_delete = (CONCAT("DELETE FROM ",_Sql_name," WHERE TO_DAYS(report_time) = TO_DAYS(?)")); -- 拼接查询sql语句
SET @sqls = _sql_for_delete;
PREPARE stmt FROM @sqls; -- 预处理动态sql语句
EXECUTE stmt USING @_dates; -- 执行sql语句
DEALLOCATE PREPARE stmt; -- 释放prepare
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根据日期查询