PostgreSQL 随读笔记-事务上
PostgreSQL 随读笔记-事务
7. 事务处理与并发控制
7.1 事务系统简介
- 事务管理器
是事务系统的中枢,本事是一个FSM。通过接受外部系统的命令或者信号,再根据当前事务所处的状态决定事务的下一步执行过程。 - 锁管理器
读阶段采用了MVCC。保证读写互不阻塞,写阶段需要由各种锁保证事务隔离级别。 - 日志
事务提交日志CLOG(记录执行结果和状态)和事务日志XLOG(保持了一定的冗余数据)
PostgreSQL在接到语句后,会fork出一个子进程postgres处理。首先会从总控进入事务块环境中,如果没有一个事务信息,调用底层事务处理函数启动一个事务,返回上层。Begin、RollBack、end会改变事务块状态。
7.2 事务系统上层
事务块实现数据库内部底层事务和终端命令的交互。一般来说,大家所认为的事务对应着数据库理论中的命令概念,反应一个SQL(命令)的执行状态。实际上“事务块”才对应着数据库理论中的“事务”。一个事务块包含着多个事务,所以事务块状态数量要比底层事务的状态数量多得多。
- pg中事务块:DB理论中的事务
- pg中事务:事务块中sql语句
执行一条SQL语句前会调用:StratTransactionCommand。执行结束调用CommitTransactionCommand,如果命令执行失败,调用AbortCurrentTransaction。这三个函数根据事务块的状态,执行不同的操作,调用不同的底层事务执行函数。
7.2.1 事务块状态
默认情况下,PostgreSQL中一个事务处理一条SQL语句,事务块使得一个事务能够处理多条SQL语句。每一个Postgres进程中,至始至终只存在一个事务块,当用户开始一个新的事务时,并不进入新的事务块,而是修改事务块的状态使之重新开始记录用户“事务”的状态。
事务块状态是指整个事务系统的状态,反应当前用户输入命令的变化过程和事务底层执行状态的变化。Begin、RollBack、end会改变事务块状态,底层事务提交或者终止也会改变该状态。事务块状态:TBlockState
7.2.2 事务块操作
任何语句通过事务块入口函数进入并执行,执行完毕后,通过事务块出口函数退出。
- 事务块基本操作
3个:
- StartTransactionCommand
每条语句执行前,都需要执行该函数,通过判断当前事务块状态进入不同的底层事务执行函数。例如当前事务块状态为TBLOCK_DEFAULT,调用当前函数时,启动一个事务设置事务块状态为TBLOCK_STARTED。 - CommitTransactionCommand
每条语句执行后,都需要执行该函数,通过判断当前事务块状态进入不同的底层事务执行函数。例如当前TBLOCK_DEFAULT,调用该函数时说明此事系统没有任何事务信息,由于每个语句都是在事务中执行的,所以调用当前函数处于一个非法状态,打印错误信息并返回。 - AbortCurrentTransaction
系统遇到错误时,会返回调用点执行该函数。假设【事务块】当前TBLOCK_DEFAULT,系统执行遇到错误,返回到sigsetjmp函数,然后调用该函数进行出错处理,如果底层事务的状态是TRANS_DEFAULT,说明系统中没有任何事务信息,不需要进行事务出错处理,直接返回;如果当前底层事务状态为TRANS_START,则退出事务块需要调用AbortTransaction,再CleanupTransaction。
- 事务块状态改变
BeginTransactionBlock (Begin语句执行)
执行begin命令,会进入事务块,此时调用该函数,将事务块状态从 TBLOCK_STARTED 到 TBLOCK_BEGIN,如果遇到其他状态都是不合理的。
EndTransactionBlock (End语句执行/commit)
接受到end命令,调用该函数,事务块结束。可能由commit导改致,也可能是rollback,根据返回值来确定系统动作。
该函数调用:判断当前事务块状态。如果事务块当前状态TBLOCK_INPROGRESS则把当前事务块状态改为TBLOCK_END,返回值设为TRUE;如果当前事务失败,则事务块状态为TBLOCK_ABORT,则需要退出该事务块,事务块状态设置为TBLOCK_ABORT_END,返回false。
- UserAbortTransactionBlock (Rollback语句执行)
执行用户提交的一个rollback命令会进入该函数。
其他错误场景:
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)循环。
7.3 事务系统底层
底层事务的状态:也就是事务块中一条sql语句真正的状态:
7.3.1 事务状态
/** 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;
- TBlockState blockState:就是这里介绍的上层(事务块状态层),本质就是一个状态机,不做实质性的操作;
- TransState state:事务真正状态;
- nestingLevel: 当前事务嵌套级别,顶层为1,开启子事务PushTransaction,子事务的nestingLevel就在父基础上+1;
- curTransactionOwner:指针,记录当前事务占有资源
- 其他:略
7.3.2 事务操作函数
事务实际操作函数由StartTransaction、CommitTransaction、AbortTransaction、CleanupTransaction等函数。
1. 启动事务(StartTransaction)
调用该函数一定是顶层事务,子事务启动会调用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
2. 提交事务(CommitTransaction)
1)检查事务状态:确保事务状态为TRANS_INPROGRESS
2)触发所有延迟触发器:SQL中after触发器
3)关闭大对象AtEOXact_LargeObject
4)如果改了pg_database、pg_authid、pg_auth_members,则执行更新操作通知Postmaster进程
5)提交事务,TRANS_COMMIT,执行RecordTransactionCommit,将日志写会磁盘,返回最后处理完的当前事务或者子事务的xid
6)清理,略
3. 退出事务(AbortTransaction)
主要是系统遇到错误时调用,关中断,释放所有资源设置TRANS_ABORT等
4. 清理事务
略
7.4 事务保存点和子事务
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);}
7.5 两阶段提交
pg只支持分布式数据库的两阶段提交协议,即提供相关操作接口,并没有实现整个协议。
暂不深入
7.6 PostgreSQL的并发控制
隔离级别,最小实体是元组,对元组需要实施访问控制,通过锁操作和MVCC操作。
7.7 PostgreSQL的三种锁
SpinLocl
s_lock.c
是一种和硬件结合的互斥锁,借用了硬件提供的原子操作的原语来对一些共享变量进行封锁,通常适用于临界区比较小的情况。特点是:封锁时间很短、无死锁检测机制和等待队列、事务结束时不会自动释放SpinLock。
自旋锁顾名思义就是一直原地旋转等待的锁。一个进程如果想要访问临界区,必须先获得锁资源,如果不能获得,就会一直自旋(进程不停止,一直在循环),直到获取到锁资源。
显然这种自旋会造成CPU浪费,但是通常它保护的临界区非常小,封锁时间很短,因此通常自旋比释放CPU资源带来的上下文切换消耗要小。
作为底层锁,利用其来实现LWlock轻量锁
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_DEBUGpg_atomic_uint32 nwaiters; /* number of waiters */struct PGPROC *owner; /* last exclusive owner of the lock */
#endif
} LWLock;
State变量有32位,其中低24位作为共享锁的计数器,因此一个轻量锁最多可以有2^24个共享锁持锁者。有1位作为排他锁的标记,因为同一时间最多只能有一个持锁者。
申请:
如果等待队列中第一个申请的是排他锁,则只有这一个申请者被唤醒,其他申请者继续等待。
如果等待队列中第一个申请的是共享锁,则所有共享锁申请者都被唤醒,其他排他锁申请者继续等待。
RegularLock
常规锁(Regular Lock):就是通常说的对数据库对象的锁。按照锁粒度,可以分为表锁、页锁、行锁等;按照等级,pg锁一共有8个等级。特点是:封锁时间可以很长、有死锁检测机制和等待队列、事务结束时会自动释放。
我们平时说的表锁、页锁、咨询锁等等(行锁除外),实际上都是常规锁根据不同锁定对象划分的子类。
1. Regularlock的锁方法
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<<mode* corresponds to a particular lock mode).*/
typedef int LOCKMASK;
typedef int LOCKMODE;
2. RegularLock支持的锁模式
这里是锁模式(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的锁表示允许其他用户共享此锁,事务执行期间阻止排他锁的使用。
挨着分析:
- AccessShareLock:内部锁模式,select自动施加在表上
- AccessExclusiveLock:被alter table、drop table 或者 vacuum full 使用 (最强的锁)
- ShareLock:使用不带concurrently选项对create index语句请求时用该锁对表加锁(也就是会停止该表上的所有dml,vacuum等操作,只允许同一张表select,select for update/for share,create index操作)
- ExclusiveLock:比sharelock多阻塞 sharelock和rowsharelock,即除了阻止dml、vaccum等,同时阻塞在该表上创建索引,阻止select for update/for share操作,只允许查,Q:啥时候用,A:比如向pg_lock系统表插入一个数据,比如去extend FSM等(一般涉及extend)。
下面四个锁是pg特有的锁:
- RowShareLock 锁用于 select for update/for share语句。
- select for update 就是将select出来的行被锁住,要更新;所以避免它们这些行在当前事务结束前被其他事务修改或者删除。所以这里需要阻塞其他update、delete、select for update这些行的事务(注意这里没有insert),直到当前事务结束。同时,如果一个其他事务的update、delete、select for update已经锁住了某个或者某些行,select for update会等到那些事务结束,并且随后锁住并返回更新的行。
- for share类似,只是在每个检索出来的行上要求一个共享锁,而不是一个排他锁?一个共享锁阻塞update、delete、select for update但不阻塞select for share。
【注意这里为啥只和7、8级冲突,保证一致性靠的是在行上标记事务,并非锁,对1级锁来说多了和7级的冲突】
- RowExclusiveLock:insert/update/delete操作
- ShareUpdateExclusiveLock:vacuum,回收已删除行、或update过时的行占用的空间
- ShareRowExclusiveLock:?
3. RegularLock的存储
三种基本的锁结构: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;
4. RegularLock数据结构
按照上面三个最主要的结构体进行细分(lock.h):
1. Lock结构体:为了存储每个可锁对象、可以保持锁或者请求锁
/*
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;
- LOCKTAG 标志唯一被锁对象,是一个hashkey,是key information对应着一个lock item在lock hashtable里面,即描述被锁的是一个什么东西、什么类型、用什么方法锁。
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是啥:
- LOCKMASK grantMask 和 LOCKMASK waitMask(32位);
前者是这个对象已经被加锁的所有类型,后面是这个对象所有正在被等待加锁的锁类型。这两个标志为了判断已持有锁与请求锁是否冲突。
typedef int LOCKMASK;
- SHM_QUEUE procLocks; PROC_QUEUE waitProcs;
下面这两个就是持有该对象锁的proc(进程)队列(信息用PROCLOCK存),和等待该对象锁的proc(进程)队列(信息用PGPROC存)
2. 锁持有者信息描述体
对于一个可加锁的对象,可能有几个不同事物持有或等待锁,我们需要将持有/等待锁和进程联系起来。这些信息应该保存起来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;
- PROCLOCKTAG tag 同样是为了在HTAB的LockMethodProcLockHash中查找:一个被锁对象和一个等待/持有进程标志唯一一个PROCLOCKTAG。
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.
3. 本地锁表
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;
5. Regularlock的主要操作
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;
- sessionlock 表示为会话加锁;如果false是为当前事务申请锁;
- dontwait 表示申请锁是否允许等待
返回结果
/* 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;
- 申请加锁流程:
在pg中,锁获取最重要的函数是LockAcquire(核心是调用LockAcquireExtended),这是非常高频的操作,性能相当重要。因此在LockAcquire中,对封锁的性能做了大量优化。
常规锁主要保存在以下4个位置:
本地锁表(LOCALLOCK结构体):对于重复申请的锁进行计数,避免频繁访问主锁表和进程锁表,相当于一层缓存,如果查到了对应锁,也就是这个锁对象和模式已经授予本事务,给本地锁表对应锁增加引用计数即可,这个工作由GrantLockLocal函数完成。
快速路径(fast path):对弱锁的访问保存到本进程,避免频繁访问主锁表和进程锁表
主锁表(LOCK结构体):保存一个锁对象所有相关信息
进程锁表(PROCLOCK结构体):保存一个锁对象中与当前会话(进程)相关的信息
- 释放:
LockRelease
有唤醒
Q:会话 backend 进程 事务这都啥关系
A:前三是一样,包含很多事务?
Q:Lockmode和LWlockmode啥关系啊。。
PostgreSQL 随读笔记-事务上相关推荐
- Java核心卷Ⅱ(原书第10版)笔记(上)
Java核心卷Ⅱ(原书第10版)笔记(上) 写在最前面,个人认为,卷Ⅱ更适合当手册使用,更多的是讲API的使用,前两章内容比较实际,要是合并到卷一就好了. 文章目录 第1章 Java SE 8 的流库 ...
- JDBC学习笔记——事务、存储过程以及批量处理
2019独角兽企业重金招聘Python工程师标准>>> 1.事务 ...
- PostgreSQL函数(存储过程)----笔记
PostgreSQL函数(存储过程)----笔记 PostgreSQL 函数也称为 PostgreSQL 存储过程. PostgreSQL 函数或存储过程是存储在数据库服务器上并可以使用SQL界面调用 ...
- 《MSSQL2008技术内幕:T-SQL语言基础》读书笔记(上)
索引: 一.SQL Server的体系结构 二.查询 三.表表达式 四.集合运算 五.透视.逆透视及分组 六.数据修改 七.事务和并发 八.可编程对象 一.SQL Server体系结构 1.1 数据库 ...
- oracle 幻影读,索引+事务
这里写目录标题 事务的ACID特性 数据库的事务是什么? 隔离性(Isolation)概念说明 脏读 可重复读 不可重复读:一个事务A,不同时刻读同一数据可能不一样 幻读:事务A,B同时Insert, ...
- 读课文|读笔记|读小说|甚至读漫画|教你如何让书开口说话
以前上课,老师带着我们一起读课文,现在老师会在课件里播放课文朗读的文件:以前得用磁带光盘跟着读英语,学单词,现在人工智能读单词准确又便捷:以前笔记只能用嘴巴读用眼睛看,现在可以用耳朵听还可以分享给别人 ...
- 《GUN Make》文档粗读笔记
前言 在嵌入式工程的编译中,make经常与gcc配合使用,用于对工程进行编译.当然,这只是一份GUN Make文档的阅读笔记,我并不打算在这篇笔记中说太多与文档阅读本身无关的东西,因为我懒.一来我接触 ...
- 2020年Yann Lecun深度学习笔记(上)
2020年Yann Lecun深度学习笔记(上)
- CS231n课程笔记翻译:图像分类笔记(上)
译者注:本文翻译自斯坦福CS231n课程笔记image classification notes,由课程教师Andrej Karpathy授权进行翻译.本篇教程由杜客翻译完成.ShiqingFan对译 ...
最新文章
- python科学计算基础教程pdf下载-用Python做科学计算 pdf版
- leetcode算法题--可获得的最大点数
- DataTransmission:免费薅羊毛,Are you kidding me? 镭速传输 “百日计划”提前大曝光!Raysync传输协议要开放?
- s3c2440启动文件详细分析
- qmc0转换mp3工具_GoldenRecords for Mac(唱片录音转换软件)
- mysql根据月份查询订单销售额
- android 键盘遮盖输入框_Android软键盘遮住输入框的解决方法终极适配
- PaaS安全:降低企业风险的四条规则
- pdf怎么转换成ppt
- CocosCreator中TiledMap简单使用
- 【微信小程序】事件传参与数据同步
- 18个基于Web的代码开发编辑器
- win10两台电脑时间同步
- cpuz测试分数天梯图_2019年CPU单核跑分天梯图V1.22版(190712)
- 爬虫内容保存到txt文件
- u8 客户端修改服务器地址,u8服务器地址怎么修改
- 手机的唯一标识码 php,android手机获取唯一标识的方法
- export PATHONPATH的用法
- HTTP的请求头标签 If-Modified-Since与Last-Modified
- Ubuntu安装Qt失败
热门文章
- 南京邮电大学计算机网络ppt,南京邮电大学计算机网络第三章习题.ppt
- python网络爬虫实战——利用逆向工程爬取动态网页
- 巨人征途的成功 依赖于不处不在的海报
- PRML学习总结(8)——Graphical Models
- 【黑盒测试用例设计】正交试验法
- Jenkins 打包部署 vue项目
- 利用 Ophis 编写 Commodore 64 programs PRG 程序(五)
- 联想小新2019电脑wifi图标不见了
- 下载S1的ROM的地方
- ROS中map,odom坐标系的理解以及acml和robot_pose_ekf的对比和小车漂移方法解决