O banco de dados MySQL InnoDB 'trava' nas seleções

10

Estou tentando corrigir a configuração do MySQL em nosso servidor. As especificidades do nosso aplicativo são que muitos dados são armazenados em uma única tabela (atualmente com mais de 300 milhões de linhas). Essa tabela é usada frequentemente para inserções (elas vêm o tempo todo).

Quando executo uma consulta de seleção nessa tabela que leva mais de alguns segundos, todas as inserções (confirmadas com precisão) aguardam o acesso à tabela e deixam nosso aplicativo sem resposta.

Tanto quanto eu sei, o InnoDB não faz nenhum bloqueio na tabela quando o select está em execução. Por que a tabela de bloqueio de seleção é então?

Tentei encontrar um motivo com o innotop, mas não sei como interpretar sua saída e onde procurar. Diga-me o que você precisa e eu publicarei aqui.

+-----+---------+-----------+--------+---------+------+----------------+-----------------------------------------------------------------------------------------------------------------------------------+
| Id  | User    | Host      | db     | Command | Time | State          | Info                                                                                                                              |
+-----+---------+-----------+--------+---------+------+----------------+-----------------------------------------------------------------------------------------------------------------------------------+
|   1 | root    | localhost | dbname | Query   |   29 | NULL           | COMMIT                                                                                                                            | 
|   2 | root    | localhost | dbname | Query   |   30 | NULL           | COMMIT                                                                                                                            | 
|   4 | root    | localhost | dbname | Query   |   29 | NULL           | COMMIT                                                                                                                            | 
|   5 | root    | localhost | dbname | Query   |   29 | NULL           | COMMIT                                                                                                                            | 
|   6 | root    | localhost | dbname | Query   |   25 | NULL           | COMMIT                                                                                                                            | 
|   7 | root    | localhost | dbname | Query   |    0 | NULL           | show full processlist                                                                                                             | 
|  13 | user    | localhost | dbname | Query   |   25 | NULL           | COMMIT                                                                                                                            | 
|  38 | user    | localhost | dbname | Sleep   |    0 |                | NULL                                                                                                                              | 
|  39 | user    | localhost | dbname | Sleep   | 9017 |                | NULL                                                                                                                              | 
|  40 | user    | localhost | dbname | Query   |   33 | Sorting result | SELECT * FROM `large_table` WHERE (`large_table`.`hotspot_id` = 3000064)  ORDER BY discovered_at LIMIT 799000, 1000 | 
|  60 | user    | localhost | dbname | Sleep   | 1033 |                | NULL                                                                                                                              | 
|  83 | root    | localhost | dbname | Sleep   | 3728 |                | NULL                                                                                                                              | 
| 112 | root    | localhost | NULL   | Sleep   |    6 |                | NULL                                                                                                                              | 
+-----+---------+-----------+--------+---------+------+----------------+-----------------------------------------------------------------------------------------------------------------------------------+


=====================================
110824 12:24:24 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 19 seconds
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 1521117, signal count 1471216
Mutex spin waits 0, rounds 20647617, OS waits 239914
RW-shared spins 2119697, OS waits 1037149; RW-excl spins 505734, OS waits 218177
------------
TRANSACTIONS
------------
Trx id counter 0 412917332
Purge done for trx's n:o < 0 412917135 undo n:o < 0 0
History list length 48
Total number of lock structs in row lock hash table 5
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 0 0, not started, process no 28363, OS thread id 1092766032
MySQL thread id 83, query id 3249941 localhost root
---TRANSACTION 0 412901582, not started, process no 28363, OS thread id 1144449360
MySQL thread id 60, query id 3677008 localhost user
---TRANSACTION 0 412917189, not started, process no 28363, OS thread id 1144314192
MySQL thread id 43, query id 3905773 localhost root
---TRANSACTION 0 412534255, not started, process no 28363, OS thread id 1092630864
MySQL thread id 39, query id 14279 localhost user
---TRANSACTION 0 412917331, not started, process no 28363, OS thread id 1144179024
MySQL thread id 38, query id 3908045 localhost user
---TRANSACTION 0 412917201, not started, process no 28363, OS thread id 1092495696
MySQL thread id 13, query id 3908257 localhost user
---TRANSACTION 0 412538821, not started, process no 28363, OS thread id 1092360528
MySQL thread id 7, query id 3908258 localhost root
show engine innodb status
---TRANSACTION 0 412917330, ACTIVE 6 sec, process no 28363, OS thread id 1144043856
2 lock struct(s), heap size 368, undo log entries 1
MySQL thread id 2, query id 3907373 localhost root
COMMIT
Trx read view will not see trx with id >= 0 412917331, sees < 0 412917131
---TRANSACTION 0 412917328, ACTIVE 6 sec, process no 28363, OS thread id 1092225360
2 lock struct(s), heap size 368, undo log entries 1
MySQL thread id 6, query id 3907345 localhost root
COMMIT
Trx read view will not see trx with id >= 0 412917329, sees < 0 412917131
---TRANSACTION 0 412917326, ACTIVE 6 sec, process no 28363, OS thread id 1091955024
2 lock struct(s), heap size 368, undo log entries 1
MySQL thread id 4, query id 3907335 localhost root
COMMIT
Trx read view will not see trx with id >= 0 412917327, sees < 0 412917131
---TRANSACTION 0 412917324, ACTIVE 6 sec, process no 28363, OS thread id 1092090192
2 lock struct(s), heap size 368, undo log entries 1
MySQL thread id 5, query id 3907328 localhost root
COMMIT
Trx read view will not see trx with id >= 0 412917325, sees < 0 412917131
---TRANSACTION 0 412917321, ACTIVE (PREPARED) 7 sec, process no 28363, OS thread id 1143908688 preparing
2 lock struct(s), heap size 368, undo log entries 1
MySQL thread id 1, query id 3907125 localhost root
COMMIT
Trx read view will not see trx with id >= 0 412917322, sees < 0 412917131
---TRANSACTION 0 412917131, ACTIVE 20 sec, process no 28363, OS thread id 1074075984, thread declared inside InnoDB 111
mysql tables in use 1, locked 0
MySQL thread id 40, query id 3904958 localhost user Sorting result
SELECT * FROM `large_table` WHERE (`large_table`.`hotspot_id` = 3000064)  ORDER BY discovered_at LIMIT 848000, 1000
Trx read view will not see trx with id >= 0 412917132, sees < 0 412917132
--------
FILE I/O
--------
I/O thread 0 state: waiting for i/o request (insert buffer thread)
I/O thread 1 state: waiting for i/o request (log thread)
I/O thread 2 state: waiting for i/o request (read thread)
I/O thread 3 state: waiting for i/o request (write thread)
Pending normal aio reads: 0, aio writes: 0,
 ibuf aio reads: 0, log i/o's: 0, sync i/o's: 0
Pending flushes (fsync) log: 1; buffer pool: 0
3510225 OS file reads, 284998 OS file writes, 202897 OS fsyncs
1.05 reads/s, 21299 avg bytes/read, 8.10 writes/s, 7.58 fsyncs/s
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 275, free list len 13392, seg size 13668,
489950 inserts, 491830 merged recs, 10986 merges
Hash table size 8850487, used cells 8127172, node heap has 32697 buffer(s)
71914.53 hash searches/s, 8701.91 non-hash searches/s
---
LOG
---
Log sequence number 157 3331524445
Log flushed up to   157 3331521939
Last checkpoint at  157 3326072846
1 pending log writes, 0 pending chkp writes
199025 log i/o's done, 7.53 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 4788954432; in additional pool allocated 1048576
Buffer pool size   262144
Free buffers       0
Database pages     229447
Modified db pages  1439
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages read 7453325, created 14887, written 118658
1.37 reads/s, 0.11 creates/s, 0.53 writes/s
Buffer pool hit rate 1000 / 1000
--------------
ROW OPERATIONS
--------------
1 queries inside InnoDB, 0 queries in queue
7 read views open inside InnoDB
Main thread process no. 28363, id 1091684688, state: flushing log
Number of rows inserted 1093064, updated 249134, deleted 1405, read 1115880534
7.89 inserts/s, 2.47 updates/s, 0.05 deletes/s, 80953.21 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================

EDITAR:

Obrigado por esclarecer isso.

Então, tenho que dividir minha pergunta em dois casos agora.

  1. É normal que o bloqueio nessa tabela única faça com que meu aplicativo inteiro seja interrompido. O DB não deve responder às consultas de outras tabelas? Talvez algum buffer esteja definido muito baixo?

  2. Mudar esta tabela para o MyISAM ajuda? Não preciso de transações nesta tabela. Não haverá outros bloqueios nessa situação (seleção longa + muitas inserções rápidas)?

EDIT2:

É assim que as consultas de inserção se parecem:

INSERT INTO `large_table` (`device_address`, `hotspot_id`, `minute`, `created_at`, `updated_at`, `discovered_with_hci`, `hour`, `rssi`, `day`, `device_class`, `discovered_at`) VALUES('10:40:03:90:10:40', 3000008, 1, '2011-08-22 05:01:08', '2011-08-22 05:01:08', -1, 5, -79, '2011-08-22 05:01:01', '0', '2011-08-22 05:01:01')

É isso que os índices são definidos:

+-------------+------------+----------------------------------------------+--------------+---------------------+-----------+-------------+----------+--------+------+------------+---------+
| Table       | Non_unique | Key_name                                     | Seq_in_index | Column_name         | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-------------+------------+----------------------------------------------+--------------+---------------------+-----------+-------------+----------+--------+------+------------+---------+
| large_table |          0 | PRIMARY                                      |            1 | id                  | A         |    92396334 |     NULL | NULL   |      | BTREE      |         | 
| large_table |          1 | index_large_table_on_discovered_with_hci     |            1 | discovered_with_hci | A         |          18 |     NULL | NULL   | YES  | BTREE      |         | 
| large_table |          1 | index_large_table_on_hotspot_id              |            1 | hotspot_id          | A         |          18 |     NULL | NULL   | YES  | BTREE      |         | 
| large_table |          1 | index_large_table_on_day_and_hour_and_minute |            1 | day                 | A         |          18 |     NULL | NULL   | YES  | BTREE      |         | 
| large_table |          1 | index_large_table_on_day_and_hour_and_minute |            2 | hour                | A         |          18 |     NULL | NULL   | YES  | BTREE      |         | 
| large_table |          1 | index_large_table_on_day_and_hour_and_minute |            3 | minute              | A         |      537187 |     NULL | NULL   | YES  | BTREE      |         | 
| large_table |          1 | index_large_table_on_created_at              |            1 | created_at          | A         |     8399666 |     NULL | NULL   | YES  | BTREE      |         | 
| large_table |          1 | index_large_table_on_rssi                    |            1 | rssi                | A         |          18 |     NULL | NULL   | YES  | BTREE      |         | 
+-------------+------------+----------------------------------------------+--------------+---------------------+-----------+-------------+----------+--------+------+------------+---------+

EDIT 3:

Por que durante todas essas consultas meu aplicativo não está respondendo? Não deveria afetar apenas essa 'tabela grande'?

Talvez algo esteja errado com minha configuração do mysql? O servidor é um Xeon 2GHz de 4 núcleos com 16GB de RAM. Ele roda o MySQL + Rails App

Meus parâmetros de configuração:

skip-external-locking
key_buffer              = 64M
max_allowed_packet      = 16M
thread_stack            = 128K
thread_cache_size       = 8
query_cache_size        = 32M
tmp_table_size          = 64M
max_heap_table_size     = 64M
table_cache             = 256
read_rnd_buffer_size    = 512K
sort_buffer_size        = 2M

myisam-recover          = BACKUP
max_connections         = 200

query_cache_limit       = 1M

long_query_time = 200

max_binlog_size         = 100M

innodb_buffer_pool_size = 4G
safe-updates
max_join_size=100000000

O script Mysqltuner sugere apenas:

long_query_time (<= 10)
innodb_buffer_pool_size (>= 62G)
kaczor1984
fonte
Anexe a saída de show engine innodb status;.
Quanta
No innotop, você pode pressionar L para obter uma visão dos bloqueios. Como seu aplicativo estabelece uma conexão? JDBC? Qual nível defaultTransactionIsolation você está usando?
HTTP500
É uma aplicação RoR, então eu acho que é o padrão para o MySQL (posso verificar de alguma forma pela consulta SQL?). O innotop não mostra bloqueios enquanto essa seleção é executada.
precisa saber é o seguinte
@ kaczor1984 Você pode verificar o nível de isolamento padrão fazendo um: show variáveis ​​como 'tx_isolation'; inquerir. O padrão é REPEATABLE READ. Observe que o MVCC funciona apenas com REPEATABLE READ e READ COMMITTED. Não tenho certeza qual é a solução para o seu problema, mas a resposta do RolandoMySQLDBAs foi informativa.
HTTP500
Atualizei minha resposta com uma análise da consulta no Processo ID 40
RolandoMySQLDBA

Respostas:

15

Por favor, observe atentamente a lista de processos e o 'show engine innodb status'. O que você vê ???

Os IDs de processo 1,2,4,5,6,13 estão todos tentando executar o COMMIT.

Quem está segurando tudo ??? O ID do processo 40 está executando uma consulta no large_table.

O ID do processo 40 está em execução há 33 segundos. Os IDs de processo 1,2,4,5,6,13 estão em execução há menos de 33 segundos. A identificação de processo 40 está processando alguma coisa. Por que a demora ???

Primeiro de tudo, a consulta está aumentando no índice clusterizado de large_table via MVCC .

Nas IDs de processo 1,2,4,5,6,13, existem linhas que possuem dados MVCC protegendo seu isolamento de transação. O ID do processo 40 possui uma consulta que está marchando pelas linhas de dados. Se houver um índice no campo hotspot_id, essa chave + a chave da linha real do índice em cluster deve executar um bloqueio interno. (Nota: por design, todos os índices não exclusivos no InnoDB carregam tanto sua chave (a coluna que você pretendia indexar) + uma chave de índice em cluster). Esse cenário único é essencialmente Força imparável e Objeto imóvel.

Em essência, os COMMITs devem aguardar até que seja seguro aplicar as alterações em large_table. Sua situação não é única, não é um fenômeno pontual, não é raro.

Na verdade, eu respondi três perguntas como essa no DBA StackExchange. As perguntas foram submetidas pela mesma pessoa, relacionadas ao mesmo problema. Minhas respostas não foram a solução, mas ajudaram o remetente da pergunta a chegar a sua própria conclusão sobre como lidar com sua situação.

Além dessas respostas, respondi à pergunta de outra pessoa sobre conflitos no InnoDB em relação aos SELECTs .

Espero que minhas postagens anteriores sobre esse assunto ajudem a esclarecer o que estava acontecendo com você.

UPDATE 2011-08-25 08:10 EDT

Aqui está a consulta do processo ID 40

SELECT * FROM `large_table`
WHERE (`large_table`.`hotspot_id` = 3000064)
ORDER BY discovered_at LIMIT 799000, 1000;

Duas observações:

  • Você está fazendo 'SELECT *', precisa buscar todas as colunas? Se você precisar apenas de colunas específicas, identifique-as, porque a tabela temporária de 1000 linhas pode ser maior do que você realmente precisa.

  • As cláusulas WHERE e ORDER BY geralmente revelam problemas de desempenho ou fazem o design da mesa brilhar. Você precisa criar um mecanismo que acelere a coleta de chaves antes de coletar dados.

À luz dessas duas observações, há duas grandes mudanças que você deve fazer:

ALTERAÇÃO PRINCIPAL 1: Refatorar a consulta

Redesenhe a consulta para que

  1. chaves são coletadas do índice
  2. apenas 1000 ou eles são coletados
  3. juntou-se de volta à mesa principal

Aqui está a nova consulta que faz essas três coisas

SELECT large_table.* FROM
large_table INNER JOIN
(
    SELECT hotspot_id,discovered_at
    FROM large_table
    WHERE hotspot_id = 3000064
    ORDER BY discovered_at
    LIMIT 799000,1000
) large_table_keys
USING (hotspot_id,discovered_at);

A subconsulta large_table_keys reúne as 1000 chaves necessárias. O resultado da subconsulta é INNER JOINed para large_table. Até agora, as chaves são recuperadas em vez de linhas inteiras. Ainda são 799.000 linhas para ler. Existe uma maneira melhor de obter essas chaves, o que nos leva a ...

ALTERAÇÃO PRINCIPAL 2: Criar índices que suportam a consulta refatorada

Como a consulta refatorada apresenta apenas uma subconsulta, você só precisa criar um índice. Aqui está esse índice:

ALTER TABLE large_table ADD INDEX hotspot_discovered_ndx (hotspot_id,discovered_at);

Por que esse índice em particular? Veja a cláusula WHERE. O hotspot_id é um valor estático. Isso faz com que todos os hotspot_ids formem uma lista seqüencial no índice. Agora, veja a cláusula ORDER BY. A coluna descoberto_at é provavelmente um campo DATETIME ou TIMESTAMP.

A ordem natural que isso apresenta no índice é a seguinte:

  • O índice apresenta uma lista de hostpot_ids
  • Cada hotspot_id possui uma lista ordenada dos campos discover_at

A criação desse índice também elimina a classificação interna de tabelas temporárias.

Por favor, coloque essas duas grandes mudanças no lugar e você verá uma diferença no tempo de execução.

De uma chance !!!

UPDATE 2011-08-25 08:15 EDT

Eu olhei para seus índices. Você ainda precisa criar o índice que sugeri.

RolandoMySQLDBA
fonte
Obrigado por uma enorme explicação de como funciona. Receio não conseguir descobrir como evitar situações como essa. As inserções precisam modificar os índices e selecionar deve usar o índice em hotspot_id e discover_at. Ficaria feliz se você também pudesse responder à minha 'ideia' de mudar para o MyISAM.
precisa saber é o seguinte
Na verdade, o uso do MyISAM pode piorar as coisas porque cada INSERT, UPDATE e DELETE no MyISAM aciona um bloqueio de tabela completo. Mesmo se você usar LOW_PRIORITY INSERTs ou INSERT DELAYED, bloqueios de tabela completos ainda serão encontrados. Você deve explorar a consulta em si, pois, independentemente do mecanismo de armazenamento, as consultas podem ser ajustadas em torno desses obstáculos. No mínimo, um novo algoritmo pode ser necessário. Eu vou olhar para a consulta em um par de minutos ...
RolandoMySQLDBA
Atualizei minha primeira postagem para que você possa inserir consultas e índices nesta tabela.
precisa saber é o seguinte
Atualizei minha resposta com uma análise da consulta no Processo ID 40
RolandoMySQLDBA
4
você senhor é um herói entre os homens para as suas respostas mysql longos
Mike
3

Resolvido!

O principal problema foi o query_cache. http://bugs.mysql.com/bug.php?id=21074

Depois de desativá-lo, os 'congelamentos' desapareceram.

kaczor1984
fonte
E também muito obrigado a @RolandoMySQLDBA pelas dicas sobre como otimizar minhas consultas e índices.
precisa saber é o seguinte