Usando MySQL 5.6 com mecanismo de armazenamento InnoDB para a maioria das tabelas. O tamanho do buffer pool do InnoDB é de 15 GB e os índices do Innodb DB + são de cerca de 10 GB. O servidor tem 32 GB de RAM e está executando o Cent OS 7 x64.
Eu tenho uma grande tabela que contém cerca de 10 milhões + registros.
Recebo um arquivo de despejo atualizado de um servidor remoto a cada 24 horas. O arquivo está no formato csv. Eu não tenho controle sobre esse formato. O arquivo tem ~ 750 MB. Eu tentei inserir dados em uma tabela MyISAM linha por linha e levou 35 minutos.
Eu preciso pegar apenas 3 valores por linha de 10-12 do arquivo e atualizá-lo no banco de dados.
Qual é a melhor maneira de conseguir algo assim?
Eu preciso fazer isso diariamente.
Atualmente o Flow está assim:
- mysqli_begin_transaction
- Ler arquivo de despejo linha por linha
- Atualize cada registro Linha por Linha.
- mysqli_commit
As operações acima levam cerca de 30 a 40 minutos para serem concluídas e, ao fazer isso, há outras atualizações em andamento, o que me dá
Tempo limite de espera de bloqueio excedido; tente reiniciar a transação
Atualização 1
carregamento de dados em nova tabela usando LOAD DATA LOCAL INFILE
. No MyISAM demorou 38.93 sec
enquanto no InnoDB demorou 7 min 5,21 seg. Então eu fiz:
UPDATE table1 t1, table2 t2
SET
t1.field1 = t2.field1,
t1.field2 = t2.field2,
t1.field3 = t2.field3
WHERE t1.field10 = t2.field10
Query OK, 434914 rows affected (22 hours 14 min 47.55 sec)
Atualização 2
mesma atualização com consulta de junção
UPDATE table1 a JOIN table2 b
ON a.field1 = b.field1
SET
a.field2 = b.field2,
a.field3 = b.field3,
a.field4 = b.field4
(14 hours 56 min 46.85 sec)
Esclarecimentos de perguntas nos comentários:
- Cerca de 6% das linhas da tabela serão atualizadas pelo arquivo, mas às vezes pode chegar a 25%.
- Existem índices nos campos que estão sendo atualizados. Existem 12 índices na tabela e 8 índices incluem os campos de atualização.
- Não é necessário fazer a atualização em uma transação. Pode levar algum tempo, mas não mais de 24 horas. Estou procurando fazer isso em 1 hora sem travar a tabela inteira, pois depois tenho que atualizar o índice sphinx que depende dessa tabela. Não importa se as etapas demoram mais, desde que o banco de dados esteja disponível para outras tarefas.
- Eu poderia modificar o formato csv em uma etapa de pré-processamento. A única coisa que importa é a atualização rápida e sem travamento.
- A Tabela 2 é MyISAM. É a tabela recém-criada do arquivo csv usando o arquivo de dados de carga. O tamanho do arquivo MYI é de 452 MB. A Tabela 2 está indexada na coluna field1.
- O MYD da tabela MyISAM é de 663 MB.
Atualização 3:
aqui estão mais detalhes sobre ambas as tabelas.
CREATE TABLE `content` (
`hash` char(40) CHARACTER SET ascii NOT NULL DEFAULT '',
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
`og_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
`keywords` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
`files_count` smallint(5) unsigned NOT NULL DEFAULT '0',
`more_files` smallint(5) unsigned NOT NULL DEFAULT '0',
`files` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '0',
`category` smallint(3) unsigned NOT NULL DEFAULT '600',
`size` bigint(19) unsigned NOT NULL DEFAULT '0',
`downloaders` int(11) NOT NULL DEFAULT '0',
`completed` int(11) NOT NULL DEFAULT '0',
`uploaders` int(11) NOT NULL DEFAULT '0',
`creation_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`upload_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`last_updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`vote_up` int(11) unsigned NOT NULL DEFAULT '0',
`vote_down` int(11) unsigned NOT NULL DEFAULT '0',
`comments_count` int(11) NOT NULL DEFAULT '0',
`imdb` int(8) unsigned NOT NULL DEFAULT '0',
`video_sample` tinyint(1) NOT NULL DEFAULT '0',
`video_quality` tinyint(2) NOT NULL DEFAULT '0',
`audio_lang` varchar(127) CHARACTER SET ascii NOT NULL DEFAULT '',
`subtitle_lang` varchar(127) CHARACTER SET ascii NOT NULL DEFAULT '',
`verified` tinyint(1) unsigned NOT NULL DEFAULT '0',
`uploader` int(11) unsigned NOT NULL DEFAULT '0',
`anonymous` tinyint(1) NOT NULL DEFAULT '0',
`enabled` tinyint(1) unsigned NOT NULL DEFAULT '0',
`tfile_size` int(11) unsigned NOT NULL DEFAULT '0',
`scrape_source` tinyint(1) unsigned NOT NULL DEFAULT '0',
`record_num` int(11) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`record_num`),
UNIQUE KEY `hash` (`hash`),
KEY `uploaders` (`uploaders`),
KEY `tfile_size` (`tfile_size`),
KEY `enabled_category_upload_date_verified_` (`enabled`,`category`,`upload_date`,`verified`),
KEY `enabled_upload_date_verified_` (`enabled`,`upload_date`,`verified`),
KEY `enabled_category_verified_` (`enabled`,`category`,`verified`),
KEY `enabled_verified_` (`enabled`,`verified`),
KEY `enabled_uploader_` (`enabled`,`uploader`),
KEY `anonymous_uploader_` (`anonymous`,`uploader`),
KEY `enabled_uploaders_upload_date_` (`enabled`,`uploaders`,`upload_date`),
KEY `enabled_verified_category` (`enabled`,`verified`,`category`),
KEY `verified_enabled_category` (`verified`,`enabled`,`category`)
) ENGINE=InnoDB AUTO_INCREMENT=7551163 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=FIXED
CREATE TABLE `content_csv_dump_temp` (
`hash` char(40) CHARACTER SET ascii NOT NULL DEFAULT '',
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`category_id` int(11) unsigned NOT NULL DEFAULT '0',
`uploaders` int(11) unsigned NOT NULL DEFAULT '0',
`downloaders` int(11) unsigned NOT NULL DEFAULT '0',
`verified` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`hash`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
e aqui está a consulta de atualização que atualiza content
a tabela usando dados decontent_csv_dump_temp
UPDATE content a JOIN content_csv_dump_temp b
ON a.hash = b.hash
SET
a.uploaders = b.uploaders,
a.downloaders = b.downloaders,
a.verified = b.verified
atualização 4:
todos os testes acima foram feitos na máquina de teste., mas agora fiz os mesmos testes na máquina de produção, e as consultas são muito rápidas.
mysql> UPDATE content_test a JOIN content_csv_dump_temp b
-> ON a.hash = b.hash
-> SET
-> a.uploaders = b.uploaders,
-> a.downloaders = b.downloaders,
-> a.verified = b.verified;
Query OK, 2673528 rows affected (7 min 50.42 sec)
Rows matched: 7044818 Changed: 2673528 Warnings: 0
Eu peço desculpas pelo meu erro. É melhor usar join em vez de cada atualização de registro. agora estou tentando melhorar o mpre usando o índice sugerido por rick_james , será atualizado assim que o benchmarking for feito.
À luz de todas as coisas mencionadas, parece que o gargalo é a própria junção.
ASPECTO #1: Tamanho do Buffer de Junção
Com toda a probabilidade, seu join_buffer_size provavelmente é muito baixo.
De acordo com a documentação do MySQL sobre como o MySQL usa o cache de buffer de junção
Sendo este o caso, faça com que as chaves do buffer de junção permaneçam na RAM.
Você tem 10 milhões de linhas vezes 4 bytes para cada chave. Isso é cerca de 40M.
Tente aumentar na sessão para 42M (um pouco maior que 40M)
Se isso funcionar, continue a adicionar isso a
my.cnf
Reiniciar o mysqld não é necessário para novas conexões. Apenas corra
ASPECTO #2 : Operação de Junção
Você pode manipular o estilo da operação de junção ajustando o otimizador
De acordo com a documentação do MySQL no bloco aninhado-loop e junções de acesso de chave em lote
Esta mesma página recomenda fazer isso:
ASPECTO #3: Gravando atualizações no disco (OPCIONAL)
A maioria esquece de aumentar o innodb_write_io_threads para gravar páginas sujas fora do buffer pool mais rapidamente.
Você terá que reiniciar o MySQL para esta mudança
DE UMA CHANCE !!!
Com base na minha experiência, eu usaria LOAD DATA INFILE para importar seu arquivo CSV.
Exemplo que encontrei na internet Exemplo de Load Data . Eu testei este exemplo na minha caixa e funcionou bem
Tabela de exemplo
Exemplo de arquivo CSV
Instrução de importação a ser executada no console MySQL
Resultado
IGNORE simplesmente ignora a primeira linha que são os cabeçalhos das colunas.
Após IGNORE, estamos especificando as colunas (pulando coluna2), a serem importadas, que correspondem a um dos critérios da sua pergunta.
Aqui está outro exemplo diretamente do Oracle: Exemplo LOAD DATA INFILE
Isso deve ser suficiente para você começar.
CREATE TABLE
que corresponde ao CSVLOAD DATA
nessa mesaUPDATE real_table JOIN csv_table ON ... SET ..., ..., ...;
DROP TABLE csv_table;
A etapa 3 será muito mais rápida do que linha por linha, mas ainda bloqueará todas as linhas da tabela por um período de tempo não trivial. Se esse tempo de bloqueio for mais importante do que o tempo que todo o processo leva, ...
Se nada mais estiver escrevendo na mesa, então...
CREATE TABLE
que corresponde ao CSV; nenhum índice, exceto o que é necessário noJOIN
arquivoUPDATE
. Se for único, faça-oPRIMARY KEY
.LOAD DATA
nessa mesareal_table
paranew_table
(CREATE ... SELECT
)UPDATE new_table JOIN csv_table ON ... SET ..., ..., ...;
RENAME TABLE real_table TO old, new_table TO real_table;
DROP TABLE csv_table, old;
A etapa 3 é mais rápida que a atualização, especialmente se os índices desnecessários forem deixados de fora.
O passo 5 é "instantâneo".
Você disse:
Muitas dessas afirmações podem ser contraditórias. Por exemplo, grandes atualizações sem travar a tabela. Ou evitando condições de corrida sem usar uma transação gigante.
Além disso, como sua tabela é fortemente indexada, tanto as inserções quanto as atualizações podem ser lentas.
Evitando condições de corrida
Se você conseguir adicionar um carimbo de data/hora atualizado à sua tabela, poderá resolver as condições de corrida e evitar registrar meio milhão de atualizações em uma única transação.
Isso libera você para realizar atualizações linha por linha (como você faz atualmente), mas com confirmação automática ou lotes de transações mais razoáveis.
Você evita condições de corrida (ao atualizar linha por linha) realizando uma verificação de que uma atualização posterior ainda não ocorreu (
UPDATE ... WHERE pk = [pk] AND updated < [batchfile date]
)E, mais importante, isso permite que você execute atualizações paralelas .
Correndo o mais rápido possível — Paralelizando
Com esta verificação de carimbo de hora agora em vigor:
mysql
execute cada arquivo sql.(por exemplo
bash
, vejasplit
e vejaxargs -P
maneiras de executar facilmente um comando de várias maneiras paralelas. O grau de paralelismo depende de quantos threads você deseja dedicar à atualização )Para o
UPDATE
correr rápido, você precisaPode estar em qualquer mesa. Os três campos podem estar em qualquer ordem.
Isso facilitará a
UPDATE
possibilidade de combinar rapidamente as linhas entre as duas tabelas.And make the datatypes the same in the two tables (both
INT SIGNED
or bothINT UNSIGNED
).Large updates are I/O bound. I would suggest: