Baza danych MySQL InnoDB „zawiesza się” na selekcjach

10

Próbuję naprawić konfigurację MySQL na naszym serwerze. Cechą naszej aplikacji jest to, że wiele danych jest przechowywanych w jednej tabeli (obecnie ponad 300 milionów wierszy). Ta tabela jest często używana do wkładek (przychodzą one cały czas).

Kiedy uruchomię zapytanie typu select na tej tabeli, które trwa dłużej niż kilka sekund, wówczas wszystkie wstawki (dokładnie zatwierdzenia) czekają na dostęp do tabeli i powodują, że nasza aplikacja nie odpowiada.

O ile wiem, InnoDB nie wykonuje żadnych blokad na stole, gdy select jest uruchomiony. Dlaczego więc jest wybrana tabela blokująca?

Próbowałem znaleźć przyczynę za pomocą Innotop, ale nie jestem pewien, jak interpretować dane wyjściowe i gdzie szukać. Powiedz mi, czego potrzebujesz, a ja opublikuję to tutaj.

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

EDYTOWAĆ:

Dzięki za wyjaśnienie tego.

Więc muszę teraz podzielić moje pytanie na dwie sprawy.

  1. Czy to normalne, że blokada tego pojedynczego stołu powoduje zawieszenie się całej mojej aplikacji. Czy DB nie powinien odpowiadać na zapytania do innych tabel? Może jakiś bufor jest zbyt niski?

  2. Czy zmiana tej tabeli na MyISAM pomoże? W ogóle nie potrzebuję transakcji na tym stole. Czy w takiej sytuacji nie będzie innych blokad (długi wybór + wiele szybkich wstawek)?

EDYCJA 2:

Tak wyglądają zapytania wstawiania:

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

Tak definiowane są na nim indeksy:

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

EDYCJA 3:

Dlaczego podczas takich zapytań cała moja aplikacja nie odpowiada? Czy nie powinno to wpływać tylko na tę „dużą tabelę”?

Może coś jest nie tak z moją konfiguracją mysql? Serwer to 4-rdzeniowy Xeon 2GHz z 16 GB pamięci RAM. Działa z aplikacją MySQL + Rails

Moje parametry konfiguracji:

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

Skrypt Mysqltuner sugeruje tylko:

long_query_time (<= 10)
innodb_buffer_pool_size (>= 62G)
kaczor1984
źródło
Dołącz wynik show engine innodb status;.
kwanty
W aplikacji Innotop możesz nacisnąć L, aby uzyskać widok na zamki. Jak twoja aplikacja nawiązuje połączenie? JDBC? Jakiego poziomu defaultTransactionIsolation używasz?
HTTP500
To aplikacja RoR, więc myślę, że jest domyślna dla MySQL (czy mogę to jakoś sprawdzić za pomocą zapytania SQL?). innotop nie pokazuje żadnych blokad podczas działania tego wyboru.
kaczor1984
@ kaczor1984 Możesz sprawdzić domyślny poziom izolacji, wykonując: pokaż zmienne takie jak 'tx_isolation'; pytanie. Domyślnie ODTWARZANE CZYTANIE. Zauważ, że MVCC działa tylko z REPEATABLE READ i READ COMMITTED. Nie jestem pewien, jakie jest rozwiązanie twojego problemu, ale odpowiedź RolandoMySQLDBA była pouczająca.
HTTP500,
Zaktualizowałem moją odpowiedź analizą zapytania o identyfikatorze procesu 40
RolandoMySQLDBA,

Odpowiedzi:

15

Przyjrzyj się uważnie liście procesów i „pokaż status innodb silnika”. Co widzisz ???

Wszystkie identyfikatory procesów 1,2,4,5,6,13 próbują uruchomić COMMIT.

Kto trzyma wszystko ??? Identyfikator procesu 40 uruchamia zapytanie przeciwko dużej tabeli.

Identyfikator procesu 40 działał przez 33 sekundy. Procesy o numerach 1,2,4,5,6,13 działały krócej niż 33 sekundy. Identyfikator procesu 40 przetwarza coś. Co się trzyma ???

Po pierwsze, zapytanie uderza w indeks klastrowy large_table za pośrednictwem MVCC .

W ramach identyfikatorów procesów 1,2,4,5,6,13 znajdują się wiersze z danymi MVCC chroniącymi ich izolację transakcji. Identyfikator procesu 40 ma zapytanie, które przemierza rzędy danych. Jeśli w polu hotspot_id znajduje się indeks, ten klucz + klucz do bieżącego wiersza z indeksu klastrowanego musi wykonać blokadę wewnętrzną. (Uwaga: Z założenia wszystkie nieunikalne indeksy w InnoDB zawierają zarówno twój klucz (kolumna, którą chcesz zaindeksować) + klastrowany klucz indeksu). Ten wyjątkowy scenariusz jest zasadniczo Siłą Nie do powstrzymania, która spotyka Nieruchomy Obiekt.

Zasadniczo, COMMIT-y muszą poczekać, aż będzie można bezpiecznie zastosować zmiany w stosunku do dużej_tabeli. Twoja sytuacja nie jest wyjątkowa, ani jednorazowa, ani rzadka.

Właściwie odpowiedziałem na trzy takie pytania w DBA StackExchange. Pytania zostały przesłane przez tę samą osobę związaną z tym samym problemem. Moje odpowiedzi nie były rozwiązaniem, ale pomogły autorowi pytania dojść do wniosku, jak poradzić sobie z jego sytuacją.

Oprócz tych odpowiedzi odpowiedziałem na pytanie innej osoby o zakleszczenia w InnoDB w odniesieniu do SELECT .

Mam nadzieję, że moje wcześniejsze posty na ten temat pomogą wyjaśnić, co się z tobą działo.

AKTUALIZACJA 25.08.2011 08:10 EDT

Oto zapytanie z ID procesu 40

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

Dwie obserwacje:

  • Robisz „WYBIERZ *” czy potrzebujesz pobrać każdą kolumnę? Jeśli potrzebujesz tylko określonych kolumn, powinieneś je oznaczyć, ponieważ tabela tymczasowa 1000 wierszy może być większa, niż naprawdę potrzebujesz.

  • Klauzule WHERE i ORDER BY zwykle ujawniają problemy z wydajnością lub sprawiają, że projekt stołu świeci. Musisz stworzyć mechanizm, który przyspieszy zbieranie kluczy przed zebraniem danych.

W świetle tych dwóch obserwacji należy wprowadzić dwie główne zmiany:

GŁÓWNA ZMIANA # 1: Refaktoryzacja zapytania

Przeprojektuj zapytanie, aby

  1. klucze są zbierane z indeksu
  2. zbiera się tylko 1000 lub ich
  3. wrócił do głównego stołu

Oto nowe zapytanie, które wykonuje te trzy rzeczy

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

Podzapytanie „duże_tabela_kluczy” gromadzi 1000 potrzebnych kluczy. Wynik z podzapytania jest następnie ŁĄCZONY WEWNĘTRZNIE do dużej tabeli. Do tej pory klucze są pobierane zamiast całych wierszy. To wciąż 799,000 wierszy do przeczytania. Jest lepszy sposób na zdobycie tych kluczy, co prowadzi nas do ...

GŁÓWNA ZMIANA # 2: Utwórz indeksy, które obsługują refaktoryzowane zapytanie

Ponieważ refaktoryzowane zapytanie zawiera tylko jedno podzapytanie, wystarczy utworzyć tylko jeden indeks. Oto ten indeks:

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

Dlaczego ten konkretny indeks? Spójrz na klauzulę WHERE. Hotspot_id jest wartością statyczną. To sprawia, że ​​wszystkie hotspot_ids tworzą sekwencyjną listę w indeksie. Teraz spójrz na klauzulę ORDER BY. Kolumna found_at jest prawdopodobnie polem DATETIME lub TIMESTAMP.

Naturalny porządek występujący w indeksie jest następujący:

  • Indeks zawiera listę hostpot_ids
  • Każdy hotspot_id ma uporządkowaną listę pól found_at

Utworzenie tego indeksu eliminuje również wewnętrzne sortowanie tabel tymczasowych.

Wprowadź te dwie główne zmiany, a zobaczysz różnicę w czasie działania.

Spróbuj !!!

AKTUALIZACJA 25.08.2011 08:15 EDT

Spojrzałem na twoje indeksy. Nadal musisz utworzyć zasugerowany przeze mnie indeks.

RolandoMySQLDBA
źródło
Dzięki za wielkie wyjaśnienie, jak to działa. Obawiam się, że nie mogę wymyślić, jak uniknąć takich sytuacji. Wstawki muszą modyfikować indeksy, a select musi używać indeksu na hotspot_id i found_at. Byłbym zadowolony, gdybyś mógł również odpowiedzieć na mój „pomysł” przejścia na MyISAM.
kaczor1984
W rzeczywistości użycie MyISAM może pogorszyć sytuację, ponieważ każde WSTAWANIE, AKTUALIZACJA i USUWANIE w MyISAM powoduje pełną blokadę tabeli. Nawet jeśli użyjesz LOW_PRIORITY INSERTs lub INSERT DELAYED, pełne blokady tabeli będą nadal występować. Powinieneś zapoznać się z samym zapytaniem, ponieważ niezależnie od silnika pamięci, zapytania można dostosowywać wokół takich przeszkód. Przynajmniej może być wymagany nowy algorytm.
Zajmę się
Zaktualizowałem swój pierwszy post, abyś mógł wyświetlać zapytania dotyczące wstawiania i indeksy w tej tabeli.
kaczor1984
Zaktualizowałem moją odpowiedź analizą zapytania o identyfikatorze procesu 40
RolandoMySQLDBA,
4
proszę pana, jesteś bohaterem wśród ludzi za długie odpowiedzi w mysql
Mike
3

Rozwiązany!

Głównym problemem był query_cache. http://bugs.mysql.com/bug.php?id=21074

Po wyłączeniu „zamrożenie” zniknęło.

kaczor1984
źródło
A także wielkie podziękowania dla @RolandoMySQLDBA za wskazówki dotyczące optymalizacji moich zapytań i indeksów.
kaczor1984