cod*_*gle 6 mysql stored-procedures
我创建了一个程序来循环遍历大型 InnoDB 表并删除一大块行,然后暂停并重复直到达到允许的最大字段值。
启用了二进制日志记录,但我将其保留为默认NONDETERMINISTIC
设置,所以我认为这并不重要。
CALL 命令包括每行要删除的行数 ( num_rows
),以及要删除的最后一个字段值 ( maxId
)。(我正在使用主键 ID)。
第一部分工作正常,它循环遍历并每次通过删除一大块行。
有一个If
语句来检查@z
当前块的最后一个 id ( ) ,maxId
如果最后一个 id 为空或大于 ,它应该退出循环maxId
。但它永远不会触发并离开循环。
我无法确定我做错了什么,想法?
更新:
我已经确定了问题的根源,但我不确定如何解决它。
@z
要删除的块的最后一个 id使用@sql_text2
查询中的限制语句设置。
但最后,该查询中的限制选项导致它返回一个空结果集,因为偏移量大于结果集并且要返回的行数为 1。
例如,我运行了将在最后运行的查询,它返回一个空的结果集(为了清晰起见,更改了数字)。使用id
为keyfield
:
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
吗?
我仍然很好奇最后一次传递给@z
by 的值@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)
有两种可能的解决方案:
@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