【PostgreSQL】PostgreSQL中的锁--行锁
2022-06-13 10:14 abce 阅读(2677) 评论(0) 编辑 收藏 举报
示例环境
postgres=# create table locktest(c int); CREATE TABLE postgres=# insert into locktest values(1),(2); INSERT 0 2 postgres=#
场景:两个并发事务尝试查询一行记录用于更新。
PostgreSQL在这个场景中使用行锁。行锁与MVCC的实现紧密集成,使用隐藏的xmin、xmax列。xmin和xmax存储事务的id。所有需要行锁的语句修改xmax(即使是select for update)。修改发生在查询返回结果之后,所以为了看到xmax的改变,我们需要运行select for update两次。通常,xmax列用于标记一行记录是否过期:要么被事务彻底移除;要么表示被更新的行版本。但是xmax也用与行级锁的基础架构。
postgres=# begin; BEGIN postgres=*# select xmin,xmax,txid_current(),c from locktest where c=1 for update; xmin | xmax | txid_current | c ------+------+--------------+--- 743 | 0 | 744 | 1 (1 row) postgres=*# select xmin,xmax,txid_current(),c from locktest where c=1 for update; xmin | xmax | txid_current | c ------+------+--------------+--- 743 | 744 | 744 | 1 (1 row) postgres=*#
如果,有语句尝试修改相同的行,它会检查所有尚未完成的事务。必须等到事务id=xmax的事务完成,才能执行修改。
-- second connection SELECT xmin,xmax,txid_current() FROM locktest WHERE c=1 FOR UPDATE;
select for udpate查询运行在第二个会话无法完成,在等待第一个事务结束。
pg_locks
可通过查询pg_locks来查看产生的等待和锁信息:
postgres=# SELECT locktype,transactionid,virtualtransaction,pid,mode,granted,fastpath FROM pg_locks WHERE transactionid=744; locktype | transactionid | virtualtransaction | pid | mode | granted | fastpath ---------------+---------------+--------------------+-------+---------------+---------+---------- transactionid | 744 | 3/4883 | 25981 | ExclusiveLock | t | f transactionid | 744 | 4/522 | 30293 | ShareLock | f | f (2 rows) postgres=#
可以看到,transactionid=744。来看看持有锁的对应的pid和backend id
postgres=*# SELECT id,pg_backend_pid() FROM pg_stat_get_backend_idset() AS t(id) WHERE pg_stat_get_backend_pid(id) = pg_backend_pid(); id | pg_backend_pid ----+---------------- 3 | 25981 (1 row) postgres=*#
每个backend有一个操作系统进程标识符(pid)和PostgreSQL内部标识符(backend id)。PostgreSQL可以处理多个事务,但是锁定只能发生在backend之间,每个backend执行一个事务。内部记录只需要一个虚拟事务标识符:backend id和backend内部的序列号组成的对。
无论锁定的行数有多少,PostgreSQL在pg_locks表中都只有一个相关的锁。查询可能会修改数十亿行,但PostgreSQL不会为冗余锁结构浪费内存。
写线程在它的transactionid上设置ExclusiveLock。所有行级锁等待者都设置了 ShareLock。一旦写线程释放锁,锁管理器就会恢复所有之前锁定的backend锁。
锁释放发生事务提交或者回滚时。
pg_stat_activity
另一个查看锁信息的方式是查看表pg_stat_activity:
postgres=# select pid,backend_xid,wait_event_type,wait_event,state,query FROM pg_stat_activity WHERE pid IN (25981,30293); -[ RECORD 1 ]---+--------------------------------------------------------------------------------------------------------------------------- pid | 25981 backend_xid | 744 wait_event_type | Client wait_event | ClientRead state | idle in transaction query | SELECT id,pg_backend_pid() FROM pg_stat_get_backend_idset() AS t(id) WHERE pg_stat_get_backend_pid(id) = pg_backend_pid(); -[ RECORD 2 ]---+--------------------------------------------------------------------------------------------------------------------------- pid | 30293 backend_xid | 745 wait_event_type | Lock wait_event | transactionid state | active query | select xmin,xmax,txid_current() from locktest where c=1 for update; postgres=#
源代码层研究
可以使用gdb和pt-mpt工具来跟踪。结果类似如下:
# pt-pmp -p 31369 Sat Jul 28 10:10:25 UTC 2018 30 ../sysdeps/unix/sysv/linux/epoll_wait.c: No such file or directory. 1 epoll_wait,WaitEventSetWaitBlock,WaitEventSetWait,WaitLatchOrSocket,WaitLatch,ProcSleep,WaitOnLock,LockAcquireExte
WaitOnLock函数是由等待引起的,该函数在lock.c文件中。 lock表是一个共享的内存hash表。冲突进程睡眠等待storage/lmgr/proc.c中的锁。大多数时候,这些代码由lmgr.c或另外一个锁管理模块调用,而不是直接调用。
pg_stat_activity中列出的锁,也被称作重量级锁,由锁管理器管理。重量级锁也被用于很多高级别的操作。
总结
·避免长时间运行的事务修改频繁更新的行或过多的行
·不要在支持MVCC的数据库中使用热点数据(由多个应用程序客户端连接并行更新的单行或多行)。这种工作负载更适合内存数据库,通常可以与主要业务逻辑分离。