Причины для иногда медленных запросов?


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 в ваших предыдущих двух вопросах ( вопрос 1 , вопрос 2 ) попадают в таблицу «people» с помощью PRIMARY KEY с блокировкой на уровне строк. Это то, что я сказал еще в вопросе 1 6 июня 2011 г. 10:03

Все транзакции проходят первичный ключ. Поскольку PRIMARY является кластеризованным индексом в InnoDB, ключ PRIMARY и сама строка находятся вместе. Таким образом, прохождение строки и и ПЕРВИЧНЫЙ КЛЮЧ - это одно и то же. Следовательно, любая блокировка индекса на PRIMARY KEY также является блокировкой на уровне строк.

Что-то еще не было рассмотрено, что может приписать медлительность индексам: использование индексов NON-UNIQUE в InnoDB. Каждый индексированный поиск в InnoDB, использующий неуникальные индексы, также имеет идентификатор строки каждой строки, присоединенной к неуникальному ключу. RowID в основном происходит из кластерного индекса . Обновление неуникальных индексов ДОЛЖНО ВСЕГДА взаимодействовать с кластерным индексом, ДАЖЕ ЕСЛИ В ТАБЛИЦЕ НЕ ИМЕЕТ ПЕРВИЧНОГО КЛЮЧА.

Еще одна вещь, о которой стоит подумать, это процесс управления узлами BTREE в индексе. Иногда требуется разделение страниц по узлам. Все записи в узле BTREE неуникальных индексов содержат неуникальные поля ПЛЮС rowID внутри кластерного индекса. Чтобы должным образом смягчить разбиение таких страниц BTREE без нарушения целостности данных, строка, связанная с rowID, должна иметь внутреннюю блокировку на уровне строки.

Если таблица «people» имеет много неуникальных индексов, подготовьтесь к тому, чтобы большое количество страниц индекса в табличном пространстве, а также крошечные маленькие строки блокировок время от времени подкрадывались к вам.

Есть еще один фактор, который не так очевиден: ключевое население

Иногда, когда индекс заполняется, значения ключей, составляющие индексы, могут со временем становиться однобокими и приводить к тому, что MySQL Query Optimizer переключается с поиска по ключевым словам на сканирование индекса и, наконец, на полное сканирование таблицы. То, что вы не можете контролировать, если вы не перепроектируете таблицу с новыми индексами, чтобы компенсировать однобокость ключей. Пожалуйста, предоставьте структуру таблицы для таблицы people, количество таблиц people и выходные данные show indexes для таблицы people .

Даже если в запросах используется только PRIMARY KEY, однобокость ключей в неуникальных индексах все еще требует балансировки 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. Индексируются четыре столбца (email, 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 находится на курсе с двумя разными запросами. Кто-то должен блокировать страницы BTREE в порядке очереди.

Перед лицом этих двух пар запросов на курсе коллизий возможно блокирование одних и тех же BTREE-страниц в одном индексе может быть мучительным опытом для InnoDB или любой ACID-совместимой СУБД. Таким образом, замедление индекса является уделом этих пар запросов, если только вы не можете гарантировать, что запросы выполняются с AUTOCOMMIT = 1 или разрешением грязного чтения (хотя подобные коллизии делают READ-COMMITTED и READ-UNCOMMITED кошмаром для MVCC).

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

@RedBlueThing: В запросах из вопроса 2 первый запрос является запросом диапазона, поэтому достигается много блокировок строк. Также обратите внимание, что оба запроса пытаются заблокировать одну и ту же страницу идентификатора пространства 0, а не 4611, а n бит 152 блокируется в ПЕРВИЧНОМ КЛЮЧЕ, он же кластерный индекс.

Чтобы убедиться, что ваше приложение работает, по крайней мере, на основе ожидаемой вами серии событий, есть два варианта, которые вы можете попробовать:

Вариант 1) Преобразовать эту таблицу в MyISAM (по крайней мере, на сервере разработки). Каждое UPDATE, INSERT и DELETE будет навязывать полную блокировку таблицы в порядке очереди.

Вариант 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 строк требуется только SMALLINT UNSIGNED (ограничение 65 КБ, только 2 байта). Или используйте MEDIUMINT для запаса прочности.

DOUBLE дает вам 16 значащих цифр при стоимости 8 байтов. Имеет ли battery_level более 2 значащих цифр точности? FLOAT занимает 4 байта.

Я хочу сказать, что «меньше -> больше кешируется -> быстрее».

Пожалуйста, покажите нам медленные запросы; по крайней мере, некоторые из тех, которые внезапно стали медленнее. Мы можем только догадываться без них. Включите медленный журнал и установите long_query_time = 1; это поможет найти самые медленные запросы.

Ты понимаешь пользу от "составных" показателей?

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.