La base de données MySQL InnoDB "se bloque" sur certains

10

J'essaie de corriger la configuration MySQL sur notre serveur. Les particularités de notre application sont que beaucoup de données sont stockées dans une seule table (actuellement plus de 300 millions de lignes). Ce tableau est souvent utilisé pour les encarts (ils viennent tout le temps).

Lorsque j'exécute une requête de sélection sur cette table qui prend plus de quelques secondes, toutes les insertions (validées précisément) attendent l'accès à la table et rendent notre application sans réponse.

Autant que je sache, InnoDB ne fait aucun verrou sur la table lorsque select est en cours d'exécution. Pourquoi la table de blocage de sélection est-elle alors?

J'ai essayé de trouver une raison avec innotop mais je ne sais pas comment interpréter sa sortie et où chercher. Dites-moi ce dont vous avez besoin et je le posterai ici.

+-----+---------+-----------+--------+---------+------+----------------+-----------------------------------------------------------------------------------------------------------------------------------+
| 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
============================

ÉDITER:

Merci d'avoir clarifié cela.

Je dois donc diviser ma question en deux cas maintenant.

  1. Est-il normal que le verrouillage de cette table unique entraîne le blocage de toute mon application. DB ne devrait-il pas répondre aux requêtes vers d'autres tables? Peut-être qu'un tampon est réglé trop bas?

  2. Est-ce que le basculement de ce tableau vers MyISAM sera utile? Je n'ai absolument pas besoin de transactions sur cette table. N'y aura-t-il pas d'autres verrous dans une telle situation (sélection longue + plusieurs insertions rapides)?

EDIT2:

Voilà à quoi ressemblent les requêtes d'insertion:

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')

C'est ce que les index y sont définis:

+-------------+------------+----------------------------------------------+--------------+---------------------+-----------+-------------+----------+--------+------+------------+---------+
| 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:

Pourquoi pendant de telles requêtes, mon application ne répond pas? Cela ne devrait-il pas affecter uniquement cette 'large_table'?

Peut-être que quelque chose ne va pas avec ma configuration mysql? Le serveur est un Xeon 2 GHz à 4 cœurs avec 16 Go de RAM. Il exécute l'application MySQL + Rails

Mes paramètres de configuration:

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

Le script Mysqltuner suggère uniquement:

long_query_time (<= 10)
innodb_buffer_pool_size (>= 62G)
kaczor1984
la source
Veuillez ajouter la sortie de show engine innodb status;.
quanta
Dans innotop, vous pouvez appuyer sur L pour avoir une vue sur les serrures. Comment votre application établit-elle une connexion? JDBC? Quel niveau defaultTransactionIsolation utilisez-vous?
HTTP500
C'est une application RoR donc je suppose que c'est par défaut pour MySQL (puis-je le vérifier d'une manière ou d'une autre par requête SQL?). innotop n'affiche aucun verrou pendant que cette sélection s'exécute.
kaczor1984
@ kaczor1984 Vous pouvez vérifier le niveau d'isolement par défaut en faisant a: afficher des variables comme 'tx_isolation'; requete. La valeur par défaut est REPEATABLE READ. Notez que MVCC fonctionne uniquement avec REPEATABLE READ et READ COMMITTED. Je ne sais pas quelle est la solution à votre problème, mais la réponse de RolandoMySQLDBA était informative.
HTTP500
Mise à jour de ma réponse avec une analyse de la requête au Process ID 40
RolandoMySQLDBA

Réponses:

15

Veuillez lire attentivement la liste des processus et le «statut innodb du moteur d'affichage». Que vois-tu ???

Les ID de processus 1, 2, 4, 5, 6, 13 tentent tous d'exécuter COMMIT.

Qui tient tout ??? L'ID de processus 40 exécute une requête sur large_table.

L'ID de processus 40 est en cours d'exécution depuis 33 secondes. ID de processus 1, 2, 4, 5, 6, 13 ayant exécuté moins de 33 secondes. L'ID de processus 40 traite quelque chose. Quel est le blocage ???

Tout d'abord, la requête bat sur l' index cluster de large_table via MVCC .

Dans les identifiants de processus 1, 2, 4, 5, 6, 13 contiennent des lignes dont les données MVCC protègent leur isolation des transactions. L'ID de processus 40 contient une requête qui parcourt des lignes de données. S'il y a un index sur le champ hotspot_id, cette clé + la clé de la ligne réelle de l'index cluster doit effectuer un verrouillage interne. (Remarque: De par leur conception, tous les index non uniques dans InnoDB portent à la fois votre clé (la colonne que vous vouliez indexer) + une clé d'index cluster). Ce scénario unique est essentiellement la force imparable rencontre l'objet immobile.

En substance, les COMMIT doivent attendre jusqu'à ce qu'il soit sûr d'appliquer des modifications à large_table. Votre situation n'est pas unique, ni unique, ni rare.

J'ai en fait répondu à trois questions comme celle-ci dans le DBA StackExchange. Les questions ont été soumises par la même personne concernant le même problème. Mes réponses n'étaient pas la solution, mais ont aidé l'auteur de la question à tirer sa propre conclusion sur la façon de gérer sa situation.

En plus de ces réponses, j'ai répondu à la question d'une autre personne sur les blocages dans InnoDB concernant les SELECT .

J'espère que mes précédents articles sur ce sujet aideront à clarifier ce qui vous arrivait.

MISE À JOUR 2011-08-25 08:10 EDT

Voici la requête de l'ID de processus 40

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

Deux observations:

  • Vous faites 'SELECT *' avez-vous besoin de récupérer chaque colonne? Si vous n'avez besoin que de colonnes spécifiques, vous devez les étiqueter car la table temporaire de 1000 lignes peut être plus grande que ce dont vous avez réellement besoin.

  • Les clauses WHERE et ORDER BY révèlent généralement des problèmes de performances ou font briller la conception des tableaux. Vous devez créer un mécanisme qui accélérera la collecte des clés avant de collecter les données.

À la lumière de ces deux observations, vous devez apporter deux changements majeurs:

CHANGEMENT MAJEUR # 1: Refactorisez la requête

Reconcevoir la requête afin que

  1. les clés sont recueillies à partir de l'index
  2. seulement 1000 ou eux sont collectés
  3. rejoint à la table principale

Voici la nouvelle requête qui fait ces trois choses

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);

La sous-requête large_table_keys rassemble les 1000 clés dont vous avez besoin. Le résultat de la sous-requête est alors INNER JOINed à large_table. Jusqu'à présent, les clés sont récupérées au lieu de lignes entières. C'est encore 799 000 lignes à lire. Il y a une meilleure façon d'obtenir ces clés, ce qui nous amène à ...

CHANGEMENT MAJEUR # 2: Créer des index qui prennent en charge la requête refactorisée

Étant donné que la requête refactorisée ne comporte qu'une seule sous-requête, vous devez uniquement créer un index. Voici cet indice:

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

Pourquoi cet indice particulier? Regardez la clause WHERE. Hotspot_id est une valeur statique. Cela fait que tous les hotspot_ids forment une liste séquentielle dans l'index. Maintenant, regardez la clause ORDER BY. La colonne discovery_at est probablement un champ DATETIME ou TIMESTAMP.

L'ordre naturel que cela présente dans l'indice est le suivant:

  • L'index comprend une liste de hostpot_ids
  • Chaque hotspot_id possède une liste ordonnée de champs discovery_at

La création de cet index élimine également le tri interne des tables temporaires.

Veuillez mettre ces deux changements majeurs en place et vous verrez une différence dans le temps d'exécution.

Essaie !!!

MISE À JOUR 2011-08-25 08:15 EDT

J'ai regardé tes index. Vous devez toujours créer l'index que j'ai suggéré.

RolandoMySQLDBA
la source
Merci pour une énorme explication sur le fonctionnement. J'ai bien peur de ne pas savoir comment éviter de telles situations. Les insertions doivent modifier les index et sélectionner doit utiliser l'index sur hotspot_id et discovery_at. Je serais heureux si vous pouviez également répondre à mon «idée» de passer à MyISAM.
kaczor1984
En fait, l'utilisation de MyISAM pourrait aggraver les choses car chaque INSERT, UPDATE et DELETE dans MyISAM déclenche un verrouillage complet de la table. Même si vous utilisez LOW_PRIORITY INSERTs ou INSERT DELAYED, des verrous de table complets seront toujours rencontrés. Vous devez explorer la requête elle-même car, quel que soit le moteur de stockage, les requêtes peuvent être réglées autour de ces obstacles. À tout le moins, un nouvel algorithme peut être nécessaire. Je vais regarder la requête dans quelques minutes ...
RolandoMySQLDBA
J'ai mis à jour mon premier post pour que vous puissiez voir insérer des requêtes et des index sur cette table.
kaczor1984
Mise à jour de ma réponse avec une analyse de la requête au Process ID 40
RolandoMySQLDBA
4
vous monsieur êtes un héros parmi les hommes pour vos longues réponses mysql
Mike
3

Résolu!

Le problème principal était query_cache. http://bugs.mysql.com/bug.php?id=21074

Après l'avoir désactivé, les «gels» ont disparu.

kaczor1984
la source
Et aussi un grand merci à @RolandoMySQLDBA pour des conseils sur l'optimisation de mes requêtes et index.
kaczor1984