Я помню, как однажды нашел документацию BerkeleyDB очень полезной для понимания того, как эти реализации могут работать, потому что это была база данных довольно низкого уровня, которая реализовывала транзакции без всей инфраструктуры планирования реляций/запросов.
Не все базы данных (даже те, которые вы упомянули) работают одинаково. Реализация нижнего уровня PostgreSQL сильно отличается от реализации моментальных снимков как Oracle, так и SQL Server, хотя все они основаны на одном и том же подходе (MVCC: управление параллельным выполнением нескольких версий).
Одним из способов реализации свойств ACID является запись всех изменений, которые вы («вы» здесь — некоторые изменения, вносящие изменения) в базу данных, в «журнал транзакций», а также блокировка каждой строки (единицы атомарности), чтобы гарантировать никакая другая транзакция не может изменить ее до тех пор, пока вы не зафиксируете или не откатитесь. В конце транзакции, если она зафиксирована, вы просто записываете в журнал запись о том, что вы совершили транзакцию, и снимаете блокировки. Если вы выполняете откат, вам нужно вернуться назад по журналу транзакций, отменив все ваши изменения, поэтому каждое изменение, записанное в файл журнала, содержит «предыдущее изображение» того, как данные выглядели изначально. (На практике он также будет содержать «остаточное изображение», потому что журналы транзакций также воспроизводятся для восстановления после сбоя). Блокируя каждую строку, которую вы изменяете, параллельные транзакции не увидят ваши изменения, пока вы не снимете блокировки после завершения транзакции.
MVCC — это метод, с помощью которого параллельные транзакции, которые хотят читать строки, а не блокируются вашим обновлением, вместо этого могут получить доступ к «изображению до». Каждая транзакция имеет идентификатор и способ определить, какие данные транзакций она может «видеть», а какие нет: разные правила для создания этого набора используются для реализации разных уровней изоляции. Таким образом, чтобы получить семантику «повторяемого чтения», транзакция должна найти «изображение до» для любой строки, которая была обновлена транзакцией, которая была запущена после нее, например. Вы могли бы наивно реализовать это, заставив транзакции просматривать журнал транзакций в поисках изображений «до», но на практике они хранятся где-то еще: следовательно, в Oracle есть отдельные пространства для повторов и отмен: повтор — это журнал транзакций, параллельные транзакции для использования; SQL Server хранит образы «до» в базе данных tempdb. Напротив, PostgreSQL всегда создает новую копию строки всякий раз, когда она обновляется, поэтому образы «до» живут в самих блоках данных: это имеет некоторые преимущества (фиксация и откат — очень простые операции, нет дополнительного пространства для управления) с компромиссами. (эти устаревшие версии строк должны быть удалены в фоновом режиме).
В случае с PostgreSQL (и это БД, с которой я лучше всего знаком) каждая версия строки на диске имеет некоторые дополнительные свойства, которые транзакции должны проверить, чтобы решить, является ли эта версия строки «видимой» для них. Для простоты предположим, что у них есть «xmin» и «xmax» — «xmin» указывает идентификатор транзакции, которая создала версию строки, «xmax» — (необязательный) идентификатор транзакции, которая ее удалила (что может включать создание новой версии строки для представляют собой обновление строки). Итак, вы начинаете со строки, созданной txn#20:
xmin xmax id value
20 - 1 FOO
а затем txn#25 выполняет update t set value = 'BAR' where id = 1
20 25 1 FOO
25 - 1 BAR
Пока txn#25 не будет завершен, новые транзакции будут знать, что его изменения следует рассматривать как невидимые. Таким образом, транзакция, сканирующая эту таблицу, примет версию "FOO", поскольку ее xmax является невидимой транзакцией.
Если txn#25 откатывается, новые транзакции не будут сразу пропускать его, а рассмотрят, был ли txn#25 зафиксирован или откатан. (PostgreSQL управляет таблицей поиска «состояния фиксации», чтобы обслуживать это, pg_clog
) Поскольку txn#25 откатился, его изменения не видны, поэтому снова используется версия «FOO». (И версия «BAR» пропускается, так как ее транзакция xmin невидима)
Если txn#25 зафиксирован, то версия строки "FOO" теперь не берется, поскольку ее транзакция xmax видна (то есть теперь видны изменения, сделанные этой транзакцией). Напротив, версия строки "BAR" выбирается, поскольку ее транзакция xmin видна (и у нее нет xmax).
Пока txn#25 все еще выполняется (опять же, это можно прочитать из pg_clog
), любая другая транзакция, которая хочет обновить строку, будет ждать завершения txn#25, пытаясь установить общую блокировку идентификатора транзакции эм>. Я подчеркиваю этот момент, именно поэтому PostgreSQL обычно не имеет «блокировок строк» как таковых, а только блокировки транзакций: нет блокировки в памяти для каждой измененной строки. (Блокировка с использованием select ... for update
выполняется путем установки xmax и флага, указывающего, что xmax просто указывает на блокировку, а не на удаление)
Oracle... делает что-то похожее, но мои знания о деталях гораздо туманнее. В Oracle каждой транзакции присваивается номер изменения системы, который записывается в начале каждого блока. Когда блок изменяется, его исходное содержимое помещается в пространство отмены, при этом новый блок указывает на старый блок: таким образом, у вас, по сути, есть связанный список версий блока N — последней версии в файле данных с более старыми версиями. в табличном пространстве отмены. И в верхней части блока находится список «заинтересованных транзакций», который каким-то образом реализует блокировку (опять же, не имея блокировки в памяти для каждой измененной строки), и я не могу вспомнить подробности, кроме этого.
Я считаю, что механизм изоляции моментальных снимков SQL Server во многом похож на механизм Oracle, использующий tempdb для хранения изменяемых блоков, а не выделенного файла.
Надеюсь, этот бессвязный ответ был полезен. Это все по памяти, поэтому возможны большие объемы дезинформации, особенно для реализаций, отличных от postgresql.
person
araqnid
schedule
09.02.2012