База данных MySQL InnoDB "зависает" при выборе

10

Я пытаюсь исправить конфигурацию MySQL на нашем сервере. Специфика нашего приложения заключается в том, что большое количество данных хранится в одной таблице (в настоящее время более 300 миллионов строк). Эта таблица часто используется для вставок (они приходят постоянно).

Когда я запускаю запрос на выборку для этой таблицы, который занимает больше нескольких секунд, тогда все вставки (точные коммиты) ожидают доступа к таблице и делают наше приложение не отвечающим.

Насколько я знаю, InnoDB не делает никаких блокировок таблицы, когда работает select. Почему тогда выбирается таблица блокировки?

Я пытался найти причину с innotop, но я не уверен, как интерпретировать его вывод и где искать. Скажите мне, что вам нужно, и я опубликую это здесь.

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

РЕДАКТИРОВАТЬ:

Спасибо за разъяснение этого.

Поэтому я должен разделить свой вопрос на два случая сейчас.

  1. Это нормально, что блокировка этой единственной таблицы вызывает зависание всего моего приложения. Разве БД не должна реагировать на запросы к другим таблицам? Может быть, какой-то буфер установлен слишком низко?

  2. Поможет ли переключение этой таблицы на MyISAM? Мне не нужны транзакции на этой таблице вообще. Не будет ли в такой ситуации других блокировок (длинный выбор + много быстрых вставок)?

EDIT2:

Вот так выглядят запросы на вставку:

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

Вот для чего определены индексы:

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

РЕДАКТИРОВАТЬ 3:

Почему при таких запросах целое мое приложение не отвечает? Разве это не должно влиять только на эту 'large_table'?

Может быть, что-то не так с моим конфигом mysql? Сервер представляет собой 4-ядерный Xeon 2 ГГц с 16 ГБ оперативной памяти. Работает приложение MySQL + Rails

Мои параметры конфигурации:

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

Скрипт Mysqltuner предлагает только:

long_query_time (<= 10)
innodb_buffer_pool_size (>= 62G)
kaczor1984
источник
Пожалуйста, добавьте вывод show engine innodb status;.
кванты
В innotop вы можете нажать L, чтобы увидеть замки. Как ваше приложение устанавливает соединение? JDBC? Какой уровень defaultTransactionIsolation вы используете?
HTTP500
Это приложение RoR, так что я предполагаю, что это по умолчанию для MySQL (могу ли я как-нибудь проверить его с помощью SQL-запроса?). innotop не показывает никаких блокировок во время выполнения этого выбора.
kaczor1984
@ kaczor1984 Вы можете проверить уровень изоляции по умолчанию, выполнив: show переменные типа 'tx_isolation'; запрос. По умолчанию это ПОВТОРЯЕМЫЕ ЧТЕНИЯ. Обратите внимание, что MVCC работает только с REPEATABLE READ и READ COMMITTED. Не уверен, что решение вашей проблемы, но ответ RolandoMySQLDBAs был информативным.
HTTP500
Обновил мой ответ анализом запроса с идентификатором процесса 40
RolandoMySQLDBA

Ответы:

15

Пожалуйста, внимательно посмотрите на список процессов и «Показать движок innodb status». Что ты видишь ???

Все идентификаторы процессов 1,2,4,5,6,13 пытаются запустить COMMIT.

Кто все держит? Идентификатор процесса 40 выполняет запрос к large_table.

Идентификатор процесса 40 был запущен в течение 33 секунд. Идентификаторы процесса 1,2,4,5,6,13 были запущены менее 33 секунд. Идентификатор процесса 40 обрабатывает что-то. Что за задержка ???

Прежде всего, запрос стучит по кластерному индексу large_table через MVCC .

Внутри идентификаторов процессов 1,2,4,5,6,13 - строки, в которых есть данные MVCC, защищающие изоляцию транзакций. Идентификатор процесса 40 имеет запрос, который проходит по строкам данных. Если в поле hotspot_id есть индекс, этот ключ + ключ к фактической строке из кластерного индекса должен выполнять внутреннюю блокировку. (Примечание. По замыслу все неуникальные индексы в InnoDB содержат как ваш ключ (столбец, который вы хотели индексировать), так и ключ кластеризованного индекса). Этот уникальный сценарий по сути Unstoppable Force встречает Неподвижный объект.

По сути, COMMIT должны ждать, пока безопасно применить изменения к large_table. Ваша ситуация не уникальна, не разовая и не редкое явление.

На самом деле я ответил на три таких вопроса в DBA StackExchange. Вопросы были заданы одним и тем же лицом, связанным с одной и той же проблемой. Мои ответы не были решением, но помогли автору вопроса прийти к собственному выводу о том, как справиться с ситуацией.

В дополнение к этим ответам я ответил на вопрос другого человека о взаимоблокировках в InnoDB в отношении SELECT .

Я надеюсь, что мои прошлые сообщения на эту тему помогут прояснить, что с вами происходит.

ОБНОВЛЕНИЕ 2011-08-25 08:10 ПО ВОСТОЧНОМУ ВРЕМЕНИ

Вот запрос из идентификатора процесса 40

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

Два наблюдения:

  • Вы делаете 'SELECT *', вам нужно выбрать каждый столбец? Если вам нужны только определенные столбцы, вы должны пометить их, потому что временная таблица из 1000 строк может быть больше, чем вам действительно нужно.

  • Предложения WHERE и ORDER BY обычно устраняют проблемы с производительностью или делают дизайн таблицы ярким. Вам нужно создать механизм, который ускорит сбор ключей перед сбором данных.

В свете этих двух наблюдений необходимо внести два основных изменения:

ОСНОВНОЕ ИЗМЕНЕНИЕ № 1: Рефакторинг запроса

Перепроектируйте запрос так, чтобы

  1. ключи собраны из индекса
  2. только 1000 или их собирают
  3. присоединился к основному столу

Вот новый запрос, который делает эти три вещи

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

Подзапрос large_table_keys собирает 1000 ключей, которые вам нужны. Результат из подзапроса затем INNER СОЕДИНЯЕТСЯ к large_table. Пока что ключи извлекаются вместо целых строк. Это еще 799 000 строк для чтения. Есть лучший способ получить эти ключи, что приводит нас к ...

ОСНОВНОЕ ИЗМЕНЕНИЕ № 2: Создание индексов, поддерживающих рефакторированный запрос

Поскольку реорганизованный запрос содержит только один подзапрос, вам нужно создать только один индекс. Вот этот индекс:

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

Почему именно этот индекс? Посмотрите на предложение WHERE. Hotspot_id является статическим значением. Это заставляет все hotspot_ids формировать последовательный список в индексе. Теперь посмотрим на предложение ORDER BY. Обнаруженный столбец - это, вероятно, поле DATETIME или TIMESTAMP.

Естественный порядок, представленный в индексе, следующий:

  • Индекс содержит список hostpot_ids
  • У каждого hotspot_id есть упорядоченный список полей found_at

Создание этого индекса также исключает внутреннюю сортировку временных таблиц.

Пожалуйста, внесите эти два основных изменения, и вы увидите разницу во времени выполнения.

Попробуйте!

ОБНОВЛЕНИЕ 2011-08-25 08:15 ПО ВОСТОЧНОМУ ВРЕМЕНИ

Я посмотрел на ваши индексы. Вам все еще нужно создать индекс, который я предложил.

RolandoMySQLDBA
источник
Спасибо за огромное объяснение, как это работает. Боюсь, я не могу понять, как избежать подобных ситуаций. Вставки должны изменять индексы, а для выбора необходимо использовать индексы для hotspot_id и found_at. Я был бы рад, если бы вы также могли ответить на мою «идею» перехода на MyISAM.
kaczor1984
На самом деле, использование MyISAM может ухудшить ситуацию, потому что каждый INSERT, UPDATE и DELETE в MyISAM запускает полную блокировку таблицы. Даже если вы используете LOW_PRIORITY INSERT или INSERT DELAYED, полные блокировки таблицы все равно будут встречаться. Вы должны изучить сам запрос, потому что независимо от механизма хранения, запросы могут быть настроены вокруг таких препятствий. По крайней мере, новый алгоритм вообще может потребоваться. Я посмотрю на запрос через пару минут ...
RolandoMySQLDBA
Я обновил свой первый пост, чтобы вы могли видеть запросы вставки и индексы в этой таблице.
kaczor1984
Обновил мой ответ анализом запроса с идентификатором процесса 40
RolandoMySQLDBA
4
Вы, сэр, герой среди мужчин за ваши длинные ответы MySQL
Майк
3

Решено!

Основной проблемой был query_cache. http://bugs.mysql.com/bug.php?id=21074

После отключения «зависания» исчезли.

kaczor1984
источник
А также большое спасибо @RolandoMySQLDBA за подсказки по оптимизации моих запросов и индексов.
kaczor1984