Usando o MySQL 5.6 com o mecanismo de armazenamento InnoDB para a maioria das tabelas. O tamanho do buffer pool do InnoDB é de 15 GB e os índices Innodb DB + estão em torno de 10 GB. O servidor possui 32 GB de RAM e está executando o Cent OS 7 x64.
Eu tenho uma tabela grande que contém cerca de 10 milhões de registros.
Eu 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. Tentei inserir dados em uma tabela MyISAM linha por linha e demorou 35 minutos.
Eu preciso pegar apenas 3 valores por linha de 10 a 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 é 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. Enquanto isso, outras atualizações acontecem, o que me fornece
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, 38.93 sec
enquanto no InnoDB, foram necessários 7 min e 5,21 segundos. 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 tempo, mas não mais que 24 horas. Eu estou olhando para fazê-lo em 1 hora sem bloquear a tabela inteira, pois mais tarde eu tenho que atualizar o índice de esfinge que depende dessa tabela. Não importa se as etapas demoram mais tempo, desde que o banco de dados esteja disponível para outras tarefas.
- Eu poderia modificar o formato csv em uma etapa de pré-processo. A única coisa que importa é a atualização rápida e sem travar.
- A tabela 2 é MyISAM. É a tabela recém-criada do arquivo csv usando o carregamento de dados infile. O tamanho do arquivo MYI é de 452 MB. A tabela 2 é indexada na coluna field1.
- MYD da tabela MyISAM é 663MB.
Atualização 3:
Aqui estão mais detalhes sobre as duas 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 a content
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 a junção 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 a marcação de banco de dados for concluída.
INDEX(field2, field3, field4)
(em qualquer ordem)? Por favor, mostre-nosSHOW CREATE TABLE
.UPDATEs
. Diga-nos exatamente como é a declaração direta para atualizar a tabela a partir dos dados csv. Então nós podemos ser capazes de ajudá-lo a desenvolver uma técnica que atenda às suas necessidades.update
, e verifique questão atualizado, obrigado.Respostas:
Com base na minha experiência, eu usaria LOAD DATA INFILE para importar seu arquivo CSV.
Exemplo que encontrei na internet Exemplo de dados de carregamento . Eu testei este exemplo na minha caixa e funcionou bem
Tabela de exemplo
Arquivo CSV de exemplo
Declaração de importação a ser executada no console do MySQL
Resultado
IGNORE simplesmente ignora a primeira linha que é o cabeçalho da coluna.
Após IGNORE, estamos especificando as colunas (ignorando a coluna2), a serem importadas, que correspondem a um dos critérios da sua pergunta.
Aqui está outro exemplo diretamente do Oracle: Exemplo de LOAD DATA INFILE
Isso deve ser suficiente para você começar.
fonte
À luz de todas as coisas mencionadas, parece que o gargalo é a junção em si.
ASPECTO Nº 1: Tamanho do Buffer de Ingresso
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 esse o caso, mantenha as chaves do buffer de junção na RAM.
Você tem 10 milhões de linhas vezes 4 bytes para cada chave. Isso é cerca de 40 milhões.
Tente aumentar a sessão para 42 milhões (um pouco maior que 40 milhões)
Se isso funcionar, prossiga para adicioná-lo ao
my.cnf
Reiniciar o mysqld não é necessário para novas conexões. Apenas corra
ASPECTO Nº 2: Operação de Ingresso
Você pode manipular o estilo da operação de junção, apertando o otimizador
De acordo com a documentação do MySQL sobre junções de acesso em bloco aninhado e chave em lote
Esta mesma página recomenda fazer isso:
ASPECTO Nº 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ê precisará reiniciar o MySQL para esta alteração
DE UMA CHANCE !!!
fonte
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 que linha por linha, mas ainda bloqueará todas as linhas da tabela por um período não trivial. Se esse tempo de bloqueio for mais importante do que quanto tempo leva todo o processo, ...
Se nada mais estiver escrevendo na tabela, então ...
CREATE TABLE
que corresponda ao CSV; sem índices, exceto o que é necessário noJOIN
noUPDATE
. Se ú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 índices desnecessários forem deixados de lado.
O passo 5 é "instantâneo".
fonte
pt-online-schema-digest
; cuida de tais problemas através de umTRIGGER
.LOAD DATA
. Adicionar índices desnecessários é caro (com o tempo).AUTO_INCREMENT
, e depois dividindo 1K linhas por vez, com base no PK. Mas preciso ver todos os requisitos e o esquema da tabela antes de tentar detalhar os detalhes.PRIMARY index
, mas enquanto a divisão em 50k usando a consulta de pedidos leva mais tempo., seria melhor se eu criar incremento automático? e defini-lo comoPRIMARY index
?Você disse:
Muitas dessas declarações podem ser contraditórias. Por exemplo, grandes atualizações sem bloquear a tabela. Ou evitando as condições de corrida sem o uso de uma transação gigante.
Além disso, como sua tabela é fortemente indexada, inserções e 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 também evitar o registro de meio milhão de atualizações em uma única transação.
Isso libera você para executar 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 as condições de corrida (enquanto atualiza linha por linha) executando uma verificação de que uma atualização posterior ainda não ocorreu (
UPDATE ... WHERE pk = [pk] AND updated < [batchfile date]
)E, o mais importante, isso permite executar atualizações paralelas .
Correndo o mais rápido possível - paralelizando
Com este carimbo de data e hora, verifique agora:
mysql
execute cada arquivo sql.(por exemplo,
bash
vejasplit
exargs -P
encontre maneiras de executar facilmente um comando de várias maneiras paralelas. O grau de paralelismo depende de quantos threads você deseja dedicar à atualização )fonte
Grandes atualizações são vinculadas à E / S. Eu sugeriria:
fonte
Para
UPDATE
que você corra rápido, você precisaPode estar em qualquer mesa. Os três campos podem estar em qualquer ordem.
Isso facilitará a
UPDATE
capacidade de corresponder rapidamente as linhas entre as duas tabelas.E torne os tipos de dados iguais nas duas tabelas (ambas
INT SIGNED
ou ambasINT UNSIGNED
).fonte
EXPLAIN UPDATE ...;
.