Встановлення ліміту часу для транзакції в MySQL / InnoDB


11

Це випливало з цього пов'язаного питання , де я хотів знати, як змусити дві транзакції послідовно відбуватися в тривіальному випадку (де обидва працюють лише в одному рядку). Я отримав відповідь - використовуйте SELECT ... FOR UPDATEяк перший рядок обох транзакцій - але це призводить до проблеми: Якщо перша транзакція ніколи не вчиняється або відкочується, то друга транзакція буде заблокована на невизначений термін. innodb_lock_wait_timeoutЗмінні задає число секунд , після чого клієнт намагається зробити другу операцію б сказав « До жаль, спробуйте ще раз» ... але наскільки я можу сказати, що вони не були б спробувати ще раз до наступного перезавантаження сервера. Тому:

  1. Напевно, повинен бути спосіб примусити, ROLLBACKякщо транзакція береться назавжди? Повинен я вдатися до використання демона для вбивства таких транзакцій, і якщо так, як би виглядав такий демон?
  2. Якщо з'єднання припинено через wait_timeoutабо в interactive_timeoutсередині транзакції, транзакція відмовляється назад? Чи є спосіб перевірити це з консолі?

Уточнення : innodb_lock_wait_timeoutвстановлює кількість секунд, які транзакція чекатиме звільнення блокування перед відмовою; те, що я хочу, - це спосіб примусити звільнити замок.

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

START TRANSACTION;
SELECT SLEEP(55);
COMMIT;

Якщо за замовчуванням встановлено значення innodb_lock_wait_timeout = 50, ця операція завершиться без помилок через 55 секунд. І якщо ви додасте UPDATEперед SLEEPрядком, а потім ініціюєте другу транзакцію від іншого клієнта, який намагається виконати SELECT ... FOR UPDATEтой самий рядок, це друга операція, яка вичерпується, а не та, яка заснула.

Що я шукаю - це спосіб змусити припинити спокійний сон цієї транзакції.

Оновлення 2 : У відповідь на занепокоєння hobodave щодо того, наскільки реалістичним є наведений вище приклад, ось альтернативний сценарій: DBA підключається до живого сервера та працює

START TRANSACTION
SELECT ... FOR UPDATE

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


Ви щойно оновили своє запитання, щоб повністю суперечити своєму первинному питанню. Ви жирним шрифтом заявляєте: "Якщо перша транзакція ніколи не вчиняється або відкочується назад, друга транзакція буде заблокована на невизначений час". Ви спростуєте це своїм останнім оновленням: "це друга транзакція, яка вичерпується, а не та, яка заснула". - серйозно ?!
hobodave

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

@Trevor: Ваше фразування було абсолютно зрозумілим. Ви просто оновили своє запитання, щоб задати зовсім іншу річ, що є надзвичайно поганою формою.
hobodave

Питання завжди було одне і те ж: Проблема полягає в тому, що друга транзакція блокується на невизначений термін, коли перша транзакція ніколи не вчиняється або відкочується назад. Я хочу примусити скористатись ROLLBACKпершою транзакцією, якщо на її виконання піде більше nсекунд. Чи можна це зробити?
Тревор Бернхем

1
@TrevorBurnham Мені також цікаво, чому MYSQLне має конфігурації, щоб запобігти цьому сценарію. Тому що це неприйнятно зависання сервера через безвідповідальність клієнтів. Я не знайшов жодних труднощів зрозуміти ваше запитання, також це так актуально.
Dinoop paloli

Відповіді:


10

Більше половини цієї теми, схоже, стосується того, як задавати питання на ServerFault. Я думаю, що питання має сенс і є досить простим: як ви автоматично відмовляєте відстрочену транзакцію?

Одне рішення, якщо ви готові вбити весь зв'язок, - це встановити wait_timeout / interactive_timeout. Дивіться /programming/9936699/mysql-rollback-on-transaction-with-lost-disconnected-connection .


3

Оскільки ваше запитання задається тут на сервері ServerFault, логічно припустити, що ви шукаєте рішення MySQL для проблеми MySQL, особливо в царині знань, якими повинен володіти системний адміністратор та / або DBA. Як таке, наступне розділ вирішує ваші питання:

Якщо перша транзакція ніколи не вчиняється або відкочується назад, то друга транзакція буде заблокована на невизначений час

Ні, не буде. Я думаю, ти не розумієш innodb_lock_wait_timeout. Це робить саме те, що потрібно.

Він повернеться з помилкою, як зазначено в посібнику:

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
  • За визначенням, це не невизначено . Якщо ваша програма повторно підключається та блокується, то ваша програма "блокується на невизначений термін", а не транзакція. Друга транзакція дуже точно блокується на innodb_lock_wait_timeoutсекунди.

За замовчуванням транзакція не буде повернута назад. Відповідальність коду вашої програми несе за те, щоб вирішити, як впоратися з цією помилкою, спроба чи знову, або відкат.

Якщо ви хочете автоматичний відкат, це також пояснено в посібнику:

Поточна транзакція не відкочується назад. (Щоб повернути всю транзакцію назад, запустіть сервер із --innodb_rollback_on_timeoutможливістю.


RE: Ваші численні оновлення та коментарі

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

Тим не менш, я можу відповісти і на це питання. Протокол MySQL не має "тайм-аут запиту". Це означає, що ви не можете затримати першу заблоковану транзакцію. Потрібно почекати, поки воно не закінчиться, або вбити сеанс. Коли сеанс буде вбито, сервер автоматично поверне транзакцію назад.

Єдиною іншою альтернативою було б використовувати або написати бібліотеку mysql, яка використовує неблокуючі введення / виведення, що дозволить вашій програмі знищити нитку / вилку, зробивши запит через N секунд. Реалізація та використання такої бібліотеки виходять за рамки ServerFault. Це відповідне питання для StackOverflow .

По-друге, ви вказали таке у своїх коментарях:

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

Це взагалі не було очевидним у вашому первісному питанні, і досі це не так. Це можна було зрозуміти лише після того, як ви поділилися цим досить важливим примком у коментарі.

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


Вибачте, але хоча це правда, коли транзакція чекає блокування (як випливає з назви та документації innodb_lock_wait_timeout), це не так у ситуації, яку я створював: де клієнт висить або виходить з ладу до того, як отримає зміну, щоб зробити COMMITабо ROLLBACK.
Тревор Бернхем

@Trevor: Ви просто помиляєтесь. Це банально перевірити на своєму кінці. Я просто перевірив це на своєму кінці. Будь ласка, забирайте свої голоси.
hobodave

@hobo, тільки тому, що ваше право не означає, що він повинен йому подобатися.
Chris S

Кріс, ти можеш пояснити, як він має рацію? Як буде друга транзакція відбувається , якщо немає COMMITабо ROLLBACKнадсилається повідомлення , щоб вирішити першу угоду? Hobodave, можливо, було б корисно, якби ви представили свій тестовий код.
Тревор Бернхем

2
@Trevor: Ти не маєш трохи сенсу. Ви хочете серіалізувати транзакції, правда? Так, ви сказали так у своєму іншому запитанні і повторіть це тут у цьому питанні. Якщо перша транзакція не завершена, як, на вашу думку, ви б очікували завершення другої транзакції? Ви прямо сказали йому чекати, коли замок буде випущений у цьому рядку. Тому треба почекати. Що ти не розумієш з цього приводу?
hobodave

1

У подібній ситуації моя команда вирішила застосувати pt-kill ( https://www.percona.com/doc/percona-toolkit/2.1/pt-kill.html ). Він може повідомляти про або вбивати запити, які (серед іншого) працюють занадто довго. Потім можна запускати його в кроні кожні X хв.

Проблема полягала в тому, що запит, який мав несподівано довгий (наприклад, невизначений) час виконання, заблокував процедуру резервного копіювання, яка намагалася очистити таблиці з блокуванням читання, яка, в свою чергу, закрила всі інші запити на "очікування розмивання таблиць", які потім ніколи не вичерпувались тому що вони не чекають блокування, що призвело до помилок у програмі, що в кінцевому підсумку призвело до неповідомлення адміністраторів та зупинки програми.

Виправлення, очевидно, полягало в тому, щоб виправити початковий запит, але щоб уникнути випадкової ситуації в майбутньому, було б чудово, якби можна було автоматично вбити (або повідомити про нього) транзакції / запити, які тривають довше певного часу, який автор, здається, запитує. Це можливо при pt-kill.


0

Простим рішенням буде мати збережену процедуру для вбивства запитів, які займають більше часу, ніж потрібний timeoutчас, і використовувати innodb_rollback_on_timeoutпараметр разом з нею.


-2

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

Команда

SHOW PROCESSLIST;

дає вам таблицю з усіма командами, які виконуються в даний час, і час (у секундах) з часу їх запуску. Коли клієнт припинив давати команди, він описується як давання Sleepкоманди, при цьому Timeстовпець має кількість секунд з моменту останньої виконання команди. Таким чином, ви можете вручну зайти і KILLвсе, що має Timeзначення понад 5 секунд, якщо ви впевнені, що жоден запит не повинен займати більше 5 секунд у вашій базі даних. Наприклад, якщо процес з id 3 має значення 12 у своєму Timeстовпці, ви можете це зробити

KILL 3;

Документація для синтаксису KILL передбачає, що транзакція, що проводиться потоком з цим ідентифікатором, буде повернута назад (хоча я цього не перевіряв, тому будьте обережні).

Але як автоматизувати це та вбити всі сценарії понаднормових кожні 5 секунд? Перший коментар на цій самій сторінці дає нам підказку, але код є в PHP; здається , що ми не можемо зробити SELECTна SHOW PROCESSLISTз клієнта MySQL. Однак, якщо у нас є демон PHP, який ми запускаємо щосекунди $MAX_TIME, це може виглядати приблизно так:

$result = mysql_query("SHOW FULL PROCESSLIST");
while ($row=mysql_fetch_array($result)) {
  $process_id=$row["Id"];
    if ($row["Time"] > $MAX_TIME) {
      $sql="KILL $process_id";
      mysql_query($sql);
  }
}

Зауважте, що це призведе до побічного ефекту змусити всі непрацюючі клієнтські з'єднання знову підключитися, а не лише ті, що погано поводяться.

Якщо хтось має кращу відповідь, я хотів би це почути.

Редагувати: Існує хоча б один серйозний ризик використання KILLтаким способом. Припустимо, ви використовуєте сценарій вище для KILLкожного з'єднання, яке не працює протягом 10 секунд. Після того, як з'єднання не працює в режимі очікування протягом 10 секунд, але перед KILLвидачею, користувач, з’єднання якого вбивається, видає START TRANSACTIONкоманду. Потім вони KILLпід ред. Вони надсилають UPDATEі потім, подумавши двічі, видають ROLLBACK. Однак, оскільки KILLповернули свою первісну транзакцію, оновлення пройшло негайно, і її неможливо повернути! Дивіться цю публікацію для тестового випадку.


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

Добре, а не схиляючись і не залишаючи коментар з приводу, як щодо пояснення, чому це було б поганою ідеєю? Людина, яка опублікувала оригінальний скрипт PHP, заявила, що це захищає їх сервер від висіння; якщо є кращий спосіб досягти тієї ж мети, покажіть мені.
Тревор Бернхем

6
Коментар не чинить. Це попередження для всіх майбутніх читачів не намагатися використовувати ваше "рішення". Автоматичне вбивство тривалих операцій - це односторонньо страшна ідея. Це ніколи не слід робити . Це пояснення. Сеанси вбивства повинні бути винятком, а не нормою. Першопричина вашої надуманої проблеми помилка користувача. Ви не створюєте технічних рішень для DBA, які фіксують рядки, а потім "йдуть". Ти їх обстрілюєш.
hobodave

-2

Як запропоновано в коментарі hobodave, у деяких клієнтів (хоча це, мабуть, не є mysqlутилітою командного рядка), можна встановити часовий ліміт трансакції. Ось демонстрація за допомогою ActiveRecord у Ruby:

require 'rubygems'
require 'timeout'
require 'active_record'

Timeout::timeout(5) {
  Foo.transaction do
    Foo.create(:name => 'Bar')
    sleep 10
  end
}

У цьому прикладі транзакція закінчується через 5 секунд і автоматично повертається назад . ( Оновлення у відповідь на коментар hobodave: Якщо на відповідь базі даних потрібно більше 5 секунд, транзакція буде повернута назад, як тільки це станеться - не швидше.) Якщо ви хочете переконатися, що всі ваші транзакції закінчуються через nсекунди, Ви можете створити обгортку навколо ActiveRecord. Я здогадуюсь, що це стосується і найпопулярніших бібліотек Java, .NET, Python тощо, але я ще не перевіряв їх. (Якщо у вас є, будь ласка, опублікуйте коментар до цієї відповіді.)

Операції в ActiveRecord також мають перевагу в безпеці, якщо KILLвидається, на відміну від транзакцій, виконаних з командного рядка. Дивіться /dba/1561/mysql-client-believes-theyre-in-a-transaction-gets-killed-wreaks-havoc .

Здається, не можна застосувати максимальний час транзакції на стороні сервера, за винятком сценарію, як той, який я розмістив у своїй іншій відповіді.


Ви не помітили, що ActiveRecord використовує синхронний (блокуючий) введення / вивід. Все, що робить сценарій - це переривання команди сну Ruby. Якщо ваша Foo.createкоманда була заблокована на 5 хвилин, Timeout не вбивав би її. Це несумісне з прикладом, поданим у вашому запитанні. A SELECT ... FOR UPDATEможе легко блокувати протягом тривалого періоду часу, як і будь-який інший запит.
hobodave

Слід зазначити, що майже всі бібліотеки клієнтів mysql використовують блокування вводу-виводу, отже, у моїй відповіді є пропозиція про те, що вам потрібно буде використовувати або написати свій власний, що використовував неблокуючі введення-виведення. Ось проста демонстрація того, що Timeout є марним: gist.github.com/856100
hobodave

Мене насправді більше хвилювало питання щодо сценарію, коли клієнтська програма зависає (скажімо, потрапляє у нескінченний цикл) під час транзакції, ніж із тим, у якому транзакція триває довгий час після завершення MySQL. Але дякую, це хороший момент. Я оновив питання, щоб відзначити це.)
Тревор Бернхем,

Я не можу дублювати ваші заявлені результати. Я оновив суть, щоб використовувати ActiveRecord::Base#find_by_sql: gist.github.com/856100 Знову ж таки, це тому, що AR використовує блокування вводу / виводу.
hobodave

@hobodave Так, редагування було помилковим і було виправлено. Щоб було зрозуміло: timeoutметод поверне транзакцію, але не поки база даних MySQL не відповість на запит. Таким чином, timeoutметод підходить для запобігання довгих транзакцій на стороні клієнта або довгих транзакцій, які складаються з декількох відносно коротких запитів, але не для довгих транзакцій, у яких винуватцем є один запит.
Тревор Бернхем
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.