- Оригинальный адрес:Stability in a Chaotic World: How Postgres Makes Transactions Atomic
- Оригинальный автор:Brandur
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:TanJianCheng
- Корректор:xiaoyusilen mnikn
Стабильность в хаотичном мире: как Postgres делает транзакции атомарными
Атомарность (функция «ACID») указывает, что для последовательности операций с базой данных либо все операции фиксируются вместе, либо все откатываются; никакие промежуточные состояния не допускаются. Это находка для кода, который должен адаптироваться к беспорядочному реальному миру.
Сбои, которые изменяют данные и продолжают ухудшаться, заменяются, и эти изменения отменяются. Когда вы имеете дело с миллионами запросов, периодические проблемы могут привести к прерывистому соединению или какой-либо другой внезапной ситуации, которая вызовет некоторые неудобства, но не нарушит ваши данные.
Хорошо известно, что реализация Postgres обеспечивает надежную семантику транзакций. Хотя я использую его уже много лет, есть вещи, которые я никогда не понимал. Postgres работает настолько хорошо, что мне удобно обращаться с ним как с черным ящиком — он на удивление прост в использовании, но его внутренняя механика неизвестна.
Эта статья представляет собой исследование того, как Postgres поддерживает свои транзакции и фиксирует их атомарно, а также некоторые ключевые концепции, которые дадут нам глубокое понимание его внутреннего устройства.[1].
Управление одновременным доступом
Предположим, вы создаете простую базу данных, которая читает и записывает CSV-файлы на ваш жесткий диск. Когда только один клиент делает запрос, он открывает файл, считывает информацию и записывает информацию. Все отлично работает, и однажды вы решаете укрепить свою базу данных новой и более сложной функцией — мультиклиентской поддержкой!
К сожалению, новая функциональность сразу же столкнулась с проблемами, когда два клиента пытались одновременно манипулировать данными. Конфликт возникает, когда файл CSV читается, изменяется и записывается одним клиентом, если другой клиент пытается сделать то же самое.
Конкуренция за ресурсы между клиентами может привести к потере данных. Это распространенная проблема с одновременным доступом. может быть представленуправление параллелизмомрешать. Было много оригинальных решений. Например, мы можем позволить посетителям устанавливать эксклюзивные блокировки для чтения и записи файлов, или мы можем заставить все доступы проходить через точки управления потоком, чтобы только одна из них могла работать одновременно. Но эти методы не только медленные, но и поскольку они не могут масштабироваться, база данных не может полностью поддерживать функции ACID. Современные базы данных имеют идеальное решение — MVCC (Multi-Version Concurrency Control System).
В MVCC заявлениеделавыполнить внутри. Он создает новую версию без прямой перезаписи данных. Запрашивающие клиенты по-прежнему могут использовать исходные данные, но новые данные будут скрыты до тех пор, пока транзакция не будет зафиксирована. Таким образом, между клиентами не возникает прямого конфликта, а данные больше не подлежат перезаписи и могут безопасно храниться.
Когда транзакция начинает выполняться, база данных создает моментальный снимок состояния базы данных в данный момент. Каждая транзакция в базе данных будет начинаться ссериалвыполнение заказа через глобальную блокировку, чтобы гарантировать, что только одна транзакция может зафиксировать или прервать операцию за раз. Снимки идеально отображают состояние базы данных между двумя транзакциями.
Чтобы избежать накопления удаленных или скрытых строк, база данных в конечном итоге пройдет черезvacuumпрограмма (или, в некоторых случаях, очередь «микропылесосов» с неоднозначными запросами) для очистки устаревших данных, но только тогда, когда данные больше не используются другими снимками.
Давайте посмотрим, как Postgres управляет параллелизмом с MVCC.
Транзакции, кортежи и снимки
Это структура, которую Postgres использует для реализации транзакций (отproc.c):
typedef struct PGXACT
{
TransactionId xid; /* 当前由程序执行的顶级事务 ID
* 如果正在执行且 ID 被赋值;
* 否则是无效事务 ID */
TransactionId xmin; /* 正在运行最小的 XID,
* 除了 LAZY VACUUM:
* vacuum 不移除因 xid >= xmin
* 而被删除的元组 */
...
} PGXACT;
бизнесxid
(или "точный" ID) для отметки. Это оптимизация Postgres, когда транзакция начинает менять данные, Postgres присваиваетxid
дай это. Потому что только тогда другие программы должны начать отслеживать его изменения. Операции только для чтения могут выполняться напрямую без выделенияxid
.
Когда эта транзакция запускается,xmin
Немедленно настроен на выполнение транзакцииxid
минимальное значение. Вакуумный процесс вычисляет нижние границы данных, чтобы они оставалисьxmin
минимальное значение среди всех транзакций.
Кортежи с учетом жизненного цикла
В Postgres данные строки часто связаны скортежСвязанный. В то время как Postgres использует общую структуру поиска, такую как B-дерево, для быстрого получения информации, индекс не хранит полные данные элемента или какую-либо видимую информацию в нем. Вместо этого они хранят определенные строки, которые можно извлечь из физической памяти (также известной как «куча»).tid
(идентификатор кортежа). Постгрес черезtid
В качестве отправной точки куча сканируется до тех пор, пока не будет найден видимый кортеж, удовлетворяющий текущему моментальному снимку.
Это реализовано Postgresкортеж кучи(нетиндексный кортеж), и структура информации его заголовка (отhtup.h
иhtup_details.h
):
typedef struct HeapTupleData
{
uint32 t_len; /* *t_data 的长度 */
ItemPointerData t_self; /* SelfItemPointer */
Oid t_tableOid; /* 唯一标识一个表 */
HeapTupleHeader t_data; /* -> 元组的头部及数据 */
} HeapTupleData;
/* 相关的 HeapTupleData */
struct HeapTupleHeaderData
{
HeapTupleFields t_heap;
...
}
/* 相关的 HeapTupleHeaderData */
typedef struct HeapTupleFields
{
TransactionId t_xmin; /* 插入 xact ID */
TransactionId t_xmax; /* 删除或隐藏 xact ID */
...
} HeapTupleFields;
Подобно транзакциям, кортеж отслеживает свои собственныеxmin
, но это только в определенных случаях кортежа, например, он регистрируется как первая транзакция, в которой кортеж становится видимым. (то есть тот, кто его создал). Он также отслеживаетxmax
как последнийдела, где виден кортеж (то есть тот, который его удалил)[2].
можно использоватьxmin
иxmax
для отслеживания времени жизни кортежей кучи. Несмотря на то чтоxmin
иxmax
являются внутренними понятиями, но они могут отображать скрытые столбцы в любой таблице Postgres. Выберите их явно по имени:
# SELECT *, xmin, xmax FROM names;
id | name | xmin | xmax
----+----------+-------+-------
1 | Hyperion | 27926 | 27928
2 | Endymion | 27927 | 0
Моментальные снимки: xmin, xmax и xip
Это структура реализации моментального снимка (из моментального снимка.h):
typedef struct SnapshotData
{
/*
* 以下字段仅用于 MVCC 快照,和在特定的快照。
* (但 xmin 和 xmax 专门用于 HeapTupleSatisfiesDirty)
*
*
* 一个 MVCC 快照 永远不可能见到 XIDs >= xmax 的事务。
* 除了那些列表中的 snapshot,它会看到时间长的 XIDs 的内容。
* 对于大多数的元组,xmin 被存储起来是个优化的操作,这样避免去搜索 XID 数组。
*
*/
TransactionId xmin; /* id小于xmin的所有事务更改在当前快照中可见 */
TransactionId xmax; /* id大于xmax的所有事务更改在当前快照中可见 */
/*
* 对于普通的 MVCC 快照,它包含了程序中所有的 xact IDs
* 除非在它是空的情况下被使用。
* 对于历史 MVCC 的快照, 这就是刚好相反, 即它包含了在 xmin 和 xmax 中已提交的事务。
*
*
* 注意: 所有在 xip[] 的 ids 都满足 xmin <= xip[i] < xmax
*/
TransactionId *xip; /* 所有正在运行的事务的id列表 */
uint32 xcnt; /* 正在运行的事务的计数 */
...
}
снимокxmin
Вычисляется так же, как и транзакция (т. е. в выполняющейся транзакции,xid
самая низкая сделка), но имеет другую цель.xmin
это самая нижняя граница, на которой видны данные. Кортежиxid < xmin
Условное создание транзакции, видимое для моментальных снимков.
Также определяется какxmax
переменная, которая устанавливается на последнюю совершенную транзакциюxid
+1.xmax
верхний предел видимости данных;xid >= xmax
транзакции не видны снимкам.
Наконец, когда снимок создается, он определяет*xip
как хранилище для всех транзакцийxid
массив .*xip
существует, потому что даже еслиxmin
установлен на видимую границу, могут быть некоторые зафиксированные транзакцииxid
больше, чемxmin
, но и существуетxmin
такжебольше, чем некоторые транзакции в фазе выполненияxid
.
мы надеемся, что любойxid > xmin
Все результаты фиксации транзакций видны, но на самом деле они скрыты. Когда снимок будет создан,*xip
Сохраненный список действительных транзакций может помочь нам идентифицировать каждую транзакцию.
Транзакция — это операция с базой данных, а моментальный снимок предназначен для захвата мгновенной информации о базе данных.
открытая транзакция
когда вы выполняетеBEGIN
Несмотря на то, что Postgres оптимизирует некоторые часто используемые операции, он будет максимально откладывать более дорогостоящие операции. Например, мы не назначаем xid новой транзакции, пока она не начнет изменять данные. Это снижает затраты на его отслеживание в другом месте.
Новые транзакции также не используют моментальный снимок сразу. Когда транзакция выполняет первый запрос,exec_simple_query
(существуетpostgres.c
) поместит его в стек. даже простойSELECT 1;
Выражения также срабатывают:
static void
exec_simple_query(const char *query_string)
{
...
/*
* 如果解析/计划需要,则设置一个快照
*/
if (analyze_requires_snapshot(parsetree))
{
PushActiveSnapshot(GetTransactionSnapshot());
snapshot_set = true;
}
...
}
Создание нового снимка - начальная точка, в которой программа наступает на самом деле загрузка. ЭтоGetSnapshotData
(существуетprocarray.c
):
Snapshot
GetSnapshotData(Snapshot snapshot)
{
/* xmax 总是等于 latestCompletedXid + 1 */
xmax = ShmemVariableCache->latestCompletedXid;
Assert(TransactionIdIsNormal(xmax));
TransactionIdAdvance(xmax);
...
snapshot->xmax = xmax;
}
Эта функция выполняет много работы по инициализации, но, как мы уже говорили, ее основная задача — установить моментальный снимок.xmin
,xmax
,и*xip
. Самый простой из них – установитьxmax
, который можно получить из общего хранилища, управляемого почтмейстером. Почтмейстер уведомляется о каждой совершенной транзакции, иlatestCompletedXid
будет обновлено, еслиxid
выше текущегоxid
значение (подробнее об этом позже).
Следует отметить, что последнийxid
Автоинкремент выполняется функциями. Потому что в Postgres разрешено оборачивать идентификаторы транзакций, поэтому это не так просто, как самоинкремент. Идентификатор транзакции определяется как 32-разрядное целое число без знака (изc.h):
typedef uint32 TransactionId;
несмотря на то чтоxid
Он выделяется по ситуации (как было сказано выше, при чтении данных он не нужен), но большая пропускная способность системы легко доходит до 32-битной границы, поэтому системе необходимоxid
Последовательность «сбрасывается». Это обрабатывается некоторыми препроцессорами (вtransam.h):
#define InvalidTransactionId ((TransactionId) 0)
#define BootstrapTransactionId ((TransactionId) 1)
#define FrozenTransactionId ((TransactionId) 2)
#define FirstNormalTransactionId ((TransactionId) 3)
...
/* 提前一个事务ID变量, 直接操作 */
#define TransactionIdAdvance(dest) \
do { \
(dest)++; \
if ((dest) < FirstNormalTransactionId) \
(dest) = FirstNormalTransactionId; \
} while(0)
Первые несколько идентификаторов зарезервированы как специальные идентификаторы, поэтому мы обычно пропускаем их, начиная с3
Начинать.
назадGetSnapshotData
, перебирая все выполняемые транзакции, мы можем получитьxmin
иxip
(обзорснимоких роль):
/*
* 循环 procArray 查看 xid,xmin,和 subxids。
* 目的是得到所有 active xids,找到最低的 xmin,和试着去记录 subxids。
*
*/
for (index = 0; index < numProcs; index++)
{
volatile PGXACT *pgxact = &allPgXact[pgprocno];
TransactionId xid;
xid = pgxact->xmin; /* fetch just once */
/*
* 如果事务中没有被赋值的 XID,我们可以跳过;
* 对于 sub-XIDs 也同理。如果 XID >= xmax,我们也可以跳过它;
* 这样的事务被认为(任何 sub-XIDs 都将 >= xmax)。
*
*/
if (!TransactionIdIsNormal(xid)
|| !NormalTransactionIdPrecedes(xid, xmax))
continue;
if (NormalTransactionIdPrecedes(xid, xmin))
xmin = xid;
/* 添加 XID 到快照中。 */
snapshot->xip[count++] = xid;
...
}
...
snapshot->xmin = xmin;
совершить транзакцию
Транзакция прошлаCommitTransaction
(существуетxact.c
)представлен. Функция очень сложная, следующий код является более важной частью функции:
static void
CommitTransaction(void)
{
...
/*
* 我们需要去 pg_xact 标记 XIDs 来表示已提交。作为
* 已稳定提交的标记。
*/
latestXid = RecordTransactionCommit();
/*
* 让其他知道没有其他事务在程序中。
* 需要注意的是,这个操作必须在释放锁之前
* 和记录事务提交之前完成。
*/
ProcArrayEndTransaction(MyProc, latestXid);
...
}
Постоянство и WAL
Postgres полностью разработан на основе концепции постоянства. Это позволяет зафиксированным транзакциям оставаться в исходном состоянии даже в случае разрушения извне или потери питания. Как и многие превосходные системы, Postgres используетжурнал упреждающей записи( WAL, или "xlog") для достижения стабильности. Все изменения записываются на диск, и даже для таких вещей, как сбои, Postgres будет искать в WAL и восстанавливать журнал изменений, которые не были записаны в файлы данных.
сверхуRecordTransactionCommit
Во фрагменте кода измените состояние транзакции на WAL:
static TransactionId
RecordTransactionCommit(void)
{
bool markXidCommitted = TransactionIdIsValid(xid);
/*
* 如果目前我们还没有指派 XID,那我们就不能再指派,也不能
* 写入提交记录
*/
if (!markXidCommitted)
{
...
} else {
XactLogCommitRecord(xactStopTimestamp,
nchildren, children, nrels, rels,
nmsgs, invalMessages,
RelcacheInitFileInval, forceSyncCommit,
MyXactFlags,
InvalidTransactionId /* plain commit */ );
....
}
if ((wrote_xlog && markXidCommitted &&
synchronous_commit > SYNCHRONOUS_COMMIT_OFF) ||
forceSyncCommit || nrels > 0)
{
XLogFlush(XactLastRecEnd);
/*
* 如果我们写入一个有关提交的记录,那么可能更新 CLOG
*/
if (markXidCommitted)
TransactionIdCommitTree(xid, nchildren, children);
}
...
}
commit log
Наряду с WAL, Postgres также имеетcommit log(или «засорить» и «pg_xact»). Эта запись отслеживает совершение транзакций, независимо от того, была ли зафиксирована последняя транзакция или нет. вышеTransactionIdCommitTree
реализует этот функционал - сначала пытается записать серию сообщений в WAL, затемTransactionIdCommitTree
В журнале коммитов он будет изменен на «Committed».
Хотя журнал фиксации также называется «журналом», на самом деле это растровое изображение состояния фиксации, разделенное между общей памятью и на диске. В современном программировании редко встретишь такой экономный пример, когда состояние транзакции можно записать, используя всего два байта, и мы можем хранить четыре транзакции на байт, или 32758 на стандартную страницу размером 8 КБ.
#define TRANSACTION_STATUS_IN_PROGRESS 0x00
#define TRANSACTION_STATUS_COMMITTED 0x01
#define TRANSACTION_STATUS_ABORTED 0x02
#define TRANSACTION_STATUS_SUB_COMMITTED 0x03
#define CLOG_BITS_PER_XACT 2
#define CLOG_XACTS_PER_BYTE 4
#define CLOG_XACTS_PER_PAGE (BLCKSZ * CLOG_XACTS_PER_BYTE)
оптимизированный масштаб
Стабильность важна, но производительность также является ключевым элементом философии Postgres. Если транзакция никогда не назначаетxid
, Postgres пропустит WAL и журнал фиксации. Если транзакция будет прервана, мы все равно запишем ее прерванное состояние в WAL и журнал коммитов, но не спешите сбрасывать (синхронизировать) сразу, потому что на самом деле даже в случае сбоя системы мы не потеряем никакой информации. Во время отработки отказа Postgres будет предлагать непомеченные транзакции как прерванные.
защитное программирование
TransactionIdCommitTree
(существуетtransam.c, и его реализацияTransactionIdSetTreeStatus
существуетclog.c) информация о коммите представлена в виде дерева, потому что у пользователя может быть следующая фиксация. Я не буду вдаваться в подробности вторичных коммитов, потому что вторичные коммиты делаютTransactionIdCommitTree
Атомарность не гарантируется, каждый вторичный коммит фиксируется отдельно, а родительский процесс записывается как последняя операция. Когда Postgres восстанавливает данные после сбоя, вторичные записи фиксации не считаются зафиксированными (даже если они помечены как таковые) до тех пор, пока родительская запись не будет прочитана и фиксация не подтверждена.
Опять же, это атомарно; система может успешно зарегистрировать любую вторичную фиксацию, но произойдет сбой, прежде чем она сможет быть записана в родительский процесс.
каксуществуетclog.c
достигнуто:
/*
* 将提交日志中的事务目录的最终状态记录到单个页面上所有目录上。
* 原子只出现在这个页面。
*
* 其他的 API 与 TransactionIdSetTreeStatus() 相同。
*/
static void
TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
TransactionId *subxids, XidStatus status,
XLogRecPtr lsn, int pageno)
{
...
LWLockAcquire(CLogControlLock, LW_EXCLUSIVE);
/*
* 无论什么情况,都设置事务的 id。
*
* 如果我们在写的时候在这个页面上更新超过一个 xid,
* 我们可能发现有些位转到了磁盘,有些则不会。
* 如果我们在更新页面的时候提交了一个破坏原子性的最高级 xid,
* 那么在我们标记最高级的提交之前我们先提交 subxids。
*
*/
if (TransactionIdIsValid(xid))
{
/* Subtransactions first, if needed ... */
if (status == TRANSACTION_STATUS_COMMITTED)
{
for (i = 0; i < nsubxids; i++)
{
Assert(ClogCtl->shared->page_number[slotno] == TransactionIdToPage(subxids[i]));
TransactionIdSetStatusBit(subxids[i],
TRANSACTION_STATUS_SUB_COMMITTED,
lsn, slotno);
}
}
/* ... 然后是主事务 */
TransactionIdSetStatusBit(xid, status, lsn, slotno);
}
...
LWLockRelease(CLogControlLock);
}
Отметить завершенные транзакции с общей памятью
Когда транзакция регистрируется в журнале фиксации, безопасно предупредить остальную часть системы. Это происходит вышеCommitTransaction
Второй звонок (в procarray.c):
void
ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
{
/*
* 当清除我们的 XID时,我们必须锁住 ProcArrayLock
* 这样当别人设置快照的时候,运行的事务已全被清空了。
* 看讨论
* src/backend/access/transam/README.
*/
if (LWLockConditionalAcquire(ProcArrayLock, LW_EXCLUSIVE))
{
ProcArrayEndTransactionInternal(proc, pgxact, latestXid);
LWLockRelease(ProcArrayLock);
}
...
}
static inline void
ProcArrayEndTransactionInternal(PGPROC *proc, PGXACT *pgxact,
TransactionId latestXid)
{
...
/* 也是在持锁的情况下提前全局 latestCompletedXid */
if (TransactionIdPrecedes(ShmemVariableCache->latestCompletedXid,
latestXid))
ShmemVariableCache->latestCompletedXid = latestXid;
}
Вам может быть интересно, что такое «массив proc». В отличие от других серверных процессов, Postgres не использует потоки, а вместо этого использует программу модели разветвления для управления параллелизмом. Когда он принимает новое соединение, Postmaster запускает новый серверный процесс (существуетpostmaster.c
). использоватьPGPROC
структура данных для представления серверного процесса (существуетproc.h
), а набор допустимых процедур можно отслеживать в разделяемой памяти, это «массив процедур».
А теперь вспомним, как мы создали снапшот и поместили егоxmax
Установить какlatestCompletedXid + 1
? Поместив глобальную разделяемую память вlatestCompletedXid
Назначается только что зафиксированной транзакцииxid
, мы делаем его результат видимым для всех новых снимков любого серверного процесса с этого момента.
См. следующие вызовы для получения и освобождения блокировок.LWLockConditionalAcquire
иLWLockRelease
. Большую часть времени Postgres более чем счастлив, когда все программы работают параллельно, но есть места, где необходимо установить блокировки, чтобы избежать конфликтов, и именно тогда они необходимы. В начале статьи мы упомянули, как последовательно фиксируются или прерываются транзакции в Postgres.ProcArrayEndTransaction
требует монопольной блокировки, чтобы при обновленииlatestCompletedXid
Когда не мешают другие программы.
ответить клиенту
На протяжении всего процесса клиент синхронно ожидает подтверждения своей транзакции. Частичная атомарность заключается в том, что вымышленная база данных помечает транзакцию как совершенную, что не является невозможным. Сбои могут возникать во многих местах, но если есть сбой, клиент найдет его и повторит попытку или устранит проблему.
Проверить видимость
Ранее мы говорили о том, как хранить видимую информацию в кортежах кучи.heapgettup
(heapam.c) отвечает за сканирование кучи, чтобы увидеть, соответствует ли она критериям видимости моментального снимка:
static void
heapgettup(HeapScanDesc scan,
ScanDirection dir,
int nkeys,
ScanKey key)
{
...
/*
* 预先扫描直到找到符合的元组
*
*/
lpp = PageGetItemId(dp, lineoff);
for (;;)
{
/*
* if current tuple qualifies, return it.
*/
valid = HeapTupleSatisfiesVisibility(tuple,
snapshot,
scan->rs_cbuf);
if (valid)
{
return;
}
++lpp; /* 这个页面的itemId数组向前移动一个索引 */
++lineoff;
}
...
}
HeapTupleSatisfiesVisibility
это макрос препроцессора, который будет вызывать функцию «удовлетворяет», напримерHeapTupleSatisfiesMVCC
(существуетtqual.c
):
bool
HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
Buffer buffer)
{
...
else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
HeapTupleHeaderGetRawXmin(tuple));
...
/* xmax transaction committed */
return false;
}
иTransactionIdDidCommit
(отtransam.c
):
bool /* true if given transaction committed */
TransactionIdDidCommit(TransactionId transactionId)
{
XidStatus xidstatus;
xidstatus = TransactionLogFetch(transactionId);
/*
* 如果该事务标记提交,那就提交
*/
if (xidstatus == TRANSACTION_STATUS_COMMITTED)
return true;
...
}
Узнайте большеTransactionLogFetch
покажет, как это работает. Он вычисляет позицию в журнале фиксации по заданному идентификатору транзакции и получает статус фиксации в этой транзакции. Используются ли фиксации транзакций для определения видимости кортежа.
Ключом является согласованность, журнал фиксации считается стандартом для статуса фиксации (и расширяемости, видимости)[3]. Одна и та же информация возвращается независимо от того, успешно ли Postgres зафиксировал транзакцию несколько часов назад или сервер только что восстановился после первых нескольких секунд сбоя.
кий бит
Прежде чем вернуться с проверки видимости данных, из приведенного вышеHeapTupleSatisfiesMVCC
Сделайте еще одну вещь:
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
HeapTupleHeaderGetRawXmin(tuple));
Проверьте журнал фиксации, чтобы увидеть кортежxmin
илиxmax
Если транзакция зафиксирована, это дорогостоящая операция. Чтобы избежать обращения к нему каждый раз, Postgres устанавливает специальный флаг состояния фиксации (называемый «битом подсказки») для сканируемого кортежа кучи. Последующие операции могут проверять бит подсказки кучи и сохранять в журнал фиксации.
черная стена коробки
Когда я запускаю транзакцию в базе данных:
BEGIN;
SELECT * FROM users
INSERT INTO users (email) VALUES ('brandur@example.com')
RETURNING *;
COMMIT;
Я не перестаю думать о том, что там происходит. Я получаю сильную высокоуровневую абстракцию (в форме SQL), которая, как я знаю, надежна, и, как мы видим, Postgres делает всю тяжелую работу под капотом. Хорошее программное обеспечение — это черный ящик, а Postgres — особенно черный ящик (несмотря на наличие доступных внутренних интерфейсов).
благодарныйPeter GeogheganТерпеливо ответил на все мои параллельные вопросы о транзакциях и моментальных снимках Postgres, а также дал указания по поиску соответствующего исходного кода.
- 1Несколько советов: исходный код Postgres огромен, поэтому я опустил некоторые детали, чтобы читателю было легче его усвоить. Поскольку Postgres все еще находится в стадии разработки, указанный код может быть устаревшим.
-
2Читатели могут заметить, что
xmin
andxmax
Отлично подходит для отслеживания создания и удаления кортежей, но их недостаточно для обработки обновлений. Ради него я сейчас не буду рассказывать о том, как реализована операция обновления. -
3Обратите внимание, что журнал фиксации в конечном итоге будет усечен, но только после моментального снимка.
xmin
выходит за рамки, поэтому перед проверкой WAL необходимо проверить видимость.
Стабильность в хаотичном мире: как Postgres делает транзакции атомарнымиОпубликован вСан-Франциско16 августа 2017 г.
Найди меня в твиттере@brandurпожалуйста вHacker NewsНапишите свое мнение о. Если статья неверна, пожалуйстаpull request.
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,React,внешний интерфейс,задняя часть,продукт,дизайнЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.