PostgreSQL在接到语句后,会fork出一个子进程postgres处理。首先会从总控进入事务块环境中,如果没有一个事务信息,调用底层事务处理函数启动一个事务,返回上层。Begin、RollBack、end会改变事务块状态。
事务块实现数据库内部底层事务和终端命令的交互。一般来说,大家所认为的事务对应着数据库理论中的命令概念,反应一个SQL(命令)的执行状态。实际上“事务块”才对应着数据库理论中的“事务”。一个事务块包含着多个事务,所以事务块状态数量要比底层事务的状态数量多得多。
执行一条SQL语句前会调用:StratTransactionCommand。执行结束调用CommitTransactionCommand,如果命令执行失败,调用AbortCurrentTransaction。这三个函数根据事务块的状态,执行不同的操作,调用不同的底层事务执行函数。
默认情况下,PostgreSQL中一个事务处理一条SQL语句,事务块使得一个事务能够处理多条SQL语句。每一个Postgres进程中,至始至终只存在一个事务块,当用户开始一个新的事务时,并不进入新的事务块,而是修改事务块的状态使之重新开始记录用户“事务”的状态。
事务块状态是指整个事务系统的状态,反应当前用户输入命令的变化过程和事务底层执行状态的变化。Begin、RollBack、end会改变事务块状态,底层事务提交或者终止也会改变该状态。事务块状态:TBlockState
任何语句通过事务块入口函数进入并执行,执行完毕后,通过事务块出口函数退出。
BeginTransactionBlock (Begin语句执行)
执行begin命令,会进入事务块,此时调用该函数,将事务块状态从 TBLOCK_STARTED 到 TBLOCK_BEGIN,如果遇到其他状态都是不合理的。
EndTransactionBlock (End语句执行/commit)
接受到end命令,调用该函数,事务块结束。可能由commit导改致,也可能是rollback,根据返回值来确定系统动作。
该函数调用:判断当前事务块状态。如果事务块当前状态TBLOCK_INPROGRESS则把当前事务块状态改为TBLOCK_END,返回值设为TRUE;如果当前事务失败,则事务块状态为TBLOCK_ABORT,则需要退出该事务块,事务块状态设置为TBLOCK_ABORT_END,返回false。
chazhangtu
总结:事务块状态
事务块状态 | 标志 |
---|---|
默认 | TBLOCK_DEFAULT |
已开始 | TBLOCK_STARTED |
事务块开启 | TBLOCK_BEGIN |
事务块运行中 | TBLOCK_INPROGRESS |
事务块结束 | TBLOCK_END |
回滚 | TBLOCK_ABORT |
回滚结束 | TBLOCK_ABORT_END |
回滚等待 | TBLOCK_ABORT_PENDING |
在无异常情形下,一个事务块的状态按照默认(TBLOCK_DEFAULT)->已开始(TBLOCK_STARTED)->事务块开启(TBLOCK_BEGIN)->事务块运行中(TBLOCK_INPROGRESS)->事务块结束(TBLOCK_END)->默认(TBLOCK_DEFAULT)循环。剩余的状态机是在上述正常场景下的各个状态点的异常处理分支。
(1) 在进入事务块运行中(TBLOCK_INPROGRESS)之前出错,因为事务还没有开启,直接报错并回滚,清理资源回到默认(TBLOCK_DEFAULT)状态。
(2) 在事务块运行中(TBLOCK_INPROGRESS)出错分为2种情形。事务执行失败:事务块运行中(TBLOCK_INPROGRESS)->回滚(TBLOCK_ABORT)->回滚结束(TBLOCK_ABORT_END)->默认(TBLOCK_DEFAULT);用户手动回滚执行成功的事务:事务块运行中(TBLOCK_INPROGRESS)->回滚等待(TBLOCK_ABORT_PENDING)->默认(TBLOCK_DEFAULT)。
(3) 在用户执行COMMIT语句时出错:事务块结束(TBLOCK_END)->默认(TBLOCK_DEFAULT)。由图2可以看出,事务开始后离开默认(TBLOCK_DEFAULT)状态,事务完全结束后回到默认(TBLOCK_DEFAULT)状态。
(4) 隐式事务块,当客户端执行单条SQL语句时可以自动提交,其状态机相对比较简单:按照默认(TBLOCK_DEFAULT)->已开始(TBLOCK_STARTED)->默认(TBLOCK_DEFAULT)循环。
底层事务的状态:也就是事务块中一条sql语句真正的状态:
/*
* transaction states - transaction state from server perspective
*/
typedef enum TransState
{
TRANS_DEFAULT, /* idle */
TRANS_START, /* transaction starting */
TRANS_INPROGRESS, /* inside a valid transaction */
TRANS_COMMIT, /* commit in progress */
TRANS_ABORT, /* abort in progress */
TRANS_PREPARE /* prepare in progress */
} TransState;
由于子事务的引入,一个事务中可能会有多个层级的子事务。pg使用一个事务栈来保存每个层级子事务的状态,这个事务栈的结构体是 TransactionStateData:
typedef struct TransactionStateData
{
FullTransactionId fullTransactionId; /* my FullTransactionId */
SubTransactionId subTransactionId; /* my subxact ID */
char *name; /* savepoint name, if any */
int savepointLevel; /* savepoint level */
TransState state; /* low-level state */
TBlockState blockState; /* high-level state 事务块状态 */
int nestingLevel; /* transaction nesting depth */
int gucNestLevel; /* GUC context nesting depth */
MemoryContext curTransactionContext; /* my xact-lifetime context */
ResourceOwner curTransactionOwner; /* my query resources */
TransactionId *childXids; /* subcommitted child XIDs, in XID order */
int nChildXids; /* # of subcommitted child XIDs */
int maxChildXids; /* allocated size of childXids[] */
Oid prevUser; /* 记录前一个 CurrentUserId(用户名) */
int prevSecContext; /* previous SecurityRestrictionContext */
bool prevXactReadOnly; /* entry-time xact r/o state */
bool startedInRecovery; /* did we start in recovery? */
bool didLogXid; /* has xid been included in WAL record? */
int parallelModeLevel; /* Enter/ExitParallelMode counter */
bool chain; /* start a new block after this one */
bool assigned; /* assigned to top-level XID */
struct TransactionStateData *parent; /* back link to parent */
} TransactionStateData;
事务实际操作函数由StartTransaction、CommitTransaction、AbortTransaction、CleanupTransaction等函数。
调用该函数一定是顶层事务,子事务启动会调用StartSubTransaction
1)将全局变量CurrentTransactionState指向TopTransactionStateData(一个初始化好的空间)设置底层事务状态state = TRANS_START;
2)重置事务状态变量:
s->nestingLevel = 1;
s->gucNestLevel = 1;
s->childXids = NULL;
s->nChildXids = 0;
s->maxChildXids = 0;
GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext); // 继承context
初始化事务内计数器等
...
3)分配内存和资源跟踪器初始化:AtStart_Memory 和 AtStart_ResourceOwner。前者CurrentTransactionState->curTransactionContext指向内存管理器新分配的内存,后者初始化CurrentTransactionState->curTransactionOwner,里面包括打开的锁、snapshot的引用等。
4)产生事务标识XID给当前事务。
通常只读事务不会申请事务ID,只有涉及写操作时才会分配事务ID。事务会在执行第一个含有写操作的语句时分配事务ID。不过,即使没有事务id,事务也会用一个虚拟事务id来代表自己。虚拟事务id由两部分组成:backendId(后台进程id,会话独有)+ localTransactionId(进程维护的本地事务id,也就是说vxid是在本进程中递增,而不是全局进程递增),以下结构体代码在 lock.h:
typedef struct
{
BackendId backendId; /* backendId from PGPROC */
LocalTransactionId localTransactionId; /* lxid from PGPROC */
} VirtualTransactionId;
backendId是通过MyBackendId继承(与select pg_backend_pid()一一对应);localTransactionId通过GetNextLocalTransactionId进行分配,就是一个全局变量一直+++。
MyBackendId在初始化Postgres进程被赋值:
创建完成之后把localTransactionId插入当前进程队列PGPROC中:
void
VirtualXactLockTableInsert(VirtualTransactionId vxid)
{
Assert(VirtualTransactionIdIsValid(vxid));
LWLockAcquire(&MyProc->fpInfoLock, LW_EXCLUSIVE);
Assert(MyProc->backendId == vxid.backendId);
Assert(MyProc->fpLocalTransactionId == InvalidLocalTransactionId);
Assert(MyProc->fpVXIDLock == false);
MyProc->fpVXIDLock = true;
MyProc->fpLocalTransactionId = vxid.localTransactionId;
LWLockRelease(&MyProc->fpInfoLock);
}
5)设置时间戳
设置事务开始时间=命令开始时间,并初始化事务结束时间=0
6)初始化GUC,cache等结构
7)把事务状态设置为TRANS_INPROGRESS
1)检查事务状态:确保事务状态为TRANS_INPROGRESS
2)触发所有延迟触发器:SQL中after触发器
3)关闭大对象AtEOXact_LargeObject
4)如果改了pg_database、pg_authid、pg_auth_members,则执行更新操作通知Postmaster进程
5)提交事务,TRANS_COMMIT,执行RecordTransactionCommit,将日志写会磁盘,返回最后处理完的当前事务或者子事务的xid
6)清理,略
主要是系统遇到错误时调用,关中断,释放所有资源设置TRANS_ABORT等
略
savepoint,一种机制,用于回滚部分事务。必要的时候可以rollback transaction to语句回滚到该保存点。
具体来说在执行definesavepoint这个函数调用PushTransaction:
保存点涉及到的函数
xact.c:
static void PushTransaction(void); // 为子事务创建一个TransactionState并压入事务状态堆栈中,currentTransactionState切换到新创建的事务状态
static void PopTransaction(void); //出栈
这里PushTransaction会把block的状态改成 TBLOCK_SUBBEGIN;
子事务涉及函数:
static void StartSubTransaction(void);
static void CommitSubTransaction(void);
static void AbortSubTransaction(void);
static void CleanupSubTransaction(void);
在执行savepoint语句时(CommitTransactionCommand)时会调用StartSubTransaction设置blockstate为TBLOCK_SUBINPROGRESS,TBLOCK_SUBBEGIN这里在pushTransaction的时候设置的。
/*
* We were just issued a SAVEPOINT inside a transaction block.
* Start a subtransaction. (DefineSavepoint already did
* PushTransaction, so as to have someplace to put the SUBBEGIN
* state.)
*/
case TBLOCK_SUBBEGIN:
StartSubTransaction();
s->blockState = TBLOCK_SUBINPROGRESS;
break;
通常上层事务在创建过程中可以得到一个XID,用于标志自己,但不对数据库写操作,就没必要单独获得事务或者子事务XID。只有GetCurrentTransactionId函数才分配XID。
根据CurrentTransactionState的vxid决定分配:
GetCurrentTransactionId->AssignTransactionId
注意:子任务分配XID时,会递归分配为父亲分配xid,这样保证子事务的xid大于parent:
AssignTransactionId:
/*
* Ensure parent(s) have XIDs, so that a child always has an XID later
* than its parent. Mustn't recurse here, or we might get a stack
* overflow if we're at the bottom of a huge stack of subtransactions none
* of which have XIDs yet.
*/
if (isSubXact && !FullTransactionIdIsValid(s->parent->fullTransactionId))
{
TransactionState p = s->parent;
TransactionState *parents;
size_t parentOffset = 0;
parents = palloc(sizeof(TransactionState) * s->nestingLevel);
while (p != NULL && !FullTransactionIdIsValid(p->fullTransactionId))
{
parents[parentOffset++] = p;
p = p->parent;
}
/*
* This is technically a recursive call, but the recursion will never
* be more than one layer deep.
*/
while (parentOffset != 0)
AssignTransactionId(parents[--parentOffset]);
pfree(parents);
}
pg只支持分布式数据库的两阶段提交协议,即提供相关操作接口,并没有实现整个协议。
暂不深入
隔离级别,最小实体是元组,对元组需要实施访问控制,通过锁操作和MVCC操作。
s_lock.c
是一种和硬件结合的互斥锁,借用了硬件提供的原子操作的原语来对一些共享变量进行封锁,通常适用于临界区比较小的情况。特点是:封锁时间很短、无死锁检测机制和等待队列、事务结束时不会自动释放SpinLock。
自旋锁顾名思义就是一直原地旋转等待的锁。一个进程如果想要访问临界区,必须先获得锁资源,如果不能获得,就会一直自旋(进程不停止,一直在循环),直到获取到锁资源。
显然这种自旋会造成CPU浪费,但是通常它保护的临界区非常小,封锁时间很短,因此通常自旋比释放CPU资源带来的上下文切换消耗要小。
作为底层锁,利用其来实现LWlock轻量锁
负责保护共享内存中的数据结构,有共享和排他两种模式,类似Oracle中的latch。特点是:封锁时间较短、无死锁检测机制、有等待队列、事务结束时会自动释放。
pg中的轻量锁类型定义在lwlocknames.h文件中(这个文件是在编译时由lwlocknames.txt生成的),在pg 14中,目前有48种轻量锁。
#define ShmemIndexLock (&MainLWLockArray[1].lock)
#define OidGenLock (&MainLWLockArray[2].lock)
#define XidGenLock (&MainLWLockArray[3].lock)
#define ProcArrayLock (&MainLWLockArray[4].lock)
#define SInvalReadLock (&MainLWLockArray[5].lock)
#define SInvalWriteLock (&MainLWLockArray[6].lock)
#define WALBufMappingLock (&MainLWLockArray[7].lock)
#define WALWriteLock (&MainLWLockArray[8].lock)
#define ControlFileLock (&MainLWLockArray[9].lock)
可以看出,各锁被保存在MainLWLockArray数组中(前48个值),每种LWLocks都有自己固定要保护的对象,使用方式如下:
LWLockAcquire(BtreeVacuumLock, LW_SHARED);
LWLockRelease(BtreeVacuumLock);
其定义如下:
typedef struct LWLock
{
uint16 tranche; /* tranche ID */
pg_atomic_uint32 state; /* state of exclusive/nonexclusive lockers */
proclist_head waiters; /* list of waiting PGPROCs */
#ifdef LOCK_DEBUG
pg_atomic_uint32 nwaiters; /* number of waiters */
struct PGPROC *owner; /* last exclusive owner of the lock */
#endif
} LWLock;
State变量有32位,其中低24位作为共享锁的计数器,因此一个轻量锁最多可以有2^24个共享锁持锁者。有1位作为排他锁的标记,因为同一时间最多只能有一个持锁者。
申请:
如果等待队列中第一个申请的是排他锁,则只有这一个申请者被唤醒,其他申请者继续等待。
如果等待队列中第一个申请的是共享锁,则所有共享锁申请者都被唤醒,其他排他锁申请者继续等待。
常规锁(Regular Lock):就是通常说的对数据库对象的锁。按照锁粒度,可以分为表锁、页锁、行锁等;按照等级,pg锁一共有8个等级。特点是:封锁时间可以很长、有死锁检测机制和等待队列、事务结束时会自动释放。
我们平时说的表锁、页锁、咨询锁等等(行锁除外),实际上都是常规锁根据不同锁定对象划分的子类。
pg提供两种加regular锁的方法:
/*
* map from lock method id to the lock table data structures
*/
static const LockMethod LockMethods[] = {
NULL,
&default_lockmethod,
&user_lockmethod
};
这是个静态的,postmaster启动时作为,分配一块内存区作为regularlock方法表并初始化。其中 LockMethod 定义:
lock.c
typedef struct LockMethodData
{
int numLockModes;
const LOCKMASK *conflictTab;
const char *const *lockModeNames;
const bool *trace_flag;
} LockMethodData;
lockdefs.h文件
这里可以看到LOCKMODE是当整型用的,而LOCKMASK是当位图用的,所以前面的const LOCKMASK *conflictTab,这个冲突数组其实相当于是个二维数组。
/*
* LOCKMODE is an integer (1..N) indicating a lock type. LOCKMASK is a bit
* mask indicating a set of held or requested lock types (the bit 1<
这里是锁模式(Lockmode):
对应源码:
/*
* These are the valid values of type LOCKMODE for all the standard lock
* methods (both DEFAULT and USER).
*/
/* NoLock is not a lock mode, but a flag value meaning "don't get a lock" */
#define NoLock 0
#define AccessShareLock 1 /* SELECT */
#define RowShareLock 2 /* SELECT FOR UPDATE/FOR SHARE */
#define RowExclusiveLock 3 /* INSERT, UPDATE, DELETE */
#define ShareUpdateExclusiveLock 4 /* VACUUM (non-FULL),ANALYZE, CREATE INDEX CONCURRENTLY */
#define ShareLock 5 /* CREATE INDEX (WITHOUT CONCURRENTLY) */
#define ShareRowExclusiveLock 6 /* like EXCLUSIVE MODE, but allows ROW SHARE */
#define ExclusiveLock 7 /* blocks ROW SHARE/SELECT...FOR UPDATE */
#define AccessExclusiveLock 8 /* ALTER TABLE, DROP TABLE, VACUUM FULL, and unqualified LOCK TABLE */
带exclusive的锁表示在事务执行期间阻止其他任何类型锁作用于词此表,带share的锁表示允许其他用户共享此锁,事务执行期间阻止排他锁的使用。
挨着分析:
下面四个锁是pg特有的锁:
三种基本的锁结构:lock,proclock以及locallock结构。
lock:为了存储每个可锁对象、可以保持锁或者请求锁
prolock:存储进程和锁之间的关系(pg_locks的grant中的f等)
locallock:locallock是对每个backend的锁操作的加速。对于某进程的加锁要求,首先在locallock中查找当前进程是否已获得该锁,已获得就直接引用计数+1。不用LWlock。进对自己进程可见。
三张hashtable
static HTAB *LockMethodLockHash;
static HTAB *LockMethodProcLockHash;
static HTAB *LockMethodLocalHash;
按照上面三个最主要的结构体进行细分(lock.h):
/*
typedef struct LOCK
{
/* hash key */
LOCKTAG tag; 1. /* unique identifier of lockable object */
/* data */
LOCKMASK grantMask; 2. /* bitmask for lock types already granted */
LOCKMASK waitMask; 2. /* bitmask for lock types awaited */
SHM_QUEUE procLocks; /* list of PROCLOCK objects assoc. with lock */
PROC_QUEUE waitProcs; /* list of PGPROC objects waiting on lock */
int requested[MAX_LOCKMODES]; /* counts of requested locks */
int nRequested; /* total of requested[] array */
int granted[MAX_LOCKMODES]; /* counts of granted locks */
int nGranted; /* total of granted[] array */
} LOCK;
typedef struct LOCKTAG
{
uint32 locktag_field1; /* a 32-bit ID field */
uint32 locktag_field2; /* a 32-bit ID field */
uint32 locktag_field3; /* a 32-bit ID field */
uint16 locktag_field4; /* a 16-bit ID field */
uint8 locktag_type; /* see enum LockTagType */
uint8 locktag_lockmethodid; /* lockmethod indicator */
} LOCKTAG;
拆解一下,这是什么东西,怎么将一个需要锁的对象包装成一个唯一的locktag?
主要掉下面的几个宏:
#define SET_LOCKTAG_RELATION(locktag,dboid,reloid)
#define SET_LOCKTAG_RELATION_EXTEND(locktag,dboid,reloid)
#define SET_LOCKTAG_DATABASE_FROZEN_IDS(locktag,dboid)
#define SET_LOCKTAG_PAGE(locktag,dboid,reloid,blocknum)
#define SET_LOCKTAG_TUPLE(locktag,dboid,reloid,blocknum,offnum)
#define SET_LOCKTAG_TRANSACTION(locktag,xid)
#define SET_LOCKTAG_VIRTUALTRANSACTION(locktag,vxid)
...
这里的LOCKTAG中的locktag_type标志被锁对象的类型(可以看到万物都可锁):
typedef enum LockTagType
{
LOCKTAG_RELATION, /* whole relation */
LOCKTAG_RELATION_EXTEND, /* the right to extend a relation */
LOCKTAG_DATABASE_FROZEN_IDS, /* pg_database.datfrozenxid */
LOCKTAG_PAGE, /* one page of a relation */
LOCKTAG_TUPLE, /* one physical tuple */
LOCKTAG_TRANSACTION, /* transaction (for waiting for xact done) */
LOCKTAG_VIRTUALTRANSACTION, /* virtual transaction (ditto) */
LOCKTAG_SPECULATIVE_TOKEN, /* speculative insertion Xid and token */
LOCKTAG_OBJECT, /* non-relation database object */
LOCKTAG_USERLOCK, /* reserved for old contrib/userlock code */
LOCKTAG_ADVISORY /* advisory user locks */
} LockTagType;
这里好奇一下LOCKTAG_RELATION_EXTEND是啥:
typedef int LOCKMASK;
对于一个可加锁的对象,可能有几个不同事物持有或等待锁,我们需要将持有/等待锁和进程联系起来。这些信息应该保存起来PROCLOCK:
typedef struct PROCLOCK
{
/* tag */
PROCLOCKTAG tag; /* unique identifier of proclock object */
/* data */
PGPROC *groupLeader; /* proc's lock group leader, or proc itself */
LOCKMASK holdMask; /* bitmask for lock types currently held */
LOCKMASK releaseMask; /* bitmask for lock types to be released */
SHM_QUEUE lockLink; /* list link in LOCK's list of proclocks */
SHM_QUEUE procLink; /* list link in PGPROC's list of proclocks */
} PROCLOCK;
typedef struct PROCLOCKTAG
{
/* NB: we assume this struct contains no padding! */
LOCK *myLock; /* link to per-lockable-object information */
PGPROC *myProc; /* link to PGPROC of owning backend */
} PROCLOCKTAG;
SHM_QUEUE lockLink:在上一个lock结构体中,记录了一个持有该对象的锁的进程的queue,这里的locklink就是记录本ProcLock在queue的位置;
SHM_QUEUE procLink:同样每一个pgproc对象都有一个proclock链表,这里的proclink就是记录本proclock在pgproc链表的位置。
所以可以这么理解:proclock是pgproc和lock的链接。
proclock的所有者可能有两种:事务(由后端pgproc+该事务的事务id标识)或者会话(由后端pgproc和事务的invalidTransactionId标识)
注意proclock为用户锁和vacuum持有的跨事务锁使用。变量holdmask标识该proclock锁表示的已经分配的锁,如果一个进程正在等待锁,那么该进程和所等待的锁构成的proclock中holdmask为0.
HTAB *LockMethodLocalHash;
每一个后台进程(会话)维护一个它自己感兴趣的每一个锁的本地hash表,每个都有一个hashtable 当事务重复在同一个对象上申请同类型锁时,无须做冲突检测,只要将这个锁记录在本地即可,避免频繁访问主锁表和进程锁表。
typedef struct LOCALLOCK
{
/* tag */
LOCALLOCKTAG tag; /* unique identifier of locallock entry */
/* data */
uint32 hashcode; /* copy of LOCKTAG's hash value */
LOCK *lock; /* associated LOCK object, if any */
PROCLOCK *proclock; /* associated PROCLOCK object, if any */
int64 nLocks; /* 持锁数量 */
int numLockOwners; /* # 锁持有者数量 */
int maxLockOwners; /* allocated size of array */
LOCALLOCKOWNER *lockOwners; /* 锁持有者列表 */
bool holdsStrongLockCount; /* bumped FastPathStrongRelationLocks */
bool lockCleared; /* we read all sinval msgs for lock */
} LOCALLOCK;
标志就不说了:
typedef struct LOCALLOCKTAG
{
LOCKTAG lock; /* identifies the lockable object */
LOCKMODE mode; /* lock mode for this table entry */
} LOCALLOCKTAG;
366页
lock.c文件
(1)空间计算LockShmemSize
#define NLOCKENTS() \
mul_size(max_locks_per_xact, add_size(MaxBackends, max_prepared_xacts))
/* lock hash table */
max_table_size = NLOCKENTS();
size = add_size(size, hash_estimate_size(max_table_size, sizeof(LOCK)));
/* proclock hash table */
max_table_size *= 2;
size = add_size(size, hash_estimate_size(max_table_size, sizeof(PROCLOCK)));
/*
* Since NLOCKENTS is only an estimate, add 10% safety margin.
*/
size = add_size(size, size / 10);
(2)初始化
初始三个hashtable
(3)regularlock加锁
LockAcquire加锁:
LockAcquireResult LockAcquire(const LOCKTAG *locktag,
LOCKMODE lockmode,
bool sessionLock,
bool dontWait);
四个参数:
LOCKTAG :被锁对象唯一标识
LOCKMODE :获得的锁模式
只有三种:
typedef enum LWLockMode
{
LW_EXCLUSIVE,
LW_SHARED,
LW_WAIT_UNTIL_FREE /* A special mode used in PGPROC->lwWaitMode,
* when waiting for lock to become free. Not
* to be used as LWLockAcquire argument */
} LWLockMode;
返回结果
/* Result codes for LockAcquire() */
typedef enum
{
LOCKACQUIRE_NOT_AVAIL, /* lock not available, and dontWait=true */
LOCKACQUIRE_OK, /* lock successfully acquired */
LOCKACQUIRE_ALREADY_HELD, /* incremented count for lock already held */
LOCKACQUIRE_ALREADY_CLEAR /* incremented count for lock already clear */
} LockAcquireResult;
常规锁主要保存在以下4个位置:
本地锁表(LOCALLOCK结构体):对于重复申请的锁进行计数,避免频繁访问主锁表和进程锁表,相当于一层缓存,如果查到了对应锁,也就是这个锁对象和模式已经授予本事务,给本地锁表对应锁增加引用计数即可,这个工作由GrantLockLocal函数完成。
快速路径(fast path):对弱锁的访问保存到本进程,避免频繁访问主锁表和进程锁表
主锁表(LOCK结构体):保存一个锁对象所有相关信息
进程锁表(PROCLOCK结构体):保存一个锁对象中与当前会话(进程)相关的信息
Q:会话 backend 进程 事务这都啥关系
A:前三是一样,包含很多事务?
Q:Lockmode和LWlockmode啥关系啊。。