Как стать автором
Обновить

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

> Офф. сайт Mysql советует комититься почаще

Этот совет выглядит как «составляйте имена переменных подлиннее, чтобы наверняка не создать ещё одну переменную с таким же именем».

Транзакции должны быть такими, какими должны быть, не длиннее, не короче. Длина транзакции должна трактоваться бизнес-задачей и здравым смыслом. Слепое следование совету «короче-лучше» ни к чему хорошему не приведёт.
Меня это тоже мягко говоря удивило )
Нормальный совет, старая как мир ошибка ожидания пользовательского ввода внутри транзакции или чтение файлика из сети. Может быть совет из разряда не переходите дорогу в неправильных местах, но это не значит что такие советы не нужны
И какая проблема этим советом, собственно, решается?

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


Если не все операции ещё завершены, и для продолжения работы с данными нужен дополнительный ввод (файл из сети, пользовательский ввод) то что вы предлагаете делать?

Коммитить? Неконсистентные данные?
Сначала запрашиваем или получаем все данные необходимые для транзакции, затем вставляем и коммитим. Не получив всех требуемых данных даже не начинаем транзакцию.
У нас может не быть достаточно информации, чтобы запросить их до начала работы. Данные могут поступать во время, или зависеть вообще от текущего состояния базы (которое до начала транзакции невозможно предугадать в принципе).

Пример навскидку: обработка какой-то очереди файлов, очередь в таблице. Обработчиков больше одного.
Это скорее всего проблема архитектуры приложения. За много лет не было такого случая. когда между begin tran и commit было что-то в конец неопределенное, которое может занять секунду, а может час. Но хорошо помню случай когда один программист у нас при открытии модального окна начинал транзакцию, инсертил что-то, по «Ок» коммитил, по «Cancel» роллбачил. О последствиях нетрудно догадаться. Полконторы висело в блокировке когда менеджер открыл этот диалог и ушел курить. Я думаю тут речь именно об этом. А случаев разных кривых конечно много можно придумать.
Предложите ровное решение.

Пускай у нас есть mysql с путями до файлов, и файлы в фс.

Нужно обрабатывать эти файлы. Обработчиков больше одного. После обработки запись из таблицы удаляется.
Уверен что решение есть :) Надо смотреть систему в целом. Как вариант — не держать пути к файлам в mysql :)
Я говорю — суррогатных примеров можно много придумать, но как раз совет про то что такие случаи надо решать. Как — это и есть работа разработчика в каждом конкретном случае. Не пойму о чем спор? Совет правильный, не всегда ему можно следовать к сожалению, если было бы иначе и было бы универсальное решение, то и дедлоков не было бы совсем.
Например. Пользователи популярных Java-фреймворков начинают смотреть в настройки по умолчанию наконец и узнают почему SELECT, запущенный 3 часа назад, заблокировал сотню апдейтов.
Этот совет почти как ПДД: написан кровью на основании практики работы с приложениями, страдающими от deadlock-ов, случившихся в результате необдуманного использования autocommit=0 (а это значение по умолчанию во многих популярных framework-ах) и транзакций, запущенных часами. Классический пример: autocommit=0, connection pool, делаем SELECT и забываем закрыть транзакцию.
Совет «не забываем закрывать транзакции» смысл имеет.

Совет «делайте транзакции короче» — нет. Никто в здравом уме не будет делать транзакции длиннее чем нужно. И наоборот — короче чем есть транзакцию сделать нельзя, потому что бизнес требует, чтобы определённые операции выполнялись в единой транзакции. Так что делать её короче — значит не выполнять бизнес-требования.

PS: я комментирую исходя из того, что программисты руководствуются здравым смыслом и понимают, что делают.

PPS: ещё аналогия — «делайте транзакции короче» это то же самое что и «выбирайте меньше записей» (в случае, например, если мы реализуем экспорт и нам физически нужно выбрать все записи).
Совет «делайте транзакции короче» — нет. Никто в здравом уме не будет делать транзакции длиннее чем нужно. И наоборот — короче чем есть транзакцию сделать нельзя, потому что бизнес требует, чтобы определённые операции выполнялись в единой транзакции.


Ещё один часто встречающийся пример: залить таблицу из бэкапа на боевом, скажем, слэйве. 100,000 записей. Имеет смысл разбить на несколько транзакций?

> PS: я комментирую исходя из того, что программисты руководствуются здравым смыслом и понимают, что делают.

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

> PPS: ещё аналогия — «делайте транзакции короче» это то же самое что и «выбирайте меньше записей» (в случае, например, если мы реализуем экспорт и нам физически нужно выбрать все записи).

А что? Нормальная аналогия. Часто эффективнее выбирать данные по частям, чем сразу все. К сожалению, MySQL не поддерживает server-side cursors =(
> А что? Нормальная аналогия. Часто эффективнее выбирать данные по частям, чем сразу все. К сожалению, MySQL не поддерживает server-side cursors =(

Я в моей аналогии выражал другое, скорее не то, что не нужно выбирать много за раз, а что вообще не нужно выбирать много совсем. Никогда.

> Ещё один часто встречающийся пример: залить таблицу из бэкапа на боевом, скажем, слэйве. 100,000 записей. Имеет смысл разбить на несколько транзакций?

Зависит от задачи. Если с таблицей больше никто не работает — то 1 транзакция лучше. Но я с mysql давно уже не работаю плотно. В оракле 1 транзакция была бы лучше и правильнее.
> Я в моей аналогии выражал другое, скорее не то, что не нужно выбирать много за раз, а что вообще не нужно выбирать много совсем. Никогда.

=)

> Зависит от задачи. Если с таблицей больше никто не работает — то 1 транзакция лучше. Но я с mysql давно уже не работаю плотно. В оракле 1 транзакция была бы лучше и правильнее.

В MySQL это не всегда так.
zerkms, мы знаем чтоты грамотный разработчик, и для тебя совет «делайте транзакции короче» вполне очевиден, одняко для большинства разработчиков — это грабли…
Я не zerkms, но да, это так =)
Жаль в MySQL нет автономных транзакций. Можно было бы получить dead lock из одной сессии :)
Я не понимаю, если MySQL может задетектить сложившуюся патовую ситуацию, то почему он не может разрулить её способом, который для всех очевиден? Какие в этом способе подводные камни и будут ли они больше, чем существующий dead lock?
В данном случает он разруливает её единственно верным способо — откатом последней транзакции, которая учавствует в образовании deadlock. Но мне кажется тут какая-то архитектурная проблема, ведь логично же в третьем шаге дать возможность первой транзакции получить блокировку, после чего, дать доступ второй транзакции. Но происходит так, как происходит
Вообще то, я как раз говорил про логичный способ, называя его «способ, который для всех очевиден»: на третьем шаге дать первой транзакции доступ. Зачем выдавать ошибку там где ситуацию можно безошибочно разрулить?
«очевидный способ» нарушает ACID-принцип

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

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

На третьем шаге не надо никого лишать блокировки.
2) Транзакция№2 пытается получить блокировку типа X и… начинает ждать когда Транзакция№1 освободит блокировку S
3) Транзакция№1 пытается получить блокировку типа X и… начинает ждать когда Транзакция№2 получит блокировку типа X и освободит её

Т2 ещё не получила блокировку, по этому, можно легко дать блокировку типа Х транзакции№1.
Видимо там какая-то очередь. И на втором шаге в очередь попал запрос от Транзакции№2
хм… пожалуй соглашусь — логично было дать Т1 поднять уровень блокировки до X

повторил на MS SQL -думал он поумнее- тоже дедлок получил
чего то мы не учитываем в наших рассуждениях
Я же говорю — логично предположить что там очередь запросов на получение блокировки, которая не учитывает «автора» этой блокировки. Ну это единственное пока логичное объяснение такого поведения
Мне кажется есть вариант получше — сразу получать блокировку типа X.


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

Так можно договориться до того, что лучше использовать табличные блокировки. LOCK TABLE xxx WRITE, что мелочиться? Пофиг, что всю таблицу использовать нельзя, зато никаких дедлоков.
>> они не для искуственных примеров
Да не скажите. У меня вот на рабочем сервере откуда-то (пока не разобрался) берутся S-локи
Это шутка?

S-локи — это локи чтения. Например,
insert into t2 select * from t1; 

выставляет такие локи.
На t1, естественно.
S-локи — это «shared», т.е. «разделяемые», это не обязательно «чтение», все может зависеть от уровня изоляции транзакции. При уровне изоляции Serializable чтение точно также выставит X (эксклюзивную) блокировку. А при ReadUncommitted — запись выставит разделяемую. В «версионниках» запись тоже «разделяемая».
Ну да, правильно. Я для дефолтной REPEATABLE READ пример написала.
А при ReadUncommitted

Уважаемый, вы точно не спутали название уровня изоляции? Может вы имели в виду REPEATABLE READ? При ReadUncommitted происходят грязные чтения (чтения незакоммиченных данных из других транзакций), нужно совсем не переживать за консистентность данных, если используется этот уровень изоляции.
при repeatable read невозможно обновление данных, которые были прочитаны в другой транзакции.
При уровне изоляции Serializable чтение точно также выставит X (эксклюзивную) блокировку

А в каком случае чтение выставит SHARED блокировку (кроме ручного указания LOCK IN SHARE MODE), при REPEATABLE READ? И если да, то для того, чтобы записи не блокировались при чтении, мне нужен уровень изоляции READ COMMITTED?
Начиная с Repeatable read и далее read committed (snapshot), read uncommitted. При serializable упрощенно последовательное выполнение (но дедлоки все равно возможны).
Там было все немного сложнее. S-локи так же появляются при инсерте в таблицу, в которой есть foreign key check. Т.е. вставляем в table1 строку например с user_id = 2 и получаем автоматически S-lock в таблице User на строке с primary key = 2
Сложнее, но, в общем, то же самое: таблицу User же вы не изменяете. Хотя угадать сложно, это правда. Пользователь Fragster хороший комментарий по теме написал.

PS: надеюсь, про InnoDB lock monitor и таблицы InnoDB в Information Schema вы знаете =)
> Мне кажется есть вариант получше — сразу получать блокировку типа X.

Это не избавит от дедлоков. Классический вариант:

Транзакция 1: блокирует строку 1 и продолжает работу
Транзакция 2: блокирует строку 2, пытается заблокировать строку 1 и виснет
Транзакция 1: пытается заблокировать строку 2
к дэдлоку приволят 2 вещи
  • разныйпорядок захвата ресурсов (транзакция Т1 захватила рескурс Р1, транзакция Т2 захватила ресурс Т2, транзакция хочет дальше ресутс Т2, а Т2 — Т1)
  • эскалация блокировок (который и описан с статье)

И если на первый программист может как-то влиять (хотя тут при больших проектах без фреймворков уже обойтись сложно), то на второй — не всегда. Например MS SQL при большой нагрузке иногда производит эскалацию со страничной блокировки до табличной, что на больших нагрузках печалит :(
+1
особенно про порядок захвата ресурсов

а автор тему не раскрыл совсем, особенно умиляют советы про «коммитится почаще»
Вот цитата с сайта mysql
«Commit your transactions often. Small transactions are less prone to collision.»
ну рекомендация — абсолютно точная и настолько же бесполезная, как в анекдоте — «где мы? на воздушном шаре, сэр», с таким же успехом можно было порекомендовать не писать код приводящий к дедлокам.

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

другими словами, если у меня, по бизнес-требованиям, есть возможность сделать транзакцию на только из 9 выражений, я никогда не буду делать транзакцию на 10 выражений
Так я и не говорю что это хорошая рекомендация, я абсолютно согласен что это бредовый совет
кстати в 2008R2 появился волшебный хинт позволяющий запрещать эскалацию
Я решаю эту проблему повторением транзакции по которому вернулось исключение дидлока, в исключении даже описание приходит: 1213 Deadlock found when trying to get lock; try restarting transaction
На второй проход всегда проходит.
А в каких случаях блокировки могут быть лучше, например, optimistic locking (с версиями)?
optimistic locking вас ставит перед фактом уже случившегося и они немного для других целей — для длительных бизнес-процессов скорее

Вопрос почему могут возникать "Deadlock found" если нигде в явном виде не используется "LOCK IN SHARE MODE" ? Эти локи типа S могут создаваться при insert,select,update,delete автоматически?

Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории