Как базы данных выполняют атомарный ввод-вывод?

Базы данных, такие как Oracle, SQL Server и т. д., очень хороши в обеспечении целостности данных. Если бы я хотел написать хранилище данных, которое, как я знал, либо хранит некоторые данные, либо терпит неудачу (т. е. будет ACID), тогда я бы использовал базу данных, такую ​​​​как MySQL, под ним в качестве фактического хранилища, потому что эти проблемы уже решены. .

Однако, поскольку я не выпускник факультета компьютерных наук, мне остается только гадать, как ACID на самом деле работает на очень низком уровне. Я знаю, что Oracle, например, все время записывает данные в «онлайн-журналы повторов», а затем выполняет «фиксацию» в какой-то момент, когда приложение сигнализирует о том, что транзакция должна быть зафиксирована.

Именно этот этап «фиксации» я хочу рассмотреть и понять. Это просто запись «еще одного байта» на диск или переключение 0 на 1, чтобы сказать, что данная строка была успешно сохранена?


person Neil Barnwell    schedule 09.02.2012    source источник
comment
Лучше подходит для dba.stackexchange.com, так как это не вопрос программирования как таковой.   -  person Oded    schedule 09.02.2012
comment
Я с уважением не согласен. Если бы я администрировал базу данных, тогда все в порядке, но если бы я ее писал, то это вопрос программирования, на который могли бы ответить люди, получившие степень в области разработки программного обеспечения или компьютерных наук и т. д.   -  person Neil Barnwell    schedule 09.02.2012
comment
Справедливо. Кажется, вопрос касается текущих реализаций (т.е. как это делает Oracle), поэтому мой комментарий.   -  person Oded    schedule 09.02.2012
comment
Вы можете взглянуть на исходный код PostgreSQL, чтобы узнать, как они это делают. Судя по всему, код очень чистый и относительно простой для понимания.   -  person a_horse_with_no_name    schedule 09.02.2012
comment
Да, я действительно думал о том, чтобы посмотреть на RavenDB.   -  person Neil Barnwell    schedule 09.02.2012
comment
Вы можете прочитать Atomic Commit в SQLite, в котором объясняется, как это делает SQLite.   -  person John Bartholomew    schedule 09.02.2012
comment
RavenDB, вероятно, не сильно вам поможет, потому что он использует ESENT.dll для обработки транзакций.   -  person Damian Powell    schedule 09.02.2012
comment
@DamianPowell Я не уверен, что это так? ayende.com/blog/4686/raven-munin   -  person Neil Barnwell    schedule 09.02.2012
comment
@DamianPowell - это то, что использовалось изначально. С тех пор Айенде пошел дальше.   -  person Oded    schedule 09.02.2012


Ответы (3)


Я помню, как однажды нашел документацию 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
comment
На самом деле, я думаю, что MS SQL Server, PostgreSQL и Firebird/Interbase являются двоюродными братьями в отношении того, как они реализуют MVCC. Базовая техника была впервые предложена Джимом Старки для того, что должно было в конечном итоге быть выпущено как Interbase, и в основном ориентирована на строки. Oracle использует другой (больше ориентированный на страницы) алгоритм, который запатентован IIRC, таким образом сдерживая конкуренцию. - person Branko Dimitrijevic; 03.03.2012

Общий обзор Oracle:

Каждый сеанс Oracle уникален, и каждый сеанс может иметь 1* активную транзакцию. Когда транзакция начинается, Oracle присваивает ей монотонно возрастающий номер изменения системы (SCN). Когда Oracle обновляет/вставляет/удаляет строки, Oracle блокирует интересующие строки в таблице и поддерживает индекс, обновляя заголовок в записываемых блоках, а также сохраняя «исходные» блоки в пространстве отката (отката) оракула. Oracle также записывает в буфер памяти записи журнала повторов, описывающие изменения, внесенные как в таблицу, так и в блоки индекса, а также в блоки отмены. Обратите внимание, что вносимые изменения вносятся в память, а не непосредственно на диск.

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

При откате Oracle использует информацию в откате (отмене), чтобы отменить сделанные изменения.

Итак, как это реализует ACID:

Атомарность: моя сессия, моя транзакция, либо все, либо ничего. Когда я совершаю коммит, я ничего не могу сделать, пока коммит не завершится.

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

Независимость: Помните те номера системных изменений? Если вы не вносите изменений, Oracle знает, что такое SCN, когда вы запускаете оператор или объявляете курсор. Таким образом, если у вас есть долгосрочный оператор, в котором данные изменяются из-под вас, Oracle проверяет, чтобы получить данные, КАК ОНИ БЫЛИ СОВЕРШЕНЫ, когда ваш оператор начал выполняться. Это контроль согласованности нескольких версий, и это довольно сложно. Oracle не реализует все уровни изоляции, предусмотренные различными стандартами SQL — например, Oracle никогда не разрешает грязное или фантомное чтение.

Долговечность: Буфер журнала повторного выполнения, сбрасываемый на диск, является корневым уровнем устойчивости. Когда файл журнала повторов заполнен, Oracle устанавливает контрольную точку. Этот процесс заставляет Oracle записывать все измененные блоки таблиц и индексов из памяти на диск, независимо от того, были ли они зафиксированы. Если происходит сбой экземпляра и данные в файлах данных содержат незафиксированные изменения, Oracle использует журналы повторов для отката этих изменений, поскольку информация об отмене также содержится в журнале повторов.

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

person Adam Musch    schedule 09.02.2012
comment
При фиксации Oracle гарантирует, что весь буфер журнала, включая SCN для транзакции, был записан на диск... Как он это делает? Это самая суть моего вопроса - начинает ли он записывать содержимое буфера журнала и SCN на диск, а затем делает одну последнюю мелочь, например, устанавливает бит на диске с 0 на 1, чтобы сказать, что все работает нормально? - person Neil Barnwell; 10.02.2012
comment
Он использует вызовы fwrite и fflush (я полагаю, что ядро ​​Oracle все еще находится на C) и полагается на статус, возвращаемый этими вызовами, чтобы узнать, сработало оно или нет. В какой-то момент Oracle должен поверить, что ОС сделала то, о чем она говорит. - person Adam Musch; 13.02.2012

Айенде предложил мне в Твиттере посмотреть Munin, фактический механизм хранения данных, который он использует для RavenDB и Raven MQ.

person Neil Barnwell    schedule 09.02.2012