Причини періодично повільних запитів?


16

Ми запускаємо MySQL 5.1 на Windows Server 2008 R2.

Ми пізно проводили діагностику в нашій базі даних пізно і виявили тривожні артефакти, які ми не можемо пояснити . Ми додали код для входу в журнал, коли у нас були запити, які тривалий час (> 2000 мс). Результати були дивовижними (і, можливо, поясненням наших тупиків).

Інколи запити, які зазвичай займають дуже мало часу (<10 мс), займають від 4 до 13 секунд. Щоб було зрозуміло, це запити, які виконуються постійно (кілька разів на секунду) і не страждають від цих спадів часу запитів.

Ми пройшли наші індекси, шукаючи явних помилок і не мали великої долі.

Оновлення

Стіл людей:

| people | CREATE TABLE `people` (
`people_id` bigint(20) NOT NULL AUTO_INCREMENT,
`company_id` bigint(20) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`temp_password` varchar(10) DEFAULT NULL,
`reset_password_hash` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`phone` varchar(32) DEFAULT NULL,
`mobile` varchar(32) DEFAULT NULL,
`iphone_device_id` varchar(160) DEFAULT NULL,
`iphone_device_time` datetime DEFAULT NULL,
`last_checkin` datetime DEFAULT NULL,
`location_lat` double DEFAULT NULL,
`location_long` double DEFAULT NULL,
`gps_strength` smallint(6) DEFAULT NULL,
`picture_blob_id` bigint(20) DEFAULT NULL,
`authority` int(11) NOT NULL DEFAULT '0',
`active` tinyint(1) NOT NULL DEFAULT '1',
`date_created` datetime NOT NULL,
`last_login` datetime NOT NULL,
`panic_mode` tinyint(1) NOT NULL DEFAULT '0',
`battery_level` double DEFAULT NULL,
`battery_state` varchar(32) DEFAULT NULL,
PRIMARY KEY (`people_id`),
KEY `email` (`email`),
KEY `company_id` (`company_id`),
KEY `iphone_device_id` (`iphone_device_id`),
KEY `picture_blob_id` (`picture_blob_id`),
CONSTRAINT `people_ibfk_1` FOREIGN KEY (`company_id`) REFERENCES `companies` (`company_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `people_ibfk_2` FOREIGN KEY (`picture_blob_id`) REFERENCES `blobs` (`blob_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4658 DEFAULT CHARSET=utf8 |

Індекси:

+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| Table  | Non_unique | Key_name         | Seq_in_index | Column_name      | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| people |          0 | PRIMARY          |            1 | people_id        | A         |        3502 |     NULL | NULL   |      | BTREE      |         |
| people |          1 | email            |            1 | email            | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
| people |          1 | company_id       |            1 | company_id       | A         |        3502 |     NULL | NULL   |      | BTREE      |         |
| people |          1 | iphone_device_id |            1 | iphone_device_id | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
| people |          1 | picture_blob_id  |            1 | picture_blob_id  | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+

в таблиці на сервері є ~ 5000 рядків, що доставляє нам проблеми.


1
Є щось, що ви ще не показали в попередніх двох питаннях. Додайте до цього питання три (3) речі: 1) ПОКАЗУЙТЕ СТВОРИТИ СТІЛЬКИ людей \ G 2) ПОКАЖИТИ ІНДЕКСИ від людей; 3) ВИБІРТЕ КУХНУ (1) від людей;
RolandoMySQLDBA

@RolandoMySQLDBA Я зроблю це, як тільки завтра прийду на роботу. Ура :)
RedBlueThing

Я оновив свою відповідь. Будь ласка, прочитайте !!!
RolandoMySQLDBA

@RolandoMySQLDBA Спасибі :) Досі розбираємо цей матеріал. Я дам вам знати, як ми йдемо.
RedBlueThing

Відповіді:


14

UPDATE-запити у попередніх двох запитаннях ( Question1 , Question2 ) потрапляють у таблицю "люди" від PRIMARY KEY із блокуванням рівня рядків. Про це я говорив ще в Питання1 6 червня 2011 р. 10:03

Усі транзакції проходять через ПЕРВИЧНИЙ ключ. Оскільки PRIMARY - це кластерний індекс в InnoDB, ключ PRIMARY і сам рядок є разом. Таким чином, проходження ряду і ПЕРВИЧНИЙ КЛЮЧ - це одне і те саме. Тому будь-яке блокування індексу на ПЕРВИЧНОМ КЛЮЧІ також є блокуванням рівня рядків.

Ще щось не розглянуто, що може пояснити повільність індексам: Використання NON-UNIQUE індексів в InnoDB. Кожен індексований пошук у InnoDB, що використовує унікальні індекси, також має rowID кожного рядка, приєднаний до унікального ключа. RowID в основному виводиться з індексу кластеру . Оновлення не унікальних індексів ОБОВ'ЯЗКОВО ВЗАЄМО ВЗАЄМО З ВІДКЛЮЧЕНОЮ ІНФОРМАЦІЮ, ЩО ТАКОЖ ТАБЛИЦЯ НЕ МАЄ ПЕРШИЙ КЛЮЧ.

Інша річ, про яку слід подумати - це процес управління вузлами BTREE в індексі. Іноді вимагає розбиття сторінок вузлів. Усі записи у вузлі BTREE не унікальних індексів містять не унікальні поля PLUS the rowID у кластерному індексі. Щоб належним чином пом'якшити розбиття таких сторінок BTREE, не порушуючи цілісність даних, рядок, пов’язаний з rowID, повинен мати внутрішнє блокування рівня рядків.

Якщо в таблиці "людей" є багато не унікальних індексів, підготуйтеся, щоб у просторі таблиць була велика кількість сторінок з покажчиками, а також, щоб час від часу підкрадалися крихітні невеликі замки рядків.

Є ще один не очевидний фактор: ключове населення

Іноді, коли індекс заповнюється, ключові значення, що складають індекси, можуть з часом втратити чинність і призвести до того, що оптимізатор запитів MySQL переключиться з ключових пошукових запитів, на сканування індексів та, нарешті, на повне сканування таблиці. Це ви не можете контролювати, якщо ви не переробили таблицю новими індексами для компенсації однобічності клавіш. Будь ласка, вкажіть структуру таблиці для таблиці "люди", кількість таблиць "людей" та показники показів для таблиці "люди" .

Навіть якщо запити використовують лише ПЕРВІЙНИЙ КЛЮЧ, для одночасності ключів у не унікальних індексах все-таки потрібне BTREE врівноваження та розбиття сторінок. Таке управління BTREE призведе до помітного уповільнення через переривчасті блокування рівня рядків, яких ви не мали наміру робити.

ОНОВЛЕННЯ 2011-06-14 22:19

Запитання з питання 1

UPDATE people SET company_id = 1610, name = '<name>', password = '<hash>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@yahoo.com',
phone = NULL, mobile = '<phone>', iphone_device_id = 'android:<id>-<id>',
iphone_device_time = '2011-06-06 05:35:09', last_checkin = '2011-06-06 05:24:42',
location_lat = <lat>, location_long = -<lng>, gps_strength = 3296,
picture_blob_id = 1190,
authority = 1, active = 1, date_created = '2011-04-13 20:21:20',
last_login = '2011-06-06 05:35:09', panic_mode = 0,
battery_level = NULL, battery_state = NULL WHERE people_id = 3125

UPDATE people SET company_id = 1610, name = '<name>', password = '<hash>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@yahoo.com',
phone = NULL, mobile = '<phone>', iphone_device_id = 'android:<id>-<id>-<id>-<id>',
iphone_device_time = '2011-06-06 05:24:42', last_checkin = '2011-06-06 05:35:07',
location_lat = <lat>, location_long = -<lng>, gps_strength = 3296,
picture_blob_id = 1190,
authority = 1, active = 1, date_created = '2011-04-13 20:21:20',
last_login = '2011-06-06 05:35:09', panic_mode = 0,
battery_level = NULL, battery_state = NULL WHERE people_id = 3125

Зображуйте послідовність подій

  1. Знайдіть рядок за первинним ключем
  2. Заблокуйте рядок та кластерний індекс
  3. Створіть дані MVCC для всіх стовпців, що оновлюються
  4. Чотири стовпці індексуються (електронна пошта, company_id, iphone_device_id, picture_blob_id)
  5. Кожен індекс вимагає управління BTREE
  6. У межах одного простору транзакцій кроки 1-5 намагаються повторити в одному рядку, оновлюючи одні й ті ж стовпці (надсилайте однакові в обох запитах, company_id однакові в обох запитах, picture_blob_id однакові в обох запитах, iphone_device_id різні)

Запитання з питання 2

UPDATE people SET iphone_device_id=NULL
WHERE iphone_device_id='iphone:<device_id_blah>' AND people_id<>666;

UPDATE people SET company_id = 444, name = 'Dad', password = '<pass>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@gmail.com',
phone = NULL, mobile = NULL, iphone_device_id = 'iphone:<device_id_blah>',
iphone_device_time = '2011-06-06 19:12:29', last_checkin = '2011-06-07 02:49:47',
location_lat = <lat>, location_long = <lng>, gps_strength = 66,
picture_blob_id = 1661,
authority = 1, active = 1, date_created = '2011-03-20 19:18:34',
last_login = '2011-06-07 11:15:01', panic_mode = 0, battery_level = 0.55,
battery_state = 'unplugged' WHERE people_id = 666;

Ці два запити ще більш заплутані, оскільки перший запит оновлює все, крім people_id 666. Сотні рядків болісно блокуються лише першим запитом. Другий запит - це оновлення people_id 666, що виконує 5 послідовностей подій. Перший запит виконує ті самі 5 послідовностей подій у кожному задіяному рядку, за винятком people_id 666, але індекс для iphone_device_id знаходиться в курсі interecept з двома різними запитами. Хтось повинен замикатися на сторінках BTREE за принципом «перший прихід-перший-сервіс».

Перед цими двома парами запитів на курсі зіткнення, щоб можливо заблокувати одні і ті ж сторінки BTREE в межах одного індексу, може бути досвід роботи з InutDB або будь-якими сумісними з ACID RDBMS. Таким чином, уповільнення індексу - це доля цих пар запитів, якщо ви не можете гарантувати, що запити виконуються з AUTOCOMMIT = 1 або дозволяючи брудні читання (хоча колізії, подібні цим, роблять READ-COMMITTED та READ-UNCOMMITED кошмаром для MVCC).

ОНОВЛЕННЯ 2011-06-15 10:29

@RedBlueThing: У запитах із питання 2 перший запит - це запит діапазону, тому досягається багато блокувань рядків. Також зауважте, що обидва запити намагаються заблокувати один і той же пробіл 0 сторінка № 4611 n біт 152 заблоковано в ПЕРВІЙНИЙ КЛЮЧ, він також кластерний індекс.

Для того, щоб переконатися, що додаток працює, принаймні, на основі подій, які ви очікуєте, ви можете спробувати два різні варіанти:

Варіант 1) Перетворіть цю таблицю в MyISAM (принаймні на сервері розробки). Кожна ОНОВЛЕННЯ, ВСТАВКА та ВИДАЛЕННЯ накладатиме повний замок столу на принципі "перший прихід, перший сервіс".

Варіант 2) Спробуйте використовувати рівень ізоляції SERIALIZABLE . Це заблокує всі заплановані рядки в режимі SHARED.

Послідовність подій, які ви очікуєте, або порушиться, або буде успішною, використовуючи ці два альтернативних варіанти. Якщо обидва ці варіанти не вдається, вам потрібно буде переглянути свій додаток та визначити пріоритет порядку виконання запитів. Після встановлення цього пріоритету ви можете просто скасувати ці параметри (для параметра 1 поверніться до InnoDB, для варіанту 2, поверніться до рівня ізоляції за замовчуванням [припиніть використання SERIALIZABLE]).


@RolandoMySQLDBA Я оновив наше запитання деталями, про які ви запитували.
RedBlueThing

@RolandoMySQLDBA Дякую за те, що ще раз поглянули на це. Мені було цікаво, ви коментуєте питання 2, чому перший запит фіксує сотні рядків? Не заблокував би він лише 666 рядків, які відповідають ідентифікатору пристрою? (тобто один ряд)
RedBlueThing

@RolandoMySQLDBA Виходячи із вашої пропозиції з питання 1, ми перевірили налаштування для автокомісії та підтвердили, що він увімкнено.
RedBlueThing

@RolandoMySQLDBA Чи є специфічна проблема із запитами з першого питання (крім оновлення всіх полів у рядку). Щось, що пояснило б час виконання запиту 13 секунд? Я розумію, що індексація чотирьох стовпців - це не те, що ви б рекомендували, але чи справді це призведе до такої низької продуктивності?
RedBlueThing

@RolandoMySQLDBA +1 і дякую за всі ваші пропозиції. Ми не змінили рівень ізоляції, щоб вирішити проблему. Натомість ми зробили часткові оновлення поля для питання 2 та оптимізували запит на шляху оновлення. Вуаля! більше немає тупиків. :)
RedBlueThing

3

ПОКАЗУЙТЕ ВАРІАБЛИ, ПОДИХНУЮТЬ "innodb%" - Зокрема, якщо дані та індекси просто не досягли розміру буферного пулу, ви могли б вдарити по диску набагато важче, ніж раніше. I / O - це вбивця з великою продуктивністю.

Більшість ваших полів удвічі більша за необхідність. BIGINT (8 байт) є надмірним для більшості ідентифікаторів. 5000 рядків потребує лише МАЛЬКОГО НЕЗНАЧЕНОГО (обмеження 65 К, лише 2 байти). Або використовувати MEDIUMINT для межі безпеки.

DOUBLE дає вам 16 значущих цифр вартістю 8 байт. Чи рівень батареї має більш ніж дві значущі цифри точності? FLOAT займає 4 байти.

Моя думка тут полягає в тому, що "менше -> більш кешоване -> швидше".

Будь ласка, покажіть нам повільні запити; принаймні деякі з тих, що раптом стали повільнішими. Ми можемо лише здогадуватися без них. Увімкніть повільний журнал і встановіть long_query_time = 1; вони допоможуть знайти найповільніші запити.

Чи розумієте ви користь "складних" індексів?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.