Comando DELETE não concluído na tabela de 30.000.000 de linhas

22

Eu herdei um banco de dados e estou procurando limpá-lo e acelerá-lo. Eu tenho uma tabela que contém 30.000.000 linhas, muitas das quais são dados indesejados inseridos devido a um erro em nome do nosso programador. Antes de adicionar índices novos e mais otimizados, converti a tabela do MyISAM para o InnoDB e pretendo excluir muitas das linhas que contêm dados indesejados.

O banco de dados é MySQL 5.0 e eu tenho acesso root ao servidor. Eu estava executando esses comandos primeiro pelo Adminer e depois pelo phpMyAdmin, ambos com os mesmos resultados.

O comando que estou executando é,

DELETE
FROM `tablename`
WHERE `columnname` LIKE '-%'

Essencialmente, exclua tudo nesta coluna que comece com um traço -.

Ele dura cerca de 3-5 minutos e, quando vejo a lista de processos, desaparece.

Então eu corro,

SELECT *
FROM `tablename`
WHERE `columnname` LIKE '-%'

e retorna milhões de linhas.

Por que minha declaração de exclusão não está sendo concluída?

PS, estou ciente de como o MySQL 5.0 está desatualizado. Estou trabalhando para mover o banco de dados para o MySQL 5.6 w InnoDB (talvez MariaDB 10 w XtraDB), mas até que isso aconteça, estou procurando responder isso com o banco de dados como está.

-

Editar removido, veja minha resposta.

bafromca
fonte

Respostas:

24

Veja a arquitetura do InnoDB (foto de Percona CTO Vadim Tkachenko)

InnoDB Plumbing

As linhas que você está excluindo estão sendo gravadas nos logs de desfazer. O arquivo ibdata1 deve estar crescendo agora durante a exclusão. De acordo com o mysqlperformanceblog.comReasons for run-away main Innodb Tablespace :

  • Muitas mudanças transacionais
  • Transações muito longas
  • Linha de purga retardada

No seu caso, o motivo 1 ocuparia um segmento de reversão, juntamente com parte do espaço para desfazer, uma vez que você está excluindo linhas. Essas linhas devem ficar no ibdata1 até que a exclusão seja concluída. Esse espaço é descartado logicamente, mas o espaço em disco não diminui.

Você precisa matar essa exclusão agora. Depois de eliminar a consulta de exclusão, ela reverterá as linhas excluídas.

Você faz isso:

CREATE TABLE tablename_new LIKE tablename;
INSERT INTO tablename_new SELECT * FROM tablename WHERE `columnname` NOT LIKE '-%';
RENAME TABLE
    tablename TO tablename_old,
    tablename_new TO tablename
;
DROP TABLE tablename_old;

Você poderia ter feito isso com a versão MyISAM da tabela primeiro. Em seguida, converta-o para o InnoDB.

RolandoMySQLDBA
fonte
21

Acho que podemos ter complicado demais a resposta necessária no meu caso . Não tenho dúvidas de que Roland e Rick James estão corretos ao criar uma tabela temporária, injetando apenas linhas que passam no filtro, NOT LIKE '-%'mas a solução para mim foi "mais fácil" porque havia um erro importante que eu desconhecia até agora e por que peço desculpas.

Fiz a consulta no mysqlprompt interativo e notei a mensagem de erro,

mysql> DELETE FROM `slugs` WHERE `slug` LIKE '-%';
ERROR 1206 (HY000): The total number of locks exceeds the lock table size

Ao pesquisar o erro no Google, achei que a solução era aumentar innodb_buffer_pool_sizepor meio do /etc/my.cnfarquivo e reiniciar o daemon mysql. Para o meu servidor, ele foi definido como padrão 8Me eu o aumentei para 1G(o servidor possui 32 GB e esta é a única tabela atualmente no InnoDB).

mysql> DELETE FROM `slugs` WHERE `slug` LIKE '-%';
Query OK, 23517226 rows affected (27 min 33.23 sec)

Pude executar o comando e excluir 23 milhões de registros em ~ 27 minutos.

Para aqueles curiosos sobre o que innodb_buffer_pool_sizedeve ser definido, anote a quantidade de RAM que você tem e, em seguida, dê uma olhada neste segmento que fornece uma estimativa sugerida em GBs.

bafromca
fonte
12

A sugestão de Roland pode ser acelerada, fazendo as duas coisas ao mesmo tempo:

CREATE TABLE tablename_new LIKE tablename;
ALTER TABLE tablename_new ENGINE = InnoDB;
INSERT INTO tablename_new 
    SELECT * FROM tablename WHERE `columnname` NOT LIKE '-%' ORDER BY primary_key;
RENAME TABLE
    tablename TO tablename_old,
    tablename_new TO tablename
;
DROP TABLE tablename_old;

Mas aqui é um blog que explica como fazer grandes DELETEs em pedaços, em vez de aparentemente levando uma eternidade: http://mysql.rjweb.org/doc.php/deletebig A essência é caminhar através da tabela através da PK, fazendo 1K linhas de uma só vez. (É claro que há mais detalhes a serem observados.)

E este blog aborda possíveis problemas na conversão para o InnoDB: http://mysql.rjweb.org/doc.php/myisam2innodb

Rick James
fonte
5

Meu primeiro instinto seria fazer várias exclusões menores, limitando o número de resultados da consulta e executando a consulta várias vezes:

DELETE
FROM `tablename`
WHERE `columnname` LIKE '-%' LIMIT 1000000
kristianp
fonte
Uma desvantagem dessa abordagem: cada exclusão leva mais e mais tempo. Isso ocorre porque ele precisa pular mais e mais linhas que não correspondem ao WHERE.
Rick James
É verdade, mas se esse processo não ocorrer com muita frequência, várias varreduras completas de tabelas não devem ser tão ruins quanto o problema original que está sendo resolvido, ou seja, a consulta nunca é concluída devido ao tamanho do log de desfazer.
kristianp 4/16
Ponto válido. (Eu faria o LIMITinferior; dizem 10000.)
Rick James
4

A solução mais fácil é simplesmente não fazer isso - faça uma exclusão menor, que pode ser processada com mais facilidade.

Nesse caso, eu recomendaria tentar exclusões seqüenciais do formulário:

DELETE
FROM `tablename`
WHERE `columnname` LIKE '-a%'
jmoreno
fonte
2

Talvez você possa fazer algo assim:

  • Adicione um novo campo chamado deleted.
  • Faça uma atualização como UPDATE tablename SET deleted=1 WHERE `columnname` LIKE '-a%'.
  • Defina cronpara excluir isso durante a noite.
Mike Minaev
fonte
A atualização pode demorar até a exclusão.
Rick James