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
Зображуйте послідовність подій
- Знайдіть рядок за первинним ключем
- Заблокуйте рядок та кластерний індекс
- Створіть дані MVCC для всіх стовпців, що оновлюються
- Чотири стовпці індексуються (електронна пошта, company_id, iphone_device_id, picture_blob_id)
- Кожен індекс вимагає управління BTREE
- У межах одного простору транзакцій кроки 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]).