MySQL 存储过程:遍历表,删除行。逻辑问题:不会因为查询中的 LIMIT 选项而退出循环

cod*_*gle 6 mysql stored-procedures

我创建了一个程序来循环遍历大型 InnoDB 表并删除一大块行,然后暂停并重复直到达到允许的最大字段值。

启用了二进制日志记录,但我将其保留为默认NONDETERMINISTIC设置,所以我认为这并不重要。

CALL 命令包括每行要删除的行数 ( num_rows),以及要删除的最后一个字段值 ( maxId)。(我正在使用主键 ID)。

第一部分工作正常,它循环遍历并每次通过删除一大块行。

有一个If语句来检查@z当前块的最后一个 id ( ) ,maxId如果最后一个 id 为空或大于 ,它应该退出循环maxId。但它永远不会触发并离开循环。

我无法确定我做错了什么,想法?


更新:
我已经确定了问题的根源,但我不确定如何解决它。

@z要删除的块的最后一个 id使用@sql_text2查询中的限制语句设置。

但最后,该查询中的限制选项导致它返回一个空结果集,因为偏移量大于结果集并且要返回的行数为 1。

例如,我运行了将在最后运行的查询,它返回一个空的结果集(为了清晰起见,更改了数字)。使用idkeyfield

SELECT id FROM table_name WHERE id >= 7000 ORDER BY id LIMIT 1000,1;
Run Code Online (Sandbox Code Playgroud)

@sql_text2程序将运行的查询(使用INTO @z):

SELECT id INTO @z FROM table_name WHERE id >= 7000 ORDER BY id LIMIT 1000,1;
Run Code Online (Sandbox Code Playgroud)

表中的最后一个 id 是 7817。

该语句表示从前一次传递中删除的最后一个 id (7000) 开始获取 1000 行,并返回结果集的一行(最后一行)。

结果集的最后一个 id 将是 7999,但这在表中不存在。

所以我不确定分配给什么值@z并且无法检查它是否触发LEAVE loop_label;

IF块中,我正在检查is null并添加了一个检查= "",但它仍然不匹配。有谁知道@z在结果集为空的情况下会分配给什么值?

我在“用户定义的变量”的文档中找到了这个:

如果引用未初始化的变量,则它的值为 NULL 且类型为字符串。

@z 之前已初始化并使用过,当将空结果集分配为它的值时,它具有什么状态?

有没有类似PHPisset()函数的命令


解决方案:
@a1ex07 的评论有一个修复。@z在循环开始时设置为 null。

我不明白为什么这解决了这个问题。

我想可能是因为@z仅在循环内设置,因此每次循环开始时都会重置,但我尝试@z在循环开始之前进行设置(先设置为 null,然后设置为 1),但这导致了最初的问题。

它可能与在循环结束时设置@a为 的值有关@z吗?

我仍然很好奇最后一次传递给@zby 的值@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);
Run Code Online (Sandbox Code Playgroud)

rub*_*o77 4

有两种可能的解决方案:

@z在循环的一开始就设置为 null (在SET @sql_text2 = concat('......之前)


或者代替

SELECT id INTO @z FROM table_name WHERE id >= 7000 ORDER BY id LIMIT 1000,1;
Run Code Online (Sandbox Code Playgroud)

用这个:

SET @z = (SELECT id FROM table_name WHERE id >= 7000 ORDER BY id LIMIT 1000,1);
Run Code Online (Sandbox Code Playgroud)

解释:

当没有找到行时,变量与之前的值没有变化...所以这并不是说你必须“在使用它之前将其设置为 null”,而是你必须在再次使用它之前将其重置为 null,如果您正在做类似的事情,SELECT ... INTO如果没有找到任何内容,则不会重置该值。
请参阅:https ://dba.stackexchange.com/a/35207/12923