当数据库内的数据达到百万甚至千万条数的时候,直接DELETE FROM tablename 可能会耗费非常久的时间。
所以这里提供我实际使用的方法,可能不是最合适的方法,后续有更好的方法会进行补充。

方法一 TRUNCATE TABLE

通过TRUNCATE TABLE 来删除表中百万条数据的情况

  1. 创建临时表备份数据
    比如我只想保留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’的数据。

  1. 截断表
TRUNCATE TABLE tablename ;
  1. 备份的数据插入到表中
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;

注意:

  1. 存储动态SQL的值的变量不能是自定义变量,必须是用户变量或者全局变量 如:set sql = 'xxx'; prepare stmt from sql;是错的,正确为: set @sql = 'xxx'; prepare stmt from @sql;
  2. 即使 preparable_stmt 语句中的 ? 所代表的是一个字符串,你也不需要将 ? 用引号包含起来。
  3. 如果动态语句中用到了 in ,正常写法应该这样:select * from table_name t where t.field1 in (1,2,3,4,...);则sql语句应该这样写:set @sql = "select * from user where user_id in (?,?,?) "
  4. 如果不确定有几个?,可以用find_in_set函数,例如:
select * from table_name t where find_in_set(t.field1,'1,2,3,4');

参考文档1:mysql数据库快速删除千万级数据
参考文档2:MySQL根据日期查询

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐