Eu criei um procedimento para percorrer grandes tabelas InnoDB e excluir um pedaço de linhas, depois pausar e repetir até que o valor máximo do campo permitido seja alcançado.
O log binário está habilitado, mas estou deixando-o na NONDETERMINISTIC
configuração padrão, então não acho que isso importe.
O comando CALL inclui o número de linhas a serem excluídas por linha ( num_rows
) e o último valor de campo a ser excluído ( maxId
). (Estou usando o id de chave primária).
A primeira parte funciona corretamente, faz um loop e exclui um pedaço de linhas a cada passagem.
Há uma If
instrução para verificar o último id ( @z
) do pedaço atual em relação maxId
a , ele deve sair do loop se o último id for nulo ou for maior que o maxId
. Mas nunca dispara e sai do loop.
Não consigo determinar o que fiz de errado, pensamentos?
ATUALIZAÇÃO:
Eu determinei a origem do problema, mas não tenho certeza de como corrigi-lo.
O último id @z
do bloco a ser excluído é definido com uma instrução de limite na @sql_text2
consulta.
Mas, no final, a opção limite nessa consulta faz com que ela retorne um conjunto de resultados vazio porque o deslocamento é maior que o conjunto de resultados e as linhas a serem retornadas são um.
Por exemplo, executei a consulta que seria executada no final e ela retorna um conjunto de resultados vazio (números alterados para maior clareza). Usando id
para keyfield
:
SELECT id FROM table_name WHERE id >= 7000 ORDER BY id LIMIT 1000,1;
A consulta @sql_text2
como seria executada pelo procedimento (com INTO @z
):
SELECT id INTO @z FROM table_name WHERE id >= 7000 ORDER BY id LIMIT 1000,1;
O último id na tabela é 7817.
A instrução diz para obter 1000 linhas começando com o último id excluído na passagem anterior (7000) e retornar uma linha (a última linha) do conjunto de resultados.
O último id do conjunto de resultados seria 7999, mas isso não existe na tabela.
Portanto, não tenho certeza de qual valor está atribuído @z
e não posso verificar se ele é acionadoLEAVE loop_label;
No IF
bloco, eu estava verificando is null
e adicionei um cheque = ""
e ainda não corresponde. Alguém sabe qual valor seria atribuído @z
no caso de um conjunto de resultados vazio?
Eu encontrei isso nos documentos para "Variáveis definidas pelo usuário":
Se você se referir a uma variável que não foi inicializada, ela terá um valor NULL e um tipo de string.
@z
foi inicializado e usado anteriormente, que status ele tem quando atribuído a um conjunto de resultados vazio como seu valor?
Existe um comando como a isset()
função PHP
SOLUÇÃO:
O comentário de @a1ex07 teve uma correção. Defina @z
como null no início do loop.
Eu não entendo porque isso resolveu o problema embora.
Eu pensei que talvez fosse porque @z
só é definido dentro do loop e, portanto, é redefinido toda vez que o loop começa, mas tentei configurar @z
antes do início do loop (para null e depois 1), mas isso causou o problema original.
Poderia ter algo a ver com a definição @a
do valor de @z
no final do loop?
Ainda estou curioso sobre qual valor é atribuído @z
por @sql_text2
na última passagem.
Aqui está a instrução de criação do procedimento, seguida pela instrução Call usada para implementá-la.
Atualização O código a seguir foi atualizado com a correção e funciona corretamente:
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);
Há duas soluções possíveis:
definido
@z
como null no início do seu loop (antes deSET @sql_text2 = concat('
....)ou em vez de
usa isto:
Explicação: