[Из песочницы] Unlocking the Postgres Lock Manager. Брюс Момжиан

Расшифровка доклада 2020 года Брюса Момжиана «Unlocking the Postgres Lock Manager».

ty-6uiivopfbn26wlezoscj2awy.png

(Примечание: Все SQL запросы из слайдов вы можете получить по этой ссылке: http://momjian.us/main/writings/pgsql/locking.sql)

Привет! Замечательно снова быть здесь в России. Я прошу прощение, что я не смог приехать в прошлом году, но в этом году у Ивана и у меня большие планы. Я, надеюсь, что буду здесь гораздо чаще. Я обожаю приезжать в Россию. Я буду посещать Тюмень, Тверь. Я очень рад, что мне удастся побывать в этих городах.

Меня зовут Брюс Момжиан. Я работаю в EnterpriseDB и работаю с Postgres более 23 лет. Я живу в Филадельфии, в США. Путешествую примерно 90 дней в году. И посещаю порядка 40 конференций. Мой веб сайт, который содержит слайды, которые я вам буду сейчас показывать. Поэтому после конференции вы можете с моего личного сайта их скачать. Там также содержатся около 30 презентаций. А также есть видео и большое количество записей в блоге, более 500. Это достаточно содержательный ресурс. И если вам интересен этот материал, то я вас приглашаю им воспользоваться.

Я раньше был преподавателем, профессором до того, как начал работать с Postgres. И я очень рад, что мне удастся сейчас рассказать вам то, что я собираюсь вам рассказать. Это одна из самых интересных моих презентаций. И эта презентация содержит 110 слайдов. Говорить мы начнем с простых вещей, а к концу доклад станет все сложнее и сложнее, и станет достаточно сложным.

kcd3wyncbyh3ulvnxul6gwhqpdu.png

Это довольно неприятная беседа. Блокировка — это не самый популярный предмет. Мы хотим, чтобы это куда-то исчезло. Это как ходить к стоматологу.

koeqpc9kesmf5qyjmlcf7bkey2i.png


  1. Блокировка является проблемой для большого количества людей, которые работают в базах данных и у которых работают одновременно несколько процессов. Им необходима блокировка. Т. е. сегодня я вам дам базовые знания по блокировке.
  2. Идентификаторы транзакций. Это довольно скучная часть презентации, но их необходимо понять.
  3. Далее мы поговорим о типах блокировки. Это достаточно механическая часть.
  4. И далее мы приведем некоторые примеры блокировок. И это будет достаточно сложно для восприятия.

onwfblqi_nai0io_xaouefgvj18.png

Давайте поговорим о блокировках.

mx7xun0qiyzyubuuwps9-buci9g.png

Терминология у нас достаточно сложная. Сколько из вас знают, откуда этот отрывок? Два человека. Это из игры, которая называется «Колоссальное приключение в пещере». Это было текстовая компьютерная игра в 80-ых годах, мне кажется. Там надо было зайти в пещеру, в лабиринт и текст менялся, но при этом содержание было примерно одинаковое каждый раз. Вот так я помню эту игру.

kwsvwrw0bdvvyg9epyhjmwn1v4o.png

И здесь мы видим наименование блокировок, которые пришли к нам из Oracle. Мы используем их.

wihacnekca_ard5l0wr0a28zfk4.png

Здесь мы видим термины, которые меня смущают. Например, SHARE UPDATE ECXLUSIVE. Далее SHARE RAW ECXLUSIVE. Честно говоря, эти названия не очень понятны. Мы постараемся их более детально рассмотреть. Некоторые содержать слово «share», которое значит — отделиться. Некоторые содержат слово «exclusive» — эксклюзивный. В некоторых содержатся оба эти слова. Я бы хотел начать с того, как эти блокировки работают.

bkbngjiwtsy--n4vn5vdriac6lc.png

И также очень важно слово «доступ» — access. И слова «row» — строка. Т. е. распределение доступа, распределение строк.

jiv6lc2oy3odsczuzk7dbd1cnjg.png

Еще одна проблема, которую необходимо понять в Postgres, я, к сожалению, не смогу рассказать об ней в своем выступлении, это MVCC. У меня есть отдельная презентация по этой теме на моем веб-сайте. И если вы думаете, что эта презентация сложная, то MVCC — это, наверное, моя самая сложная. И если вам интересно, то можете посмотреть ее на сайте. Посмотреть можете видео.

1onnexe4_3w1bq45puzxp8pbhla.png

Еще один момент, который нам необходимо понять — это идентификаторы транзакции. Многие транзакции не могут работать без уникальных идентификаторов. И здесь у нас идет пояснение того, что такое транзакция. В Postgres есть две системы нумерации транзакций. Я знаю, это не очень красивое решение.

x-ciazdg9smwob_dypivaatcryo.png

Также имейте в виду, что слайды будут достаточно сложные для восприятия, поэтому на то, что выделено красным цветом, именно на это надо обратить внимание.

rycvcputt0-zjjaw_qd8-2eqho8.png

http://momjian.us/main/writings/pgsql/locking.sql

Смотрим. Красным цветом выделен номер транзакции. Здесь показана функция SELECT pg_back. Она возвращает мою транзакцию и ID этой транзакции.

Еще один момент, если вам нравится эта презентация и вы хотите запустить ее в своей базе данных, то вы можете пройти по этой ссылке, выделенной розовым цветом, и скачать SQL для этой презентации. И можете просто запустить ее в вашем PSQL и вся презентация окажется у вас на экране незамедлительно. Она не будет содержать цветов, но по крайней мере мы сможете ее увидеть.

zd4uxy8mi9mt_eykebehpgneo8c.png

В данном случае мы видим ID транзакции. Это номер, который мы ей присвоили. И есть еще один тип ID транзакции в Postgres, который называется виртуальный ID транзакция

И мы должны понять это. Это очень важно, иначе мы не сможем понять блокировку в Postgres.

Виртуальный ID транзакция — это ID транзакции, которая не содержит постоянных значений. Например, если я запускаю команду SELECT, то я, скорее всего, не буду менять базу данных, я ничего не буду блокировать. Поэтому, когда мы запускаем простой SELECT, мы не даем этой транзакции постоянный ID. Мы там даем ей только виртуальный ID.

И это повышает производительность Postgres, улучшает возможности для очистки, поэтому виртуальный ID транзакции состоит из двух чисел. Первое число перед слэшем — это ID бэкенда. А справа мы видим просто счетчик.

vrmwa9hp0-j7knie7jhqklrckjk.png

Поэтому если запускаю запрос, то он говорит, что ID бэкенд — 2.

-vobikxqvfj4s3egfncoxbid6ia.png

А если я запускаю серию таких транзакций, то мы видим, что счетчик каждый раз увеличивается, когда я запускаю запрос. Например, когда я запускаю запрос 2/10, 2/11, 2/12 и т. д.

1vhe1mmid-yapcww2wdyokymlqo.png

Имейте в виду, что здесь есть две колонки. Слева мы видим виртуальный ID транзакции — 2/12. А справа у нас постоянный ID транзакции. И это поле пустое. И эта транзакция не модифицирует базу данных. Поэтому я не присваиваю ей постоянный ID транзакции.

3tkgirnnr8viirpeplgekdl1kfq.png

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

w9smftopxt_bx5qgzzm6iwtoytq.png

Итак, здесь еще один запрос, еще одна транзакция. Виртуальный номер транзакции — 2/13. И если я попрошу постоянный ID транзакции, то, когда я запущу запрос, я его получу.

zc37xrdzyuhhcf682f6he9dq4xc.png

Итак, еще раз. У нас есть виртуальный ID транзакции и постоянный ID транзакции. Просто поймите этот момент, чтобы понять поведение Postgres.

rev4he96mrpvcq1ivnfowxgseh4.png

Мы переходим к третьему разделу. Здесь мы просто пройдем через различные типы блокировок в Postgres. Это не очень интересно. Последний раздел будет гораздо интересней. Но мы должны рассмотреть базовые вещи, потому иначе мы не поймем того, что будет дальше.

Мы пройдем через этот раздел, мы посмотрим на каждый тип блокировок. И я вам покажу примеры, как они устанавливаются, как они работают, покажу вам некоторые запросы, которые можно использовать, чтобы посмотреть, как работает блокировка в Postgres.

yoilbudxydqtmk1z_7o581u48zs.png

Чтобы создать запрос и посмотреть, что происходит в Postgres, нам нужно выпустить запрос в system view. В данном случае красным цветом у нас выделен pg_lock. Pg_lock — это системная таблица, которая говорит нам, какие блокировки сейчас используются в Postgres.

Тем не менее мне очень сложно показать вам pg_lock сам по себе, потому что это довольно сложно. Поэтому я создал view, который показывает pg_locks. И он также выполняет для меня некоторую работу, которая позволяет мне лучше понять. Т. е. он исключает мои блокировки, мою собственную сессию и т. д. Это просто стандартный SQL и он позволяет лучше вам показать, что происходит.

nrc18-rlejg40j6lu1mjuugkodg.png

Еще одна проблема в том, что этот view очень широкий, поэтому мне приходится создать второй — lockview2.

sex1kfzsm2tygn617zoajpr3fk8.png И он показывает мне еще колонки из таблицы. И еще один, который показывает мне остальные колонки. Это достаточно сложно, поэтому я постарался представить это как можно проще.

qxr6m6qjqrjnhchy8xqhtiuso-o.png

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

60lz84so-qobmbu9zmvyf1t4ops.png

Итак, одна строка, одна колонка. Первый тип блокировки называется ACCESS SHARE. Это наименее запрещающая блокировка. Это означает, что она практически не конфликтует с остальными блокировками.

И если мы хотим эксплицитно определить блокировку, мы запускаем команду «lock table». И она явно заблокирует, т. е. в режиме ACCESS SHARE мы запускаем lock table. И если я запущу PSQL в фоновом режиме, то я запускаю таким образом вторую сессию из моей первой сессии. Т. е. что я сделаю здесь? Я перехожу к другой сессии и говорю ей «покажи мне lockview для данного запроса». И здесь у меня AccessShareLock в этой таблице. Это как раз то, что я запрашивал. И он говорит, что блокировка была присвоена. Очень просто.

j6ujhmnnztgc5qjkoxddvnux5xe.png

Далее, если мы смотрим во вторую колонку, то там ничего нет. Они пустые.

az70hdzyqazow_5irgpuuce4zji.png

И если я запускаю команду «SELECT», то это имплицитный (явный) способ запросить AccessShareLock. Поэтому я выпускаю свою таблицу и запускаю запрос, и запрос возвращает несколько строк. И в одной из строк мы видим AccessShareLock. Таким образом SELECT вызывает AccessShareLock в таблице. И он не конфликтует практически ни с чем, потому что это блокировка низкого уровня.

ktj3w6gqbn4gwuxlwfutrynkrt8.png

Что если я запущу SELECT и у меня будет три разных таблиц? Ранее я запускал только одну таблицу, теперь я запускаю три: pg_class, pg_namespace и pg_attribute.

jc2fnithwb-lik3giz6o1sdltrw.png

И теперь, когда я смотрю на запрос, я вижу 9 AccessShareLocks в трех таблицах. Почему? Синим цветом выделено три таблицы: pg_attribute, pg_class, pg_namespace. Но вы также можете видеть, что все индексы, которые определены через эти таблицы, также имеют AccessShareLock.

И это блокировка, которая практически не конфликтует с другими. А все, что она делает, это просто не дает нам сбросить таблицу, пока мы ее выбираем. Это имеет смысл. Т. е. если мы выбираем таблицу, она в этот момент исчезает, то это неправильно, поэтому AccessShare — это блокировка низкого уровня, которая говорит нам «не удаляйте эту таблицу, пока я работаю». По сути, это все, что она делает.

u0ty8_qyjuu5u5-r-6greycfmtg.png

ROW SHARE — это блокировка немного отличается.

zkgjn3bmn640cob3xvnrow5ow4g.png

Возьмем пример. SELECT ROW SHARE способ блокировки каждой строки по отдельности. Таким образом никто не может удалить их или изменить их, пока мы их смотрим.

4hogharj-bsbodrw2mkrpzjforw.pngИтак, то, что делает SHARE LOCK? Мы видим, что ID транзакции 681 для SELECT«а. И это интересно. Что у нас здесь произошло? Первый раз мы видим номер в поле «Lock». Мы берем ID транзакции, и он говорит, что блокирует ее в эксклюзивном режиме. Все, что он делает, он говорит, что у меня есть строка, которая технически заблокирована где-то в таблице. Но не говорит, где конкретно. Чуть позже мы более подробно это рассмотрим.

junwmv7jwbpxgc4s0qopob7kvhg.png

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

yqfhyrdt8xz_xz9e6ekavwqehta.png

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

88stxwweuhwtfiooq3udq2blchk.png

SHARE EXCLUSIVE — это более длинная блокировка.

dvmv7s-8huuwoslr5rljhu0dstw.png

Это (ANALYZE) команда анализатора, которая будет использоваться.

wkpwm9xmt4m-ie1h13snfrb799a.png

SHARE LOCK — вы можете эксплицитно заблокировать в режиме share.

-oi2linulgdo8lgnqv87txb8ykg.png

Вы можете также создать уникальный индекс. И там вы можете увидеть SHARE LOCK, который является их частью. И он блокирует таблицу и устанавливает на нее блокировку SHARE LOCK.

По умолчанию SHARE LOCK на таблице означает, что другие люди могут читать таблицу, но никто не может ее модифицировать. И именно это происходит, когда вы создаете уникальный индекс.

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

gyybjexmhcgztjcq4h27tmhdocs.png

SHARE ROW EXCLUSIVE — опять ее можно задать эксплицитно (явно).

i7nih9tusd1zcwfomm5fpmy72uc.png

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

5lh1wsisbjbqdpridfgekbv_tus.png

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

pdnzzyuxly0zfcmvjw18vlnbbzy.png

Здесь мы видим различные типы блокировок.

dtzvw4xuw_5-435qnrxqfh4xtvs.png

ACCESS EXCLUSIVE, например, это команда блокировки. Например, если вы делаете CLUSTER table, то это будет означать, что никто не сможет записывать туда. И она блокирует не только саму таблицу, но и индексы также.

1ie8vrwyrky3jvkglu20anyarqs.png

Это вторая страница блокировки ACCESS EXCLUSIVE, где мы видим конкретно, что она блокирует в таблице. Она блокирует отдельные строки таблицы, что достаточно интересно.

Это вся базовая информация, которую я хотел дать. Мы говорили о блокировках, об ID транзакций, говорили о виртуальных ID транзакций, о постоянных ID транзакций.

aalcyvja84ifuy1mkdw2kjjlfps.png

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

Давайте рассмотрим определенные примеры.

f3oxontnqztymukor7gdwl49owc.png

Мы начнем с таблиц и с одной строки в таблице. Когда я вставляю что-то, у меня отображается ExclusiveLock, ID транзакции и ExclusiveLock на таблице.

y0vcji_5zb9ggz98jbosrhrll8a.png

А что будет, если я вставлю еще два ряда? И теперь в нашей таблице три ряда. И я вставил один ряд и получил вот это на выходе. И если я вставляю еще два ряда, что здесь странного? Здесь есть странность, потому что я добавил три ряда к этой таблице, но у меня все еще два ряда в таблице блокировки. И это, по сути, основополагающее поведение Postgres.

Многие думаю, что если в базе данных, вы блокируете 100 рядов, то вам будет необходимо создать 100 вводов блокировок. Если я буду блокировать сразу 1 000 рядов, то тогда мне нужна будет 1 000 таких запросов. И если мне нужно миллион или миллиард заблокировать. Но если мы так будем делать, то это будет не очень хорошо работать. Если вы использовали систему, которая создает вводы блокировки для каждого отдельного ряда, то вы видите, что это сложно. Потому что вам нужно определить сразу таблицу блокировки, которая может переполниться, но Postgres так не делает.

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

pzl-h2nxh9nwwkl_kn2nizfyoj0.png

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

gsevmr4vqpvn1jy1iphqpofwquk.png

А что будет, если я хочу обновить два ряда? И мы видим, что он ведет себя также. Мы проводим в два раза больше обновлений, но точно такое же количество строчек блокировки.

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

qdc1pzbps7xox5wav3_9n6qna_s.png

А если я хочу что-то удалить? Если я удаляю, например, один ряд и у меня все еще есть мои два вводных у блокировки, и даже если я захочу удалить их все, то они все равно там присутствуют.

9ijrnhqlocj_kbj3towy8pbetj4.png

И, например, я хочу вставить 1 000 строчек, а потом или удалить, или добавить 1 000 строчек, то те индивидуальные строки, которые я добавляю или меняю, они не записываются здесь. Они записываются на более низком уровне внутри самого ряда. И во время выступления по MVCC я говорил об этом в деталях. Но очень важно, когда вы анализируете блокировки, убедиться, что у вас блокировка на уровне таблицы и что здесь вы не видите, как ведется запись отдельных рядов.

cnbzyqrrrvr9ydrqyh7tzgtd8zo.png

А как насчет эксплицитной блокировки?

lwzzttmi-cbsglpuhsjlgbdesbi.png

Если я нажму «обновить», то у меня есть два заблокированных ряда. И если выделю их все и нажму «обновить везде», то у меня все равно остаются две записи блокировки.

hs2koz_w3jrxzezq2xkujtjok94.png

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

1d1jam27-cid6s0jm0yflvyyxtm.png

И тоже самое, если мы делаем shared, мы можем делать на все 30 раз.

pdsmblbbrchqmv9d1o-tobqvwik.png

Мы восстанавливаем нашу таблицу, все удаляем, потом снова вставляем один ряд.

wl3tvpiiggfrwwy1yc2iovgevky.png

Еще один вид поведения, который вы видите в Postgres, это очень хорошо известное и желаемое поведение — это то, что вы можете проводить update или select. И вы можете это делать одновременно. И select не блокируют update и тоже самое в обратную сторону. Мы говорим читающему не блокировать того, кто пишет, а тот, кто пишет, не блокировал читателя.

Я вам покажу пример этого. Я сделаю сейчас выбор. Мы потом сделаем INSERT. И вы потом сможете увидеть — 694. Вы сможете увидеть ID транзакции, которая провела эту вставку. И это то, как это работает.

1zt8y1aaadf1ri8x0vikzs-iube.png

И если я сейчас посмотрю на свой бэкенд ID, то он стал — 695.

jrwbejin2jrdcxoronmyffxolyw.png

И я могу увидеть, что 695 появляется в моей таблице.

gkxl-hpyyvujgiwu9c-4q9khj-y.png

И если я провожу обновление здесь вот так, то я получаю другой кейс. В этом случае 695 — эксклюзивная блокировка, а у update такое же поведение, но между ними не возникает конфликта, что достаточно необычно.

И вы можете заметить, что на верху — это ShareLock, а внизу — это ExclusiveLock. И обе транзакции получились.

И нужно послушать мое выступление в MVCC, чтобы понять, как это происходит. Но это иллюстрация того, что вы можете делать это одновременно, т. е. одновременно делать SELECT и UPDATE.

4r0jmwlw1hjbpfa3skdt1oplnh0.png

Давайте мы сбросим и еще раз сделаем одну операцию.

lvtbn511ui9hsih_tbhw9u88_3m.png

Если вы попробуете запустить одновременно два update на одном и том же ряду, то оно заблокируется. И помните, я говорил, что читающий не блокирует писателя, а писатель читателя, но один писатель блокирует другого писателя. Т. е. мы не можем делать так, чтобы два человека одновременно обновляли один и тот же ряд. Нужно ждать, пока один из них закончит.

4e7nyt84khptnikltinuyytuoru.png

И для того, чтобы это проиллюстрировать я посмотрю на Lockdemo таблицу. И мы посмотрим на один ряд. На транзакцию 698.

Мы это обновили до 2-х. 699 — это первое обновление. И оно прошло успешно или оно находится в ожидающей транзакции и ожидает, когда мы подтвердим или отменим.

hhd7sfp2u6wsvktx_jk9hkx3s6o.png

Но посмотрите на другое — 2/51 — это наша первая транзакция, наша первая сессия. 3/112 — это второй запрос, который появился сверху и который поменял это значение на 3. И если вы заметете, то верхний заблокировал сам себя, который 699. Но 3/112 не предоставили блокировку. В колонке Lock_mode написано, что он ожидает. Он ожидает 699. И если вы посмотрите, где 699, он выше. И что сделала первая сессия? Она создала эксклюзивную блокировку на своем собственном транзакционном ID. Это то, как Postgres это делает. Он блокирует собственный транзакционный ID. И если вы хотите ждать, пока кто-то подтвердит или отменит, то нужно ждать, пока есть ожидающаяся транзакция. И поэтому мы можем увидеть странную строчку.

Давайте посмотрим еще раз. Слева мы видим наше процессинговое ID. Во второй колонке мы видим наш виртуальный ID транзакции, а в третьей видим lock_type. Что это означает? По сути, она говорит, что блокирует транзакционный ID. Но заметьте, что во всех рядах внизу написано relation. И поэтому у вас два вида блокировки в таблице. Есть блокировка relation. А также есть блокировка transactionid, где вы мы блокируете самостоятельно, это именно то, что происходит на первом ряду или в самом низу, где transationid, где мы ожидаем, чтобы 699 закончил свою операцию.

Я смотрю, что здесь получается. И здесь одновременно происходят две вещи. Вы смотрите на блокировку по транзакционному ID в первом ряду, которая блокирует сама себя. И она блокирует сама себя, чтобы заставить людей ждать.

Если вы посмотрите на 6-ую строчку, то та же запись, что и первая. И поэтому транзакция 699 блокируется. 700 тоже самоблокируется. И потом в нижнем ряду вы увидите, что мы ждем, когда 699 закончит свою операцию.

29wtgnmrm_0qjcp1vbmabyvj974.png

И в lock_type, tuple вы видите числа.

lvtbn511ui9hsih_tbhw9u88_3m.png

Вы можете увидеть, что это 0/10. И это номер страницы, и также offset этого конкретного ряда.

4e7nyt84khptnikltinuyytuoru.png

И вы видите, что становится 0/11, когда мы обновляем.

29wtgnmrm_0qjcp1vbmabyvj974.png

Но на самом деле — это 0/10, потому что происходит ожидание этой операции. У нас есть возможность посмотреть, что это тот ряд, который я жду, чтобы подтвердить.

nmfg1nwfbtigfsk7tazxnv_3dvq.png

Как только мы его подтвердили и нажали commit, и когда обновление закончилось, это то, что мы получаем снова. Транзакция 700 — это единственная блокировка, она больше никого не ждет, потому что ее закоммитили. Она просто ждет, чтобы транзакция завершилась. Как только 699 заканчивается, мы больше ничего не ждем. И теперь транзакция 700 говорит, что все хорошо, что все блокировки, которые нужны, у нее есть во всех разрешенных таблицах.

c9-3v_j-pdbxdxxhxllwnbsstvs.png

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

6ltuhiwe6vxtf8c0stv_36fwofw.png

Это рекурсивный вид, у которого также есть еще одна секция. И оно потом снова возвращает все вместе. Давайте использовать это.

xqilmdyqzuwhgcn7dlxqeilu9vo.png

Что, если мы сделаем три одновременных обновления и скажем, что ряд сейчас равен трем. И мы поменяем 3 на 4.

lrnv-9bbj9ffaa1jarcva_wxuoa.png

И вот мы видим 4. И транзакционный ID 702.

nirmkosh6lkrca00smkrgfn9dr0.png

И затем я поменяю 4 на 5. А 5 на 6, а 6 на 7. И я выстраиваю в очередь ряд людей, которые будут ожидать того, чтобы эта одна транзакция закончилась.

y02td09hohuuy8kmlxxkle0xb7e.png

И все становится понятным. Какой первый ряд? Это 702. Это транзакционный ID, который изначально задал это значение. А что у меня написано в колонке Granted? У меня есть отметки f. Это те мои обновления, которые (5, 6, 7) не могут быть одобрены, потому что мы ждем, чтобы транзакционный ID 702 закончился. Там у нас есть блокировка транзакционного ID. И получается 5 транзакционных блокировок ID.

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

5mk-sshdoscnrj0thyyzfa1ymmc.png

Это то, как это выглядит. Понятно, что они все ждут 12-ую строчку.

xqilmdyqzuwhgcn7dlxqeilu9vo.png

Это то, что мы видели вот здесь. Вот 0/12.

flzrd8kvuh6x5ljtwdfpm6xrtwi.png

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

mfaemwsvab0hwlvr76jfffjkmdk.png

Вот, что происходит. 702 коммитется. И теперь 703 получает эту блокировку ряда, а потом 704 начинает ждать, когда 703 закоммитется. И 705 тоже этого ждет. И когда все это завершается, то они сами себя зачищают. И я хотел бы указать на то, что все выстраиваются в очередь. И это очень похоже на ситуацию с пробкой, когда все ожидают первую машину. Первая машина остановилась, и все выстраиваются в длинную линию. Потом она двигается, потом следующая машина может проехать вперед и получить свою блокировку и т. д.

7_06bgs1tmeylaqevzchxpjz0oe.png

И если вам это показалось недостаточно сложным, то мы сейчас поговорим с вами о deadlocks. Я не знаю, кто из вас с ними сталкивался. Это достаточно распространенная проблема в системах баз данных. Но deadlocks — это тот случай, когда одна сессия ожидает, чтобы что-то выполнила другая сессия. А в этот момент другая сессия ожидает, чтобы первая сессия выполнила что-то.

И, например, если Иван говорит: «Дай мне что-нибудь», а я говорю: «Нет, я тебе это дам только, если ты мне дашь что-то другое». А он говорит: «Нет, я не дам тебе это, если ты мне не дашь». И мы получаемся в ситуации мертвой блокировки. Я уверен, что Иван так не сделает, но вы понимаете смысл, что у нас два человека хотят что-то получить и они не готовы это отдать, пока другой человек им не отдаст, то что они хотят. И тут нет решения.

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

6ndgv2qloe_yrfyuafzhuo4qsna.png

И мы поставим сейчас две deadlocks. Мы поставим 50 и 80. В первый ряд я проведу обновление с 50 на 50. У меня получится номер транзакции 710.

zxmoxcq5k6ff0shfvox8uaukkhy.png

И затем я поменяю 80 на 81, и 50 на 51.

cbokyhcbkszew0vnwa_b6v7ufz0.png

И вот, как это будет выглядеть. И поэтому у 710 есть блокировка ряда, а 711 ожидает подтверждение. Мы видели это, когда обновляли. 710 — является владельцем нашего ряда. А 711 ожидает, чтобы 710 закончил транзакцию.

w9f7nnisbvvlgsmo3dbc7s8ajwu.png

И там даже написано на каком именно ряде у нас происходит deadlocks. И вот где это начинает становится странным.

naxutndlcu48ucdbloyioa2rjhg.png

Теперь мы обновляем 80 на 80.

3uxywubotj3ihomc4-pwb9sswhe.png

И вот, где начинается deadlocks. 710 ожидает отклика от 711, а 711 ожидает 710. И это нехорошо кончится. И из этого нет выхода. И они будут ожидать отклика друг от друга.

qw7yf0trrnmhhh2f2dxsc7xn8la.png

И это просто все начнет задерживать. И мы этого не хотим.

fcakku67l1is1vydt7sieqgx0o4.png

И в Postgres есть способы замечать, когда это происходит. И когда это происходит, то вы получаете вот такую ошибку. И из этого ясно, что такой-то процесс ожидает SHARE LOCK«а от другого процесса, т. е. который блокируется 711 процессом. А тот процесс ожидал, чтобы был дан SHARE LOCK на такой-то транзакционный ID и был заблокированный таким-то процессом. Поэтому тут ситуация мертвой блокировки.

oitb93bhmzo8481rogb81nkdsne.png

А бывает ли трехсторонний deadlocks? Возможно ли это? Да.

n185_s_ubtaqfm_ppolrxy6msta.png

Мы вбиваем эти числа в таблицу. Мы меняем 40 на 40, мы делаем блокировку.

wjy8xm1tj4tfx6pzuz31fnclln8.png

Меняем 60 на 61, 80 на 81.

dxw1lffe6dzrvcgdjm2euoyd6zq.png

А затем мы меняем 80, а затем — бум!

q9jed8hv_eyk4ic1uvf16vnlttk.png

И 714 теперь ожидает 715. 716-ый 715-го ожидает. И с этим уже ничего не сделать.

--rma5efbtgz-ssdujwlexflwg4.png

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

7scbtqdev7smkaxv1uoiddo2zmo.png

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

unlbeteykdgdz7t5ejy3scv6iqm.png

Следующая проблема — это serializable.

1itunk9jbmanzavuflchgnwlt78.png

Если специальная serializable блокировка.

lu4tstatcimpafrtluux8ugquui.png

И возвращаемся к 719. У него вполне нормальная выдача.

8zzmow7aahjpgbsdauympsia7lw.png

И вы можете нажать, чтобы сделать транзакцию из serializable.

9prgjcm2agwlaiakeodt7og-ajy.png

И вы понимаете, что у вас теперь есть другой вид блокировки SA — это означает serializable.

oz5koibztsax7mzf9xccwh71f6i.png

lu0hutzgjk56vrfsgbfimqf8qok.png

И поэтому у нас есть новый вид блокировки, который называется SARieadLock, который является серийной блокировкой и позволяет вводить серийники.

06eu84daldsy26vjgbxybwzaefi.png

И также вы можете вставлять уникальные индексы.

© Habrahabr.ru