我创建了一个过程来遍历大型 InnoDB 表并删除一大块行,然后暂停并重复,直到达到允许的最大字段值。
二进制日志记录已启用,但我将其保留为默认NONDETERMINISTIC
设置,所以我认为这并不重要。
CALL 命令包括每行要删除的行数 ( num_rows
),以及要删除的最后一个字段值 ( maxId
)。(我正在使用主键 ID)。
第一部分工作正常,它循环遍历并每次通过删除一大块行。
有一条If
语句可以检查@z
当前块的最后一个 id() 与 . maxId
,如果最后一个 id 为 null 或大于maxId
. 但它永远不会触发并离开循环。
我无法确定我做错了什么,想法?
更新:
我已经确定了问题的根源,但我不确定如何解决它。
要删除的块的最后一个 id@z
在@sql_text2
查询中使用限制语句设置。
但最后,该查询中的限制选项会导致它返回一个空的结果集,因为偏移量大于结果集并且要返回的行是一。
例如,我运行了将在最后运行的查询,它返回一个空结果集(为清楚起见更改了数字)。用于:id
_keyfield
SELECT id FROM table_name WHERE id >= 7000 ORDER BY id LIMIT 1000,1;
@sql_text2
将由过程运行的查询(带有INTO @z
):
SELECT id INTO @z FROM table_name WHERE id >= 7000 ORDER BY id LIMIT 1000,1;
表中的最后一个 id 是 7817。
该语句说要从上一次传递(7000)中删除的最后一个 id 开始获取 1000 行,并返回结果集的一行(最后一行)。
结果集的最后一个 id 将是 7999,但在表中不存在。
所以我不确定分配给什么值@z
并且无法检查它是否触发LEAVE loop_label;
在IF
块中,我正在检查is null
并添加了检查= ""
,但它仍然不匹配。有谁知道@z
在结果集为空的情况下会分配什么值?
我在“用户定义变量”的文档中找到了这个:
如果你引用一个没有被初始化的变量,它的值是 NULL 和一个字符串类型。
@z
之前已经初始化和使用过,当分配一个空结果集作为它的值时它有什么状态?
有没有类似PHPisset()
函数的命令
解决方案:
@a1ex07 的评论已修复。在循环开始时设置@z
为 null。
我不明白为什么这解决了这个问题。
我认为这可能是因为@z
仅在循环内部设置,因此每次循环开始时都会重置,但我尝试@z
在循环开始之前设置(先设置为 null,然后设置为 1),但这导致了原始问题。
它可能与设置为循环结束时的@a
值有关吗?@z
我仍然对最后一次通过时分配的值感到@z
好奇@sql_text2
。
这是过程创建语句,后面是用于实现它的 Call 语句。
更新以下代码已通过修复更新并正常工作:
delimiter //
Create PROCEDURE `removeProcessed`(table_name VARCHAR(255), keyField VARCHAR(255), maxId INT, num_rows INT)
BEGIN
SET @table_name = table_name;
SET @keyField = keyField;
SET @maxId = maxId;
SET @num_rows = num_rows;
SET @sql_text1 = concat('SELECT MIN(',@keyField,') INTO @a FROM ',@table_name);
PREPARE stmt1 FROM @sql_text1;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
loop_label: LOOP
SET @z = NULL;
SET @sql_text2 = concat('SELECT ',@keyField,' INTO @z FROM ',@table_name,' WHERE ',@keyField,' >= ',@a,' ORDER BY ',@keyField,' LIMIT ',@num_rows,',1');
PREPARE stmt2 FROM @sql_text2;
EXECUTE stmt2;
DEALLOCATE PREPARE stmt2;
If @z is null THEN
LEAVE loop_label;
ELSEIF @z = "" THEN
LEAVE loop_label;
ELSEIF @z > @maxId THEN
LEAVE loop_label;
END IF;
SET @sql_text3 = concat('DELETE FROM ',@table_name,' WHERE ',@keyField,' >= ',@a,' AND ',@keyField,' <= ',@z);
PREPARE stmt3 FROM @sql_text3;
EXECUTE stmt3;
DEALLOCATE PREPARE stmt3;
SET @a = @z;
SELECT SLEEP(1);
END LOOP;
SET @sql_text4 = concat('DELETE FROM ',@table_name,' WHERE ',@keyField,' <= ',@maxId);
PREPARE stmt4 FROM @sql_text4;
EXECUTE stmt4;
DEALLOCATE PREPARE stmt4;
END
//
delimiter ;
CALL db_name.removeProcessed('table_name', 'key_field_name', 1300731617, 100000);
有两种可能的解决方案:
在循环的最开始设置
@z
为 null (在SET @sql_text2 = concat('
....之前)或代替
用这个:
解释: