AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 119621
Accepted
user16108
user16108
Asked: 2015-10-30 18:32:31 +0800 CST2015-10-30 18:32:31 +0800 CST 2015-10-30 18:32:31 +0800 CST

Como atualizar mais de 10 milhões de linhas na tabela única do MySQL o mais rápido possível?

  • 772

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:

  1. mysqli_begin_transaction
  2. Ler arquivo de despejo linha por linha
  3. Atualize cada registro Linha por Linha.
  4. 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 secenquanto 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 contenta 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.

mysql performance
  • 6 6 respostas
  • 138666 Views

6 respostas

  • Voted
  1. RolandoMySQLDBA
    2015-12-29T13:56:12+08:002015-12-29T13:56:12+08:00

    À 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

    Armazenamos apenas as colunas usadas no buffer de junção, não as linhas inteiras.

    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)

    SET join_buffer_size = 1024 * 1024 * 42;
    UPDATE table1 a JOIN table2 b 
    ON a.field1 = b.field1 
    SET 
    a.field2 = b.field2,
    a.field3 = b.field3,
    a.field4 = b.field4;
    

    Se isso funcionar, continue a adicionar isso amy.cnf

    [mysqld]
    join_buffer_size = 42M
    

    Reiniciar o mysqld não é necessário para novas conexões. Apenas corra

    mysql> SET GLOBAL join_buffer_size = 1024 * 1024 * 42;
    

    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

    Quando o BKA é usado, o valor de join_buffer_size define o tamanho do lote de chaves em cada solicitação ao mecanismo de armazenamento. Quanto maior o buffer, mais acesso sequencial será à tabela à direita de uma operação de junção, o que pode melhorar significativamente o desempenho.

    Para que o BKA seja usado, o sinalizador batched_key_access da variável de sistema otimizador_switch deve estar ativado. O BKA usa MRR, portanto, o sinalizador mrr também deve estar ativado. Atualmente, a estimativa de custo para MRR é muito pessimista. Portanto, também é necessário que mrr_cost_based esteja desativado para que o BKA seja usado.

    Esta mesma página recomenda fazer isso:

    mysql> SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
    

    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.

    [mysqld]
    innodb_write_io_threads = 16
    

    Você terá que reiniciar o MySQL para esta mudança

    DE UMA CHANCE !!!

    • 20
  2. Best Answer
    Craig Efrein
    2015-10-31T04:45:26+08:002015-10-31T04:45:26+08:00

    Com base na minha experiência, eu usaria LOAD DATA INFILE para importar seu arquivo CSV.

    A instrução LOAD DATA INFILE lê linhas de um arquivo de texto em uma tabela em uma velocidade muito alta.

    Exemplo que encontrei na internet Exemplo de Load Data . Eu testei este exemplo na minha caixa e funcionou bem

    Tabela de exemplo

    CREATE TABLE example (
      `Id` int(11) NOT NULL AUTO_INCREMENT,
      `Column2` varchar(14) NOT NULL,
      `Column3` varchar(14) NOT NULL,
      `Column4` varchar(14) NOT NULL,
      `Column5` DATE NOT NULL,
      PRIMARY KEY (`Id`)
    ) ENGINE=InnoDB
    

    Exemplo de arquivo CSV

    # more /tmp/example.csv
    Column1,Column2,Column3,Column4,Column5
    1,A,Foo,sdsdsd,4/13/2013
    2,B,Bar,sdsa,4/12/2013
    3,C,Foo,wewqe,3/12/2013
    4,D,Bar,asdsad,2/1/2013
    5,E,FOObar,wewqe,5/1/2013
    

    Instrução de importação a ser executada no console MySQL

    LOAD DATA LOCAL INFILE '/tmp/example.csv'
        -> INTO TABLE example
        -> FIELDS TERMINATED BY ','
        -> LINES TERMINATED BY '\n'
        -> IGNORE 1 LINES
        -> (id, Column3,Column4, @Column5)
        -> set
        -> Column5 = str_to_date(@Column5, '%m/%d/%Y');
    

    Resultado

    MySQL [testcsv]> select * from example;
    +----+---------+---------+---------+------------+
    | Id | Column2 | Column3 | Column4 | Column5    |
    +----+---------+---------+---------+------------+
    |  1 |         | Column2 | Column3 | 0000-00-00 |
    |  2 |         | B       | Bar     | 0000-00-00 |
    |  3 |         | C       | Foo     | 0000-00-00 |
    |  4 |         | D       | Bar     | 0000-00-00 |
    |  5 |         | E       | FOObar  | 0000-00-00 |
    +----+---------+---------+---------+------------+
    

    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.

    • 18
  3. Rick James
    2015-12-06T23:49:30+08:002015-12-06T23:49:30+08:00
    1. CREATE TABLEque corresponde ao CSV
    2. LOAD DATAnessa mesa
    3. UPDATE real_table JOIN csv_table ON ... SET ..., ..., ...;
    4. 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...

    1. CREATE TABLEque corresponde ao CSV; nenhum índice, exceto o que é necessário no JOINarquivo UPDATE. Se for único, faça-o PRIMARY KEY.
    2. LOAD DATAnessa mesa
    3. copie o real_tablepara new_table( CREATE ... SELECT)
    4. UPDATE new_table JOIN csv_table ON ... SET ..., ..., ...;
    5. RENAME TABLE real_table TO old, new_table TO real_table;
    6. 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".

    • 4
  4. Peter Dixon-Moses
    2015-12-29T13:07:13+08:002015-12-29T13:07:13+08:00

    Você disse:

    • As atualizações afetam 6-25% da sua mesa
    • Você quer fazer isso o mais rápido possível (<1hr)
    • sem trava
    • não precisa ser em uma única transação
    • ainda (no comentário sobre a resposta de Rick James), você expressa preocupação com as condições da corrida

    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:

    1. Divida seu arquivo de lote em alguns pedaços de tamanho razoável (digamos 50.000 linhas/arquivo)
    2. Em paralelo, faça com que um script leia cada arquivo e produza um arquivo com 50.000 instruções UPDATE.
    3. Em paralelo, assim que (2) terminar, mysqlexecute cada arquivo sql.

    (por exemplo bash, veja splite veja xargs -Pmaneiras de executar facilmente um comando de várias maneiras paralelas. O grau de paralelismo depende de quantos threads você deseja dedicar à atualização )

    • 3
  5. Rick James
    2015-12-31T08:03:21+08:002015-12-31T08:03:21+08:00

    Para o UPDATEcorrer rápido, você precisa

    INDEX(uploaders, downloaders, verified)
    

    Pode estar em qualquer mesa. Os três campos podem estar em qualquer ordem.

    Isso facilitará a UPDATEpossibilidade de combinar rapidamente as linhas entre as duas tabelas.

    And make the datatypes the same in the two tables (both INT SIGNED or both INT UNSIGNED).

    • 0
  6. user3127882
    2019-01-23T21:41:19+08:002019-01-23T21:41:19+08:00

    Large updates are I/O bound. I would suggest:

    1. Create a distinct table that will store your 3 frequently updated fields. Let's call one table assets_static where you keep, well, static data, and the other assets_dynamic that will store uploaders, downloaders and verified.
    2. If you can, use the MEMORY engine for the assets_dynamic table. (backup to disk after each update).
    3. Update your lightweight and nimble assets_dynamic as per your update 4 (i.e. LOAD INFILE ... INTO temp; UPDATE assets_dynamic a JOIN temp b on a.id=b.id SET [what has to be updated]. This should take less than a minute. (On our system, assets_dynamic has 95M rows and updates impact ~ 6M rows, in a little more than 40s.)
    4. When you run Sphinx's indexer, JOIN assets_static and assets_dynamic (assuming that you want to use one of these fields as an attribute).
    • 0

relate perguntas

  • Onde posso encontrar o log lento do mysql?

  • Como posso otimizar um mysqldump de um banco de dados grande?

  • Quando é o momento certo para usar o MariaDB em vez do MySQL e por quê?

  • Como um grupo pode rastrear alterações no esquema do banco de dados?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host

    • 12 respostas
  • Marko Smith

    Como fazer a saída do sqlplus aparecer em uma linha?

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

    Como faço para listar todos os esquemas no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Jin conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane Como faço para listar todos os esquemas no PostgreSQL? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve