Размышления о транзакциях

pilot911

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

sverel

Новичок
sverel: пошёл тестить скорость работы с AUTO_COMMIT=1 и без него...
Готовы тесты. И они меня поразили на столько, что я даже не поверил и многократно перепроверил тестирующие скрипты, но ошибки не нашёл.

Итак, создаю таблицу из двух столбцов и забиваю в неё 1000 рандомных записей
PHP:
CREATE TABLE `transactions` (
	`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
	`rand` int(11) UNSIGNED NOT NULL,
	PRIMARY KEY (`id`)
) ENGINE=InnoDB
Далее пишу ф-цию, которая выполняет UPDATE для каждой строки ставя новое рандомное значение.
Тест первый: засекаю время; запускаю ф-цию; определяю время работы.
Тест второй: засекаю время; BEGIN; запускаю ф-цию; COMMIT; определяю время работы.

Результат:
Время выполнения без транзакции: 26 153.05 мсек.
Время выполнения внутри транзакции: 240.19 мсек.

Удивительные результаты, которые заставили меня 10 раз перепроверить скрипт. Если калькулятор мне не изменят, то 1000 апдейтов внутри транзакции выполняются в 109 раз быстрее, чем без транзакции.

Посмотреть и скачать исходники теста можно тут: http://dev.web-fusion.ru/tests/transactions-speed.phps
Тесты выполнялись на WinXP, PHP 5.2.14, MySQL 5.1, AMD Phenom x64, ОЗУ 4Гб.


Sluggard: увеличивается время блокировки записей.
Где же оно увеличивается?
 

newARTix

Новичок
sverel
оно увеличивается "между строк" :) Странно, что работая с транзакциями ты не хочешь видеть разницы между пакетным апдейтом строк и реальной практикой. В реальности апдейтится одна строка, а при этом блокируются все текущие операции на чтение, это ты не учел в своем тесте?
Впрочем, я тоже не имею особого опыта работы с транзакциями, но для меня эта проблема очевидна.
Хотя, насколько я понимаю, все-таки операции на чтение не блокируются, просто чуть увеличивается время выполнения и кушается оператива. Не?
 

sverel

Новичок
pilot911:
если проходят деньги и есть нормальный коммит - это прекрасно и должно отработать независимо от следующих операций, которые могут быть второстепенными... лучше сразу это понять, имхо
Вот! В этом и заключается ошибка! Второстепенных операций НЕ БЫВАЕТ! Все инсёрты, апдейты и делиты очень важны! Даже если не нарушают целостность данных, всё равно они важны.
Покажу это на примере.
Допустим при каждом обращении авторизованного юзера, Вы в таблице `users` обновляете `date_last_visit`. При этом, если запрос не выполнится, то всем будет насрать то что у юзера не правильно отображаться "время последнего визита". На целостность данных это тем более не влияет. Вам наверное кажется, что это "второстепенный запрос"?
Хорошо. Представте: юзер переводит деньги со своего счёта на другой счет и, допустим, транзакция успешно коммитится, а время date_last_visit не обновляется. Но у Вас это второстепенный запрос, поэтому Вам на него насрать. Таким образом мы получим, что перевод денег был осуществлён в 22:30 - 10 сентября 2010, а время последнего визита юзера 20 мая 2010 года. Т.е. получаем, что юзер не был на сайте пол года, а деньги отправил кто-то другой!
Что теперь Вы думаете про "второстепенные запросы"?

P.S.> по этому тема топика названа РАЗМЫШЛЕНИЯ над транзакциями. Я ничего не утверждаю. Всё вышесказаное является моим ИМХО, но по моему, в современных приложениях транзакции используются не совсем корректно.
Или же все вышеперечисленные аргументы не являются весомымы.
1. Увеличивается время выполнения? Оказалось, что наоборот, внутри транзакций СКЛ-запросы выполняются намного быстрее.
2. Вы говорите, что скрипт необходимо разбивать на единичные не делимые (корпускулярные) операции? Вышеописанный пример демонстрирует, что не делимым должны быть ВСЕ инсерты, апдейты, делиты. Даже, те которые Вам кажутся абсалютно не важными.
3. ... возможно есть какой-то другой весомый аргумент?
 

newARTix

Новичок
sverel
я не пойму. как можно не видеть очевидного. раз уж вы так привязались к своим переводам денег. допустим деньги переводятся с одного и того же счета. два раза подряд. может это происходить одновременно? нет. второй перевод не может даже начаться, пока не выполнится первый. потому что ТРАНЗАКЦИЯ БЛОКИРУЕТ другие запросы. Ну? Дошло?
 

sverel

Новичок
Автор оригинала: newARTix В реальности апдейтится одна строка, а при этом блокируются все текущие операции на чтение, это ты не учел в своем тесте?
Опять ошибка. Когда я внедрял глобальную транзакцию в свой фрейворк, то первым делом написать TDD тесты для транзакций. В этих тестах тестируется запуск паралельного процесса, который обращается к тем записям, которые уже изменены первым процессом. Выглядит это примерно так:

PHP:
// Проверяю работоспособность паралельного процесса.
$SQL->query('UPDATE ... SET `fld`=88 WHERE `id`=25');
$testValue = get_file_content('http://domain.com/_tests/helper-transaction.php'); // Скрипт только печатает значение текущего значения `fld` в 25-ой строке.
assertIdentical('88', $testValue);

// Проверяю транзакцию
$SQL->query('BEGIN');
$SQL->query('UPDATE ... SET `fld`=55 WHERE `id`=25');
$testValue = get_file_content('...helper-transaction.php');
assertIdentical('88', $testValue); // Для паралельного процесса новые значение ещё не вступили в силу
$SQL->query('COMMIT');
$testValue = get_file_content('...helper-transaction.php');
assertIdentical('55', $testValue); // А теперь вступили.

// Дальше тоже самое, но с ROLLBACK-ом.
-~{}~ 10.09.10 23:17:

Автор оригинала: newARTix может это происходить одновременно?
Для этого один юзер должен открыть 2 окна и одновременно засубмитить их. Даже если он так сделает, то мелкие локальные транзакции Вас тоже не спасут. Хотя вероятность одновременного их запуска конечно снизится, но принципиально, Ваш подход ничего не меняет.
Это, кстати, надо проверить... Подозреваю, что если писать SET `fld`=`fld`+5000 - логично, что такие запросы можно ставить в очередь. Но надо проверить, как с этим работает МуСКЛ.
 

newARTix

Новичок
sverel
вот именно, что вводя глобальные транзакции вы увеличиваете вероятность одновременного запуска до 100%. То есть пока для одного пользователя не выполнятся ВСЕ запросы, второй пользователь ждет. Кагбе очевидно, что БД разрешает эти конфликты и блокировки на своей стороне, но готовы ли вы доверить ей всё это? Я лично не настолько доверяю БД, чтобы вешать из-за неё всё приложение. Мало ли что ей в голову взбредет. Она не для этого предназначается. Не надо ударяться в крайности из-за сомнительной выгоды.

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

sverel

Новичок
P.S.>ктати, TDD тесты оказались очень полезными. Например некоторые хостинги настраивают БД без поддержки InnoDB. Когда создаёшь таблицу с ENGINE=InnoDB, то создаётся MyISAM. Мои тесты сразу же отваливаются на таком хостинге. :) Наверно мне тоже можно поставить подпись "По жизни с TDD".

-~{}~ 10.09.10 23:25:

newARTix
Я протестирую как ведёт себя МуСКЛ в такой ситуации и попробую разобраться.
Но если вы не доверяете БД, то на кой чёрт Вам эти транзакции и вообще БД?
 

newARTix

Новичок
sverel
это неестественная для неё ситуация. В ней она может повести себя непредсказуемо. Так как все начинает зависеть от моего конкретного случая. Зачем мне эти проблемы? А классическое и документированное поведение вполне предсказуемо, тут я спокоен :) Вообще транзакции на уровне приложения почти не использую, там где они прям вообще необходимы - предпочитаю процедуры.
 

dimagolov

Новичок
на самом деле одим счетчиком транзакций дело не ограничивается. ведь часто к успешному или нет исполнению транзакции привязаны манипуляции с файлами или дальнейшие изменения БД. просто вложенными счетчиками транзакций тут не обойтись, так как вложенная транзакция будет думать, что она отработала успешно, а сбой во внешней ее откатит. у меня возникают идеи о неком call-back на успешное/не успешное завершение транзакции или об использовании исключений. кто и как решает побную задачу? или вложенность транзакций нельзя допускать во избежание подобных проблем?

-~{}~ 10.09.10 16:52:

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

п.с. вообще странный человек sverel: выполнил на 999 операций начала/коммита транзакции в одном из тестов больше и удивляется, что при этом время исполнения тоже получилось больше.
 

newARTix

Новичок
dimagolov
ну вот, разрушил все иллюзии одним постом :)
Насчет вложенных транзакций, как я уже сказал, по-моему лучше применять процедуры. Городить исключения вокруг всего этого - перебор. Не дело это PHP.
 

sverel

Новичок
А как Вы избавитесь от вложенных транзакций? Если есть базовая модель и над ней начинают городить модель под проект, которая более сложная...
Можно конечно, выкинуть из базовой модели транзакции. Но ведь на её основе Вам может понадобится создать другую модель...

dimagolov: п.с. вообще странный человек sverel: выполнил на 999 операций начала/коммита транзакции в одном из тестов больше и удивляется, что при этом время исполнения тоже получилось больше.
1. Ну мне это было не очевидно.
2. Я даже и представить себе не мог, что будет ТАКАЯ огромная разница: в 100-120 раз быстрее! Для меня это открытие, которое я пока не знаю как объяснить...
3. В любом случае, это мой опыт. Вам это очевидно? Наверное Вы уже наигрались с транзакциями. Дайте мне тоже их потыкать...
 

Sluggard

Новичок
Где же оно увеличивается?
Ты что-то пытался доказать? Твой пример не в тему. Но даже если его рассматривать. При глобальном коммите длительность блокировки каждой записи - 240 мсек. При AUTOCOMMIT = 1, в среднем, - 26 мсек = время блокировки в 9 раз меньше.
У меня твой пример дал следующие результаты:
Время выполнения без транзакции: 1741.79 мсек.
Время выполнения внутри транзакции: 237.07 мсек.
Done.
Разница в 136 раз? Хотя эта цифра ни о чем не говорит.
Ну мне это было не очевидно.
Каждый коммит сбрасывает журнал на диск. При частых коммитах скорость вращения диска станет узким местом.
Я даже и представить себе не мог, что будет ТАКАЯ огромная разница: в 100-120 раз быстрее! Для меня это открытие, которое я пока не знаю как объяснить...
Выполни: SET GLOBAL innodb_flush_log_at_trx_commit = 0
Будешь удивлен не меньше.
 

nirex

Новичок
dimagolov
уже говорил, что нужен счетчик транакций увеличивать при страте с проверкой если он равен 0 открывать транзакцию, иначе просто игнорить, а в комите этот счетчик на -1 делать а потом провекру, если == 0 то делать настоящий коммит, в роллбеке счетчик скидывать в 0 и все дела )))
 

tenshi

Новичок
как ты будешь откатывать вложенную транзакцию не откатывая внешнюю?
 

nirex

Новичок
tenshi
не знаю таких задач, если хотя бы одна часть транзакции не выполнима то и откатывать надо все. Остальное велосипед ;)

Смотри, если возмем атом, пускай он состоит из кучи маленьких частиц, и вдруг какие -то условия отщепнули от него одну частицу и закрепились с пустотой.
В итоге получится , что этот атом уже имеет слабую структуру и может разрушиться в любой момент.
Точно также с твоей конструкцией.

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

tenshi

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

nirex

Новичок
"транзакционность называют атомарной операцией"
Чего ты фантазируешь ? :))) Конечно можно и дом ка курьях ножка построить, лишь бы желание было :)
PHP:
BEGIN;
 SELECT * FROM test; 
SAVEPOINT t1 
INSERT INTO test SET `id`=1 
SAVEPOINT t2 
INSERT INTO test SET `id`=2
SELECT * FROM test;
 ROLLBACK TO SAVEPOINT t1
 INSERT INTO test SET `id`=3 
COMMIT;
http://dev.mysql.com/doc/refman/5.0/en/savepoint.html
 

tenshi

Новичок
приведённый код - реализация вложенных транзакций через goto
 

sverel

Новичок
sverel:
newARTix
Я протестирую как ведёт себя МуСКЛ в такой ситуации и попробую разобраться.
Протестировал на одной таблице. Первый процесс меняет date_last_visit для 23-его юзера, второй процесс меняет для 58-ого. Итог: оба запроса выполнились одновременно, паралельно. Т.е. один другому не мешает.
В случае если оба процесса изменяют одну и ту же запись, то второй процесс ждёт коммита со стороны первого.

Таким образом, изменение date_last_visit внутри глобальной транзакции никак не будет сказываться на скорости работы даже если одновременно зайдёт очень много разных юзеров. Лиш в редких случаях, когда разные юзеры выполняют операцию над одним и тем же, придётся подождать (хотя в Вашем подходе им тоже придётся подождать). Так и чем же глобальные транзакции хуже ядерных?

И ещё: почему-то все участники данного обсуждения считют, что всё что не разрушает базу, является второстепенными и не важными запросами. Продолжают обсуждать атомарные и молекулярные транзакции, проигнорировав мой пример с прошедшей транзакцией, но с отвалившимся апдейтом date_last_visit. ( http://phpclub.ru/talk/showthread.php?postid=912576#post912576 ). А что же Вы будете делать, если всё же в Вашей системе возникнет ситуация когда юзер перевёл бабки не заходя в систему?
 
Сверху