【PostgreSQL】PostgreSQL中的锁--行锁
2022-06-13 10:14 abce 阅读(2836) 评论(0) 编辑 收藏 举报
示例环境
1 2 3 4 5 | 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也用与行级锁的基础架构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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的事务完成,才能执行修改。
1 2 | -- second connection SELECT xmin,xmax,txid_current() FROM locktest WHERE c=1 FOR UPDATE ; |
select for udpate查询运行在第二个会话无法完成,在等待第一个事务结束。
pg_locks
可通过查询pg_locks来查看产生的等待和锁信息:
1 2 3 4 5 6 7 8 | 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
1 2 3 4 5 6 7 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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工具来跟踪。结果类似如下:
1 2 3 4 | # 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的数据库中使用热点数据(由多个应用程序客户端连接并行更新的单行或多行)。这种工作负载更适合内存数据库,通常可以与主要业务逻辑分离。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
2017-06-13 dbms_random.seed
2017-06-13 Oracle - 层次查询