Понимание транзакций pdo mysql

#mysql #concurrency #transactions

#php #mysql #pdo #транзакции

Вопрос:

В документации PHP говорится:

Если вы никогда раньше не сталкивались с транзакциями, они предлагают 4 основных функции: атомарность, согласованность, изоляция и долговечность (ACID). С точки зрения непрофессионала, любая работа, выполняемая в транзакции, даже если она выполняется поэтапно, гарантированно будет безопасно применена к базе данных и без вмешательства других соединений, когда она будет зафиксирована.

ВОПРОС:

Означает ли это, что у меня могут быть два отдельных php-скрипта, выполняющих транзакции одновременно, без их вмешательства друг в друга?


УТОЧНЕНИЕ ТОГО, ЧТО Я ПОДРАЗУМЕВАЮ ПОД «ВМЕШАТЕЛЬСТВОМ«:

Представьте, что у нас есть следующая employees таблица:

  __________________________
|  id  |  name  |  salary  |
|------ -------- ----------|
|  1   |  ana   |   10000  |
|------ -------- ----------|
 

Если у меня есть два сценария с похожим / одинаковым кодом, и они выполняются в одно и то же время:

script1.php и script2.php (оба имеют один и тот же код):

 $conn->beginTransaction();

$stmt = $conn->prepare("SELECT * FROM employees WHERE name = ?");
$stmt->execute(['ana']);
$row = $stmt->fetch(PDO::FETCH_ASSOC);

$salary = $row['salary'];
$salary = $salary   1000;//increasing salary

$stmt = $conn->prepare("UPDATE employees SET salary = {$salary} WHERE name = ?");
$stmt->execute(['ana']);

$conn->commit(); 
 

и предполагая, что последовательность событий выглядит следующим образом:

  • script1.php выбирает данные
  • script2.php выбирает данные
  • script1.php обновляет данные
  • script2.php обновляет данные
  • script1.php происходит фиксация()
  • script2.php происходит фиксация()

Какова будет итоговая зарплата ana в этом случае?

  • Будет ли это 11000? И будет ли это означать, что 1 транзакция будет перекрывать другую, потому что информация была получена до того, как произошла любая фиксация?
  • Будет ли это 12000? И будет ли это тогда означать, что независимо от порядка, в котором данные были обновлены и выбраны, commit() функция заставляла их выполняться по отдельности?

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

Комментарии:

1. Я почти уверен SQLSTATE[23000]: Integrity constraint violation , что для одного из этих сценариев появится ошибка. Вопрос в том, какой из них, если они оба выполняются в одно и то же время.

Ответ №1:

Вы не найдете ответа в документации php, потому что это не имеет ничего общего с php или pdo.

Табличный движок Innodb в mysql предлагает 4 так называемых уровня изоляции в соответствии со стандартом sql. Уровни изоляции в сочетании с блокирующими / неблокирующими чтениями будут определять результат приведенного выше примера. Вам нужно понять последствия различных уровней изоляции и выбрать подходящий для ваших нужд.

Подводя итог: если вы используете сериализуемый уровень изоляции с отключенной автоматической фиксацией, то результат будет 12000. На всех других уровнях изоляции и сериализации с включенной автоматической фиксацией результат будет 11000. Если вы начнете использовать блокировку чтения, то результат может быть 12000 при всех уровнях изоляции.

Ответ №2:

Судя по приведенным условиям (отдельный оператор DML), вам здесь нужна не транзакция, а блокировка таблицы. Это очень распространенная путаница.

Вам нужна транзакция, если вам нужно убедиться, что ВСЕ ваши инструкции DML были выполнены правильно или не были выполнены вообще.

Означает

  • вам не нужна транзакция для любого количества запросов SELECT
  • вам не нужна транзакция, если выполняется только один оператор DML

Хотя, как было отмечено в отличном ответе от Shadow, вы можете использовать транзакцию здесь с соответствующим уровнем изоляции, это было бы довольно запутанно. Что вам здесь нужно, так это блокировка таблицы. Движок InnoDB позволяет блокировать отдельные строки вместо блокировки всей таблицы и, следовательно, должен быть предпочтительным.

Если вы хотите, чтобы зарплата составляла 1200, тогда используйте блокировки таблиц.

Или — более простой способ — просто запустите атомарный запрос обновления:

 UPDATE employees SET salary = salary   1000 WHERE name = ?
 

В этом случае будут записаны все зарплаты.

Если ваша цель отличается, лучше выразите ее явно.

Но опять же: вы должны понимать, что транзакции в целом не имеют ничего общего с выполнением отдельных скриптов.Что касается вашей темы состояния гонки, вас интересуют не транзакции, а блокировка таблицы / строки. Это очень распространенная путаница, и вам лучше понять это прямо:

  • транзакция предназначена для обеспечения успешного выполнения набора запросов DML в рамках одного скрипта.
  • блокировка таблицы / строки предназначена для обеспечения того, чтобы другие выполнения скриптов не мешали.

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

Комментарии:

1. Я думаю, что вопрос был направлен на понимание буквы I из ACID, а не на выполнение определенного действия определенным образом. По крайней мере, так я это интерпретировал. Я не согласен с тем, что вы написали о блокировке таблиц. В innodb вы можете выполнить блокировку на уровне строк с помощью оператора select, поэтому вам не нужно блокировать всю таблицу, чтобы в итоге получить 12000. Я согласен, что в приведенном выше коде нет необходимости в выборе, поскольку вся операция может быть выполнена за одно обновление. Но если это только пример, направленный на понимание изоляции транзакций, то он служит цели.

2. Я использую «блокировку таблицы» в качестве общего термина, отмечая при этом, что с innodb предпочтительнее блокировка на уровне строк. Поэтому я предполагаю, что ваш комментарий является скорее терминологической проблемой. Что касается значения вопроса, я, вероятно, лучший парень в плане понимания вопросов, отвечающий уже 15 лет. Вопрос ACID как проблема XY для OP, с которой они только что столкнулись, исследуя свою конкретную проблему. В любом случае, я думаю, что оба наших ответа подтверждают друг друга, делают его лучше для читателя.

Ответ №3:

Увы, «без вмешательства» требуется некоторая помощь от программиста. Это необходимо BEGIN и COMMIT для определения степени «транзакции». И…

Ваш пример неадекватен. Требуется первое утверждение SELECT ... FOR UPDATE . Это сообщает обработке транзакций, что, вероятно, будет UPDATE получено сообщение для строк, которые SELECT извлекаются. Это предупреждение имеет решающее значение для «предотвращения помех». Теперь временная шкала гласит:

  • script1.php Начинается
  • script2.php Начинается
  • script1.php выбирает данные ( FOR UPDATE )
  • script2.php выбирает, что данные заблокированы, поэтому он ожидает
  • script1.php обновляет данные
  • script1.php происходит фиксация()
  • script2.php выбирает данные (и получит вновь зафиксированное значение)
  • script2.php обновляет данные
  • script2.php происходит фиксация()

(Примечание: это не «тупик», просто «ожидание».)