闵行网站设计三桥网站建设

当前位置: 首页 > news >正文

闵行网站设计,三桥网站建设,上海公司注册的详细流程,wordpress代码id减1原子性#xff08;“ACID”意义上的#xff09;要求 对于对数据库执行的一系列操作#xff0c;要么一起提交#xff0c;要么全部回滚#xff1b;不允许中间状态。对于现实世界的混乱的代码来说#xff0c;这是天赐之物。 这些更改将被恢复#xff0c;而不是导致生产环境…原子性“ACID”意义上的要求 对于对数据库执行的一系列操作要么一起提交要么全部回滚不允许中间状态。对于现实世界的混乱的代码来说这是天赐之物。 这些更改将被恢复而不是导致生产环境中的错误更改数据然后使其永久损坏。在处理数百万个请求时由于间歇性问题和其他意外状态而中途断开的长尾连接可能会造成不便但不会扰乱您的数据。 Postgres 的实现尤其以很少的开销提供强大的事务语义而闻名。虽然我已经使用它很多年了但我从来没有理解过它。 Postgres 工作得足够可靠以至于我能够将它视为一个黑匣子——非常有用但其内部工作原理却是一个谜。 本文探讨了 Postgres 如何记录其事务、事务如何原子提交以及理解这一切的关键概念1 。 管理并发访问 假设您构建了一个简单的数据库可以读取和写入磁盘上的 CSV 文件。当单个客户端发出请求时它会打开文件读取一些信息然后写回更改。大部分情况都运行良好但有一天您决定使用一项复杂的新功能多客户端支持来增强您的数据库 不幸的是新的实现立即受到问题的困扰当两个客户端几乎同时尝试访问数据时这些问题似乎尤其明显。用户打开 CSV 文件读取、修改和写入一些数据但该更改会立即被尝试执行相同操作的另一个客户端破坏。 两个客户端之间的争用导致数据丢失。 这是并发访问的问题可以通过引入并发控制来解决。有很多简单的解决方案。我们可以确保任何进程在读取或写入文件之前获取文件上的独占锁或者我们可以通过单个流控制点推送所有操作以便它们一次只运行一个。这些解决方法不仅速度慢而且无法扩展以使我们的数据库完全符合 ACID 标准。现代数据库有一个更好的方式MVCC多版本并发控制。 在 MVCC 下语句在内部作为 transaction执行并且不是直接覆盖数据而是创建它的新版本。原始数据仍然可供其他可能需要它的客户端使用并且任何新数据都将保持隐藏状态直到事务提交。客户端不再直接争用并且数据可以安全地保留因为它们不会覆盖彼此的更改。 当事务启动时它会拍摄一个快照来捕获数据库当时的状态。数据库中的每笔事务都是串行应用的 顺序使用全局锁确保只有一个正在被执行 一次确认已提交或中止。快照是一个 完美表示两者之间的数据库状态 事务。 为了避免已删除和隐藏的行没完没了地累积数据库 将通过真空进程或者在某些情况下与其他查询一起发生的可能的“微真空”来删除过时的数据但它们只会执行此操作以获取打开的快照不再需要的信息。 Postgres 使用 MVCC 管理并发访问。让我们看看它是如何工作的。 事务、元组和快照 这是 Postgres 用于表示事务的数据结构来自proc.c
typedef struct PGXACT {TransactionId xid; /* id of top-level transaction currently being* executed by this proc, if running and XID* is assigned; else InvalidTransactionId /TransactionId xmin; / minimal running XID as it was when we were* starting our xact, excluding LAZY VACUUM:* vacuum must not remove tuples deleted by* xid xmin ! /… } PGXACT;事务通过xid 事务或“xact”ID进行标识。作为一项优化Postgres 仅在事务开始修改数据时才为其分配xid 因为只有在此时其他进程才需要开始跟踪其更改。只读事务不需要xid 。 当一个事务启动时‘ xmin ’会立即被设置为所有正在运行的事务中最小的‘ xid ’ 。 真空进程通过获取所有活动事务的xmin 来计算它们需要保留的最小数据边界。 元组生命周期 Postgres 中的数据行通常称为 元组。虽然 Postgres 使用 B 树等常见查找结构来加快检索速度但索引并不存储元组的完整数据集或其任何可见性信息。相反它们存储一个tid 元组 ID可用于从物理存储也称为“堆”检索行。 tid为 Postgres 提供了一个起点它可以开始扫描堆直到找到满足当前快照可见性的元组。 下面是堆元组的 Postgres 实现与索引元组相反索引元组是在索引中找到的结构以及表示其头部信息的其他一些结构来自htup.h和htup_details.h
typedef struct HeapTupleData {uint32 t_len; /
length of *t_data /ItemPointerData t_self; / SelfItemPointer /Oid t_tableOid; / table the tuple came from /HeapTupleHeader t_data; / - tuple header and data / } HeapTupleData;/ referenced by HeapTupleData / struct HeapTupleHeaderData {HeapTupleFields t_heap;… }/ referenced by HeapTupleHeaderData / typedef struct HeapTupleFields {TransactionId t_xmin; / inserting xact ID /TransactionId t_xmax; / deleting or locking xact ID */… } HeapTupleFields;与事务一样元组跟踪其自己的xmin 但在元组的情况下它被记录为表示元组变得可见的第一个事务即创建它的事务。它还跟踪xmax是最后一个 元组可见的事务即 删除它的事务 2 . 使用 xmin 和 xmax 跟踪堆元组的生命周期。 xmin和xmax是内部概念但它们可以在任何 Postgres 表上显示为隐藏列。只需按名称显式选择它们即可

SELECT *, xmin, xmax FROM names;id | name | xmin | xmax

—————————-1 | Hyperion | 27926 | 279282 | Endymion | 27927 | 0快照xmin、xmax 和 xip 这是快照结构来自 snapshot.h
typedef struct SnapshotData {/** The remaining fields are used only for MVCC snapshots, and are normally* just zeroes in special snapshots. (But xmin and xmax are used* specially by HeapTupleSatisfiesDirty.)** An MVCC snapshot can never see the effects of XIDs xmax. It can see* the effects of all older XIDs except those listed in the snapshot. xmin* is stored as an optimization to avoid needing to search the XID arrays* for most tuples./TransactionId xmin; / all XID xmin are visible to me /TransactionId xmax; / all XID xmax are invisible to me //** For normal MVCC snapshot this contains the all xact IDs that are in progress, unless the snapshot was taken during recovery in which case* its empty. For historic MVCC snapshots, the meaning is inverted, i.e.* it contains committed transactions between xmin and xmax.** note: all ids in xip[] satisfy xmin xip[i] xmax*/TransactionId xip;uint32 xcnt; / # of xact ids in xip[] */… }快照的xmin的计算方式与事务的 xmin 相同即创建快照时正在运行的事务中的最低xid 但目的不同。快照xmin是数据可见性的下限。由xid xmin的事务创建的元组对快照可见。 它还定义了一个xmax 它被设置为最后提交的xid加一。 xmax跟踪可见性的上限 xid xmax的事务对于快照不可见。 最后快照定义了*xip 一个包含所有 创建快照时正在活动的事务的xid 。 *xip是需要的因为即使已经存在xmin的可见性边界仍然可能有一些事务已经提交并且xid大于xmin 但也大于正在进行的事务的xid 因此它们不能包含在xmin中。 我们希望xid xmin的所有已提交事务的结果可见但隐藏任何正在运行的事务的结果。 *xip存储创建快照时处于活动状态的事务列表以便我们可以区分哪个是哪个。 针对数据库执行的事务和捕获某个时刻的快照。 开始事务 当执行BEGIN语句时Postgres会执行一些基本的簿记操作但它会尽可能地推迟更昂贵的操作。例如在新事务开始修改数据之前系统将延迟分配“xid”以减少其在其他位置跟踪所带来的开销。 新事务也不会立即获得快照。 当它运行第一个查询时它将 exec_simple_query 在postgres.c中会将一个压入堆栈。即使是简单的SELECT 1;足以触发它 static void exec_simple_query(const char query_string) {…/** Set up a snapshot if parse analysis/planning will need one./if (analyze_requires_snapshot(parsetree)){PushActiveSnapshot(GetTransactionSnapshot());snapshot_set true;}… }创建新快照是机器真正开始发挥作用的地方。这是GetSnapshotData 在procarray.c中 Snapshot GetSnapshotData(Snapshot snapshot) {/* xmax is always latestCompletedXid 1 */xmax ShmemVariableCache-latestCompletedXid;Assert(TransactionIdIsNormal(xmax));TransactionIdAdvance(xmax);…snapshot-xmax xmax; }该函数执行大量初始化操作但正如我们所讨论的它的一些最重要的工作是设置快照的xmin 、 xmax和xip 。其中最简单的是xmax 它是从 postmaster 管理的共享内存中检索的。每个提交的事务都会通知 postmaster 它已提交并且如果xid高于其已持有的值 latestCompletedXid将被更新。 稍后会详细介绍。 注意该函数的责任是在最后一个xid上加一。这并不像递增它那么简单因为 Postgres 中的事务 ID 可能回绕。事务 ID 被定义为一个简单的无符号 32 位整数来自ch
typedef uint32 TransactionId;尽管xid分配很节约如上所述读取不需要分配但吞吐量很大的系统很容易达到 32 位的界限因此系统需要能够回绕到“重置” xid 根据需要顺序。这是由一些预处理器处理的 魔法在transam.h中 #define InvalidTransactionId ((TransactionId) 0) #define BootstrapTransactionId ((TransactionId) 1) #define FrozenTransactionId ((TransactionId) 2) #define FirstNormalTransactionId ((TransactionId) 3)…/
advance a transaction ID variable, handling wraparound correctly / #define TransactionIdAdvance(dest) \do { (dest); \if ((dest) FirstNormalTransactionId) (dest) FirstNormalTransactionId; } while(0)前几个 ID 被保留为特殊标识符因此我们总是跳过这些 ID 并从3开始。 回到GetSnapshotData 我们得到xmin和xip 迭代所有正在运行的事务再次参见 上面的快照解释了它们的作用 /** Spin over procArray checking xid, xmin, and subxids. The goal is to gather all active xids, find the lowest xmin, and try to record* subxids.*/ for (index 0; index numProcs; index) {volatile PGXACT pgxact allPgXact[pgprocno];TransactionId xid;xid pgxact-xmin; / fetch just once //** If the transaction has no XID assigned, we can skip it; it wont have sub-XIDs either. If the XID is xmax, we can also* skip it; such transactions will be treated as running anyway* (and any sub-XIDs will also be xmax)./if (!TransactionIdIsNormal(xid)|| !NormalTransactionIdPrecedes(xid, xmax))continue;if (NormalTransactionIdPrecedes(xid, xmin))xmin xid;/ Add XID to snapshot. /snapshot-xip[count] xid;… }…snapshot-xmin xmin;提交事务 事务通过CommitTransaction 在xact.c中提交。这个函数非常复杂但以下是它的一些重要部分 static void CommitTransaction(void) {…/** We need to mark our XIDs as committed in pg_xact. This is where we durably commit./latestXid RecordTransactionCommit();/** Let others know about no transaction in progress by me. Note that this must be done before releasing locks we hold and after* RecordTransactionCommit./ProcArrayEndTransaction(MyProc, latestXid);… }持久性和 WAL Postgres 完全是围绕持久性的目标而设计的这意味着即使在崩溃或断电等极端事件中已提交的事务也应保持已提交状态。与许多优秀的系统一样它使用预写日志 WAL 或“xlog”来实现这种持久性。所有更改都会写入并刷新到磁盘即使突然终止Postgres 也可以重播它在 WAL 中找到的内容以恢复未写入其数据文件的任何更改。 上面代码片段中的RecordTransactionCommit处理将事务状态更改发送到 WAL static TransactionId RecordTransactionCommit(void) {bool markXidCommitted TransactionIdIsValid(xid);/** If we havent been assigned an XID yet, we neither can, nor do we want to write a COMMIT record./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);/** Now we may update the CLOG, if we wrote a COMMIT record above/if (markXidCommitted)TransactionIdCommitTree(xid, nchildren, children);}… }提交日志 除了 WAL 之外Postgres 还有一个提交日志或 “clog”或“pg_xact”总结了每笔事务并 无论它是提交还是中止。这就是 TransactionIdCommitTree正在执行上面的操作 – 大部分 信息先写到WAL然后 TransactionIdCommitTree遍历并将提交日志中的事务状态设置为“已提交”。 尽管提交日志被称为“日志”但它实际上更像是分布在共享内存和磁盘上的多个页面上的提交状态位图。在现代编程中很少见的节俭示例中事务的状态只能用两位来记录因此我们可以在每个字节中存储四个事务即在标准 8k​​ 页中存储 32,768 个事务。 来自clog.h和clog.c
#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 和提交日志但不必立即刷新fsync因为即使发生崩溃我们也不会丢失任何信息。在崩溃恢复期间Postgres 会注意到未标记的事务并假设它们已中止。 防御性编程 TransactionIdCommitTree 在transam.c中及其实现TransactionIdSetTreeStatus在 clog.c 提交一棵“树”因为提交 可能有 子提交。我不会进入任何子提交 细节但值得注意的是因为 TransactionIdCommitTree不能保证是原子的每个子提交都被记录为单独提交而父提交则被记录为最后一步。当 Postgres 在崩溃后恢复时在读取父记录并确认提交之前子提交记录不会被视为已提交即使它们被标记为已提交。 这又是以原子性的语义系统本来可以成功记录每个子提交但在写入父提交之前就崩溃了。 clog.c中的内容如下 /** Record the final state of transaction entries in the commit log for* all entries on a single page. Atomic only on this page.** Otherwise API is same as TransactionIdSetTreeStatus()*/ static void TransactionIdSetPageStatus(TransactionId xid, int nsubxids,TransactionId subxids, XidStatus status,XLogRecPtr lsn, int pageno) {…LWLockAcquire(CLogControlLock, LW_EXCLUSIVE);/** Set the main transaction id, if any.** If we update more than one xid on this page while it is being written out, we might find that some of the bits go to disk and others dont.* If we are updating commits on the page with the top-level xid that* could break atomicity, so we subcommit the subxids first before we mark* the top-level commit./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);}}/ … then the main transaction */TransactionIdSetStatusBit(xid, status, lsn, slotno);}…LWLockRelease(CLogControlLock); }通过共享内存发出完成信号 将事务记录到提交日志后就可以安全地向系统的其余部分发出其完成的信号。这发生在上面CommitTransaction的第二次调用中进入 procarray.c
void ProcArrayEndTransaction(PGPROC proc, TransactionId latestXid) {/** We must lock ProcArrayLock while clearing our advertised XID, so that we do not exit the set of running transactions while someone* else is taking a snapshot. See discussion in* 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) {…/ Also advance global latestCompletedXid while holding the lock /if (TransactionIdPrecedes(ShmemVariableCache-latestCompletedXid,latestXid))ShmemVariableCache-latestCompletedXid latestXid; }您可能想知道“ProcArray”是什么。与许多其他类似守护进程的服务不同Postgres 使用进程fork模型来处理并发而不是线程。当它接受新连接时Postmaster 会fork一个新后端在postmaster.c中。后端由PGPROC结构在proc.h中表示整个活动进程集在共享内存中跟踪即“ProcArray”。 现在记住当创建快照时我们如何设置它的 xmax到latestCompletedXid 1 通过设置 全局共享内存中的latestCompletedXid改为xid 刚刚提交的事务我们刚刚完成了 结果对从此开始的每个新快照可见 向前指向任何后端。 使用LWLockConditionalAcquire和 LWLockConditionalAcquire 来查看锁获取和释放调用 LWLockRelease 。大多数时候Postgres 是完美的 很高兴让进程并行工作但是有一个 少数地方需要获取锁来避免 竞争这就是其中之一。临近年初 这篇文章我们讨论了 Postgres 中的事务处理 按串行顺序提交或中止一次一个。 ProcArrayEndTransaction获取独占锁以便它可以更新latestCompletedXid而不会被另一个进程否定其工作。 响应客户 在整个过程中客户端一直在同步等待其事务被确认。原子性保证的一部分是不可能出现误报即数据库在事务尚未提交时将其标记为已提交。失败可能发生在很多地方但如果有一个地方客户会发现它并有机会重试或以其他方式解决问题。 可见性检查 我们之前介绍了可见性信息如何存储在堆元组上。 heapgettup 在heapam.c中是负责扫描堆中满足快照可见性标准的元组的方法 static void heapgettup(HeapScanDesc scan,ScanDirection dir,int nkeys,ScanKey key) {…/** advance the scan until we find a qualifying tuple or run out of stuff to scan/lpp PageGetItemId(dp, lineoff);for (;;){/** if current tuple qualifies, return it./valid HeapTupleSatisfiesVisibility(tuple,snapshot,scan-rs_cbuf);if (valid){return;}lpp; /* move forward in this pages ItemId array /lineoff;}… }HeapTupleSatisfiesVisibility是一个预处理器宏 将调用“satisfies”函数例如 HeapTupleSatisfiesMVCC 在tqual.c中 bool HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,Buffer buffer) {…else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))return false;else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,HeapTupleHeaderGetRawXmin(tuple));…/ xmax transaction committed /return false; }XidInMVCCSnapshot根据快照的 xid 进行初始检查以查看元组的xid是否可见 xmin 、 xmax和xip 。这是一个简化的实现显示了对每个来自tqual.c 的检查 static bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot) {/ Any xid xmin is not in-progress /if (TransactionIdPrecedes(xid, snapshot-xmin))return false;/ Any xid xmax is in-progress /if (TransactionIdFollowsOrEquals(xid, snapshot-xmax))return true;…for (i 0; i snapshot-xcnt; i){if (TransactionIdEquals(xid, snapshot-xip[i]))return true;}… }注意与您直观的想法相比该函数的返回值是相反的 false表示xid对快照可见。尽管令人困惑但您可以通过将返回值与调用它的位置进行比较来了解它正在做什么。 确认xid可见后Postgres 使用TransactionIdDidCommit 来自transam.c 检查其提交状态 bool / true if given transaction committed / TransactionIdDidCommit(TransactionId transactionId) {XidStatus xidstatus;xidstatus TransactionLogFetch(transactionId);/** If its marked committed, its committed./if (xidstatus TRANSACTION_STATUS_COMMITTED)return true;… }进一步探索落实 TransactionLogFetch将显示它的工作原理与宣传的一样。它根据给定的事务 ID 计算提交日志中的位置并访问该位置以获取该事务的提交状态。提交的事务是否用于帮助确定元组的可见性。 这里的关键是为了保持一致性提交日志被视为提交状态以及扩展的可见性的规范来源3 。无论 Postgres 是在几小时前成功提交事务还是在服务器刚刚从中恢复的崩溃前几秒都将返回相同的信息。 Hint bits 提示位 上面的HeapTupleSatisfiesMVCC在从可见性检查返回之前还做了一件事 SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,HeapTupleHeaderGetRawXmin(tuple));检查提交日志以查看元组的xmin或 提交xmax事务是一项昂贵的操作。为了避免每次都必须访问它Postgres 将为扫描的堆元组设置称为“提示位”的特殊提交状态标志。后续操作可以检查元组的提示位并保存到提交日志本身。 黑盒子 当我对数据库运行事务时 BEGIN;SELECT * FROM users WHERE email brandurexample.com;INSERT INTO users (email) VALUES (brandurexample.com)RETURNING *;COMMIT;我不会停下来思考发生了什么事。我获得了一个强大的高级抽象以 SQL 的形式我知道它会可靠地工作并且正如我们所见Postgres 在幕后完成了所有繁重的工作。好的软件是一个黑盒子而 Postgres 是一个特别黑的盒子尽管内部结构很容易访问。 感谢Peter Geoghegan耐心地回答了我所有关于 Postgres 事务和快照的业余问题并为我提供了一些查找相关代码的指导。 1警告几句Postgres 源代码相当庞大因此我掩盖了一些细节以使本文更容易理解。它也正在积极开发中因此随着时间的推移其中一些代码示例可能会变得相当过时。 2读者可能会注意到虽然xmin和xmax可以很好地跟踪元组的创建和删除但它们不足以处理更新。为了简洁起见我现在掩盖了更新的工作原理。 3请注意提交日志最终将被截断但只会超出快照的xmin范围因此在必须在 WAL 中进行检查之前可见性检查会短路。 原文地址:How Postgres Makes Transactions Atomic — brandur.org