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

sverel

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

Допустим, есть класс для работы с деревьями NestedSets. Метод insert() выполняет набор SQL-запросов, которые должны либо быть выполнены все, либо ни один из них. Таким образом имеем что-то вроде:
PHP:
class DB_NESTEDSETS {
  function insert(...) {
     $SQL->transactionStart();
     // .... выполняем всё что нужно ...
     $SQL->transactionCommit();
  }
}
Это базовый класс фреймворка, который давно написен, отлажен, оттестирован и отлично работает. Его трогать нельзя из-за философии.
Но в проекте надо сделать связанные данные и это тоже надо делать в транзакции. Получается вот так:
PHP:
$SQL->startTransaction();
$dbTree->insert(...); // NestedSets
$dbRelationObjects->update(...); // связь
$SQL->transactionCommit();
В итоге сталкиваюсь с вложенными транзакциями, которые не поддерживаются в MySQL и ничего не работает :( .

Отличным выходом, как мне кажется, является старт транзакций в начале скрипта и коммит в самом конце. Из всех методов, естественно, придётся выкинуть start/stop transaction. Это надо сделать всего один раз и больше никогда не думать о целостности данных при написании различных методов. Т.е. пишем в index.php (и во всех остальных точках входа):
PHP:
$SQL->transactionStart();
$application = new APP();
$application->start();
$SQL->transactionCommit();
! Главное не писать commit() в деструкторе который выполнится даже при вызове die(); из середины программы.
Или ещё лучше: в конструкторе который создаёт объект $SQL написать start, а в деструкторе поставить rollback. Тогда в конец index.php останется только поставить commit.

Такой глобальный подход намного проще: теперь можно забыть про целостность данных решив эту проблему раз и на всегда и одновременно решить проблему вложенных транзакций. По моему, вполне логично. Но настараживает тот факт, что все CMS/CMF которые я анализировал использовали именно локальные внутриметодовые транзакции и нигде я не видел глобальные транзакции.
Хотя ещё больше меня настараживает тот факт, что большинство движков вообще не используют транзакции (wordpress, mzz, drupal и даже PEAR-класс DB_NestedSet). Странно как-то. Неужели в 2010 году все сервера работают настолько стабильно, что никому уже не нужны транзакции или всем плевать на целостность данных?
 

pilot911

Новичок
интересный вопрос, тоже с таким столкнулся

кто как решает ?
 

dimagolov

Новичок
а сделать счетчик nestedTransactionCount и увеличивать его на каждый startTransaction / уменьшать на transactionCommit а собственно запросы к базе делать если счетчик равен 1/0 религия не позволяет?
 

tz-lom

Продвинутый новичок
MyISAM не поддерживает транзанкции ( это к вопросу мол а где? )
я бы сказал что такие штуки как "я знаю как работает этот класс,поэтому перед вызовом я начинаю транзанкцию а в конце завершаю" - это нихрена не правильно

если уж так надо - делайте обёртку (или наследника) которая будет для каждый метод заворачивать в транзанкцию
 

sverel

Новичок
ну вот... обвёртку для нормально и правильно работающего класса. Причём не просто обвёрстку, а дробить методы класса что бы:
1. сначала получить класс который работает, но не обеспечивает надёжность.
2. Обвёрстка-заплатка, которая делает работу первого класса надёжнее и стабильнее. Что-то вроде надстройки для увеличения безопастности. Ага. Это как в битриксе встроеный антивирус.
И это нужно сделать для всех классов, которые используют транзакции!

А потом ещё надо будет выбирать какой класс использовать... Но я не могу выбирать, потому что у меня один объект ($dbTree) используется в нескольких местах. Мне что, создавать 2 разных объекта: первый вызывать внутри внешнийх транзакций, другой вне транзакций?

А вариант с глобальной транзакцией хуже?

Я конечно понимаю, что решений тут бесконечное количество как и у любой задачки, но меня интересует самый правильный.
 

sverel

Новичок
dimagolov а зачем вложеные транзакции? Из всех методов надо будет убрать вызов startTransaction/commitTransaction. И на всегда забыть о них.
Потому что писать в каждом методе в котором есть несколько SQL-запросов start/commit - это равносильно тому что бы в каждом методе писать mysql_connect(), а в конце метода mysql_close();. Но ведь Вы же так неделаете...
 

pilot911

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

sverel

Новичок
До сих пор так и не понял, чем счетчики вложенных транзакций и обвёртки лучше одной глобальной транзакции?
 

dimagolov

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

tz-lom

Продвинутый новичок
обёрткой тоже пользоваться надо уметь
разместите классы в одниъ неймспейсах,обёртки в других,а свои классы в своих,а затем просто мапьте класс на нужный неймспейс
 

sverel

Новичок
dimagolov
В Вашем варианте придётся переписывать базовые методы $SQL->startTransaction() и commit(). В моём варианте ни о каких транзакциях думать больше не придётся и запускать их тоже не придётся, потому что всё и так внутри транзакции.

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

dimagolov

Новичок
sverel, у тебя раздвоение личности:
Это базовый класс фреймворка, который давно написен, отлажен, оттестирован и отлично работает. Его трогать нельзя из-за философии.
Но в проекте надо сделать связанные данные и это тоже надо делать в транзакции. Получается вот так:
...
В итоге сталкиваюсь с вложенными транзакциями, которые не поддерживаются в MySQL и ничего не работает
Отличным выходом, как мне кажется, является старт транзакций в начале скрипта и коммит в самом конце. Из всех методов, естественно, придётся выкинуть start/stop transaction.
Если вложенность транзакций возникает из-за действительной вложенности атомарных операций с БД, как в приведенном примера, то проблем в этом нет и решение со счетчиком решает все вопросы.
А вот запуск транзакции на "просто так" потому что где-то может возникнуть вложенность это маразм. Потому что это очевидный костыль, который ничего общего ни с логикой приложения ни с целовстностью БД не имеет. Это маразм еще и потому, что откат транзакции это ошибка, которая требует определенной реакции, а у тебя в нее будет попадать ВСЕ предыдущие изменения в БД и как именно на них реагировать ты знать скорее всего в месте отката не будешь.

-~{}~ 06.09.10 16:47:

В Вашем варианте придётся переписывать базовые методы $SQL->startTransaction() и commit().
это разве проблема? это изменение ВСЕГО ЛИШЬ двух методов, причем ТОЛЬКО их внутренней реализации. никак на их ВНЕШНЕЕ поведение это не повлияет, вернее только устранит возможные ошибки, которые могли возникать при вложенных транзакциях. как бы именно ради такого эффекта и придумали ООП - меняем реализацию одного класса, а остальной код, который его использует, остается неизменным.
 

sverel

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

Что касается роллбека, то он происходит всегда в не явном виде при закрытии соединения. Но по умолчанию в МуСКЛе стоит AUTO_COMMIT=1, поэтому все СКЛ-запросы к моменту дисконнекта уже выполнены и журнал транзакций пуст. Можно и не писать в деструкторе $SQL->transRollback() - это я для ясности и лучшего понимания написал.

Что касается глобальной транзакции: вот мы и добрались до сути топика. Вы считаете, что удаление элемента является атомарной и неделимой операцией. Это понятно, но я предлагаю пойти дальше. Представим ситауцию: есть список элементов (не обязательно древовидных, допустим, это новости). Если админ галочками отметил несколько элементов и нажал "действия с отмеченными: удалить", то мой скрипт в цикле удалит каждый элемент. Если скрипт прервётся по середине и в результате будет удалена только часть элементов, то целостность данных нарушена не будет. Но ведь откат РОЛЛБЭКом тоже вполне логичное завершение данной критичной ситуации. Тут уже наверное, вопрос филосовский. Но навсегда забыть про атомарные не делимые операции и не расставлять везде кругом start/commit - привлекательная перспектива.
 

Mols

Новичок
sverel
+1 к предложению dimagolov
Транзакции нужны там, где они нужны.
И нефиг всё делать в транзакциях.
 

sverel

Новичок
Ну транзакции в любом случае запущены, просто AUTO_COMMIT=1 делает своё дело.
 

Sluggard

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

А вариант с глобальной транзакцией хуже?
- теряется гибкость
- увеличивается время блокировки записей.
 

sverel

Новичок
Sluggard: теряется гибкость
Ну rollback я нигде не использую как механизм управления потоком, поэтому даже не представляю, как может пострадать гибкость.
Sluggard: увеличивается время блокировки записей.
Вообще, мне казалось, что всё происходит в транзакции по умолчанию ибо AUTO_COMMIT=1 - свидетельствует о том, что сразу после выполнения запроса происходит коммит. Хотя, я могу ошибаться, надо потестировать методом тыка и выяснить.

> класса _под_конкретный_проект
Это было написано в топикстарте и имелось ввиду выкидывание из DB_NESTEDSETS start/commit, что бы не получать вложенные транзакции. Вариант с вычислением на стороне ПХП глубины транзакции - это универсальный и гибкий метод, я не спорю. Вопрос только в том, что лучше и чем глобальная транзакция плоха.

пошёл тестить скорость работы с AUTO_COMMIT=1 и без него...
 

dimagolov

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