Установка лимита времени для транзакции в 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 о том, насколько реалистичен приведенный выше пример, приведем альтернативный сценарий: администратор БД подключается к работающему серверу и работает

START TRANSACTION
SELECT ... FOR UPDATE

где вторая строка блокирует строку, в которую приложение часто пишет. Затем администратор базы данных прерывается и уходит, забывая завершить транзакцию. Приложение останавливается, пока ряд не разблокируется. Я бы хотел минимизировать время, в течение которого приложение зависло в результате этой ошибки.

Тревор Бернхэм
источник
Вы только что обновили свой вопрос, чтобы полностью конфликтовать с вашим первоначальным вопросом. Вы отмечаете жирным шрифтом «Если первая транзакция никогда не будет зафиксирована или откатана, то вторая транзакция будет заблокирована на неопределенный срок». Вы опровергаете это в своем последнем обновлении: «истекает время второй транзакции, а не той, которая заснула». -- шутки в сторону?!
hobodave
Я полагаю, что моя формулировка могла бы быть более ясной. Под «заблокирован на неопределенный срок» я имел в виду, что для второй транзакции истекло время ожидания с сообщением об ошибке «попробуйте перезапустить транзакцию»; но делать это было бы бесполезно, потому что это было бы просто перерывом снова. Таким образом, вторая транзакция заблокирована - она ​​никогда не будет завершена, потому что она будет отсрочена.
Тревор Бернхэм
@Trevor: Ваша фраза была совершенно ясна. Вы просто обновили свой вопрос, чтобы задать совершенно другую вещь, что является крайне плохой формой.
hobodave
Вопрос всегда был один и тот же: проблема в том, что вторая транзакция блокируется на неопределенный срок, когда первая транзакция никогда не фиксируется или не откатывается. Я хочу форсировать ROLLBACKпервую транзакцию, если она занимает больше nсекунды. Есть ли способ сделать это?
Тревор Бернхэм
1
@TrevorBurnham Мне также интересно, почему MYSQLнет конфигурации для предотвращения этого сценария. Потому что недопустимо зависание сервера из-за безответственности клиентов. Я не обнаружил затруднений в понимании вашего вопроса, и он так актуален.
Dinoop Paloli

Ответы:

10

Кажется, что больше половины этой темы посвящено тому, как задавать вопросы по ServerFault. Я думаю, что вопрос имеет смысл и довольно прост: как автоматически откатить заблокированную транзакцию?

Одним из решений, если вы хотите разорвать все соединение, является установка wait_timeout / interactive_timeout. См. Https://stackoverflow.com/questions/9936699/mysql-rollback-on-transaction-with-lost-disconnected-connection .

pauljm
источник
3

Поскольку ваш вопрос задается здесь на ServerFault, логично предположить, что вы ищете решение MySQL для проблемы MySQL, особенно в области знаний, в которых системный администратор и / или администратор базы данных будут иметь опыт. Таким образом, следующее В разделе рассматриваются ваши вопросы:

Если первая транзакция никогда не будет зафиксирована или откатана, то вторая транзакция будет заблокирована на неопределенный срок

Нет не будет Я думаю, что вы не понимаете 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 .

hobodave
источник
Извините, но хотя true, когда транзакция ожидает блокировки (как следует из названия и документации innodb_lock_wait_timeout), это не так в ситуации, которую я изложил: когда клиент зависает или падает, прежде чем он получит изменение для выполнения COMMITили ROLLBACK,
Тревор Бернхэм
@Trevor: Вы просто не правы. Это тривиально, чтобы проверить на вашем конце. Я только что проверил это на моем конце. Пожалуйста, возьмите обратно свое голосование.
hobodave
@hobo, только потому, что твое право не означает, что ему должно это нравиться.
Крис С
Крис, не могли бы вы объяснить, как он прав? Как произойдет вторая транзакция, если нет 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

После некоторого поиска в Google, похоже, нет прямого способа установить ограничение по времени для каждой транзакции. Вот лучшее решение, которое мне удалось найти:

Команда

SHOW PROCESSLIST;

дает вам таблицу со всеми командами, выполняемыми в настоящее время, и временем (в секундах) с момента их запуска. Когда клиент перестает давать команды, он описывается как Sleepкоманда, а в Timeстолбце указывается количество секунд с момента выполнения последней команды. Таким образом, вы можете вручную ввести KILLвсе, что имеет Timeзначение, скажем, 5 секунд, если вы уверены, что ни один запрос не должен занимать более 5 секунд в вашей базе данных. Например, если процесс с идентификатором 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
Комментарий не фальшивый. Это предупреждение для всех будущих читателей, чтобы они не пытались использовать ваше «решение». Автоматическое уничтожение долгосрочных транзакций - ужасная идея в одностороннем порядке. Это никогда не должно быть сделано . Это объяснение. Убийства должны быть исключением, а не нормой. Основной причиной вашей надуманной проблемы является ошибка пользователя. Вы не создаете технические решения для администраторов баз данных, которые блокируют строки и затем «уходят». Вы увольняете их.
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 также имеют преимущество в том, что они безопасны при выдаче a KILL, в отличие от транзакций, выполняемых из командной строки. См. Https://dba.stackexchange.com/questions/1561/mysql-client-believes-theyre-in-a-transaction-gets-killed-wreaks-havoc .

По-видимому, невозможно установить максимальное время транзакции на стороне сервера, за исключением сценария, подобного тому, который я опубликовал в моем другом ответе.

Тревор Бернхэм
источник
Вы упустили из виду, что ActiveRecord использует синхронный (блокирующий) ввод-вывод. Все, что делает скрипт, прерывает команду Ruby sleep. Если ваша Foo.createкоманда заблокирована на 5 минут, тайм-аут не убьет ее. Это несовместимо с примером, приведенным в вашем вопросе. A SELECT ... FOR UPDATEможет легко блокироваться на длительные периоды времени, как и любой другой запрос.
hobodave
Следует отметить, что почти во всех клиентских библиотеках mysql используется блокирующий ввод / вывод, поэтому в моем ответе я предлагаю вам использовать или написать свою собственную, которая использовала неблокирующий ввод / вывод. Вот простая демонстрация бесполезности тайм-
аута
На самом деле меня больше интересовал мой вопрос о сценарии, в котором клиентское приложение зависает (скажем, попадает в бесконечный цикл) во время транзакции, а не о сценарии, в котором транзакция занимает много времени в конце MySQL. Но спасибо, это хороший момент. Я обновил вопрос, чтобы отметить это.)
Тревор Бернхем
Я не могу дублировать ваши заявленные результаты. Я обновил суть, чтобы использовать ActiveRecord::Base#find_by_sql: gist.github.com/856100 Опять же, это потому, что AR использует блокирующий ввод / вывод.
hobodave
@hobodave Да, это изменение было ошибочным и было исправлено. Для ясности: timeoutметод откатит транзакцию, но не до тех пор, пока база данных MySQL не ответит на запрос. Таким образом, этот timeoutметод подходит для предотвращения длинных транзакций на стороне клиента или длинных транзакций, которые состоят из нескольких относительно коротких запросов, но не для длинных транзакций, в которых виновником является один запрос.
Тревор Бернхам