Postgresql源码(124)两个事务更新同一行数据时的行为和原理分析

news/2024/7/9 21:47:54 标签: postgresql, 数据库

XactLockTableWait函数、transactionid锁的一些原理和分析

结论

  • 更新行时,会根据xmax拿transactionid锁,等对应的事务结束。
    • 如果结束是回滚,则heap_update继续更新。
    • 如果结束时提交,则heap_update要返回上层ExecUpdate调用EvalPlanQual重新拿到数据再更新(也有可能更新后不符合谓词就看不到了)。

场景

先执行事务1更新3为30,不提交。

再执行事务2更新所有小于10的数。

drop table t81;
create table t81(i int);
insert into t81 select t.i from generate_series(1,5) t(i);

-- 事务745
begin;
update t81 set i = 30 where i = 3;

-- 事务746
begin;
update t81 set i = 100 where i < 10;

事务二的update会卡住等待事务1。

postgres=# select * from pg_locks where pid <> pg_backend_pid() order by pid,locktype;
   locktype    | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction |  pid  |       mode       | granted | fastpath |
      waitstart
---------------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+-------+------------------+---------+----------+-------------------------------
 relation      |        5 |    16389 |      |       |            |               |         |       |          | 3/446              | 29044 | RowExclusiveLock | t       | t        |
 transactionid |          |          |      |       |            |           745 |         |       |          | 3/446              | 29044 | ExclusiveLock    | t       | f        |
 virtualxid    |          |          |      |       | 3/446      |               |         |       |          | 3/446              | 29044 | ExclusiveLock    | t       | t        |
 relation      |        5 |    16389 |      |       |            |               |         |       |          | 4/22               | 29246 | RowExclusiveLock | t       | t        |
 transactionid |          |          |      |       |            |           746 |         |       |          | 4/22               | 29246 | ExclusiveLock    | t       | f        |
 transactionid |          |          |      |       |            |           745 |         |       |          | 4/22               | 29246 | ShareLock        | f       | f        | xxxx-xx-xx 16:53:14.828479+08
 tuple         |        5 |    16389 |    0 |     3 |            |               |         |       |          | 4/22               | 29246 | ExclusiveLock    | t       | f        |
 virtualxid    |          |          |      |       | 4/22       |               |         |       |          | 4/22               | 29246 | ExclusiveLock    | t       | t        |
(8 rows)

分析

事务746的等锁堆栈

...
  
#5  0x00000000009a4c23 in WaitOnLock (locallock=0x1f8d120, owner=0x1f9dbc0) at lock.c:1818
#6  0x00000000009a3961 in LockAcquireExtended (locktag=0x7ffd197ae4f0, lockmode=5, sessionLock=false, dontWait=false, reportMemoryError=true, locallockp=0x0) at lock.c:1082
#7  0x00000000009a2f3f in LockAcquire (locktag=0x7ffd197ae4f0, lockmode=5, sessionLock=false, dontWait=false) at lock.c:740
#8  0x00000000009a181c in XactLockTableWait (xid=740, rel=0x7f29222b20d8, ctid=0x7ffd197ae594, oper=XLTW_Update) at lmgr.c:702
#9  0x00000000004f1453 in heap_update (relation=0x7f29222b20d8, otid=0x7ffd197ae8aa, newtup=0x205baf8, cid=0, crosscheck=0x0, wait=true, tmfd=0x7ffd197ae8d8, lockmode=0x7ffd197ae82c, update_indexes=0x7ffd197ae828) at heapam.c:3316
#10 0x00000000004fdfa3 in heapam_tuple_update (relation=0x7f29222b20d8, otid=0x7ffd197ae8aa, slot=0x205b518, cid=0, snapshot=0x1f98370, crosscheck=0x0, wait=true, tmfd=0x7ffd197ae8d8, lockmode=0x7ffd197ae82c, update_indexes=0x7ffd197ae828) at heapam_handler.c:327
#11 0x000000000077f5a0 in table_tuple_update (rel=0x7f29222b20d8, otid=0x7ffd197ae8aa, slot=0x205b518, cid=0, snapshot=0x1f98370, crosscheck=0x0, wait=true, tmfd=0x7ffd197ae8d8, lockmode=0x7ffd197ae82c, update_indexes=0x7ffd197ae828) at ../../../src/include/access/tableam.h:1535
#12 0x000000000078278f in ExecUpdateAct (context=0x7ffd197ae8b0, resultRelInfo=0x205a4a8, tupleid=0x7ffd197ae8aa, oldtuple=0x0, slot=0x205b518, canSetTag=true, updateCxt=0x7ffd197ae824) at nodeModifyTable.c:2101
#13 0x0000000000782cbb in ExecUpdate (context=0x7ffd197ae8b0, resultRelInfo=0x205a4a8, tupleid=0x7ffd197ae8aa, oldtuple=0x0, slot=0x205b518, canSetTag=true) at nodeModifyTable.c:2322
#14 0x000000000078533d in ExecModifyTable (pstate=0x205a298) at nodeModifyTable.c:3824
#15 0x0000000000746fa6 in ExecProcNodeFirst (node=0x205a298) at execProcnode.c:464
#16 0x000000000073ad27 in ExecProcNode (node=0x205a298) at ../../../src/include/executor/executor.h:273

...

可以看到事务746在等待事务745的transactionid锁。

事务746流程分析

heap_update拿到目标元组的otid和拼好的新元组后

heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, ...)

先把buffer锁上,因为另一个事务已经更新完了,所以buffer锁当前可以拿到。

LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);

然后拿到被修改元组的xmax,万一被别人改了呢?例如这里xwait=745,且745还没提交。

xwait = HeapTupleHeaderGetRawXmax(oldtup.t_data);

注意这里要先放buffer,因为有可能别的事务会修改,后面需要重新锁上拿数据

LockBuffer(buffer, BUFFER_LOCK_UNLOCK);

先把行锁拿到,避免别人正在更新

heap_acquire_tuplock(relation, &(oldtup.t_self), LockTupleNoKeyExclusive, LockWaitBlock, &have_tuple_lock);

在去拿xmax=745的事务锁,确保修改那个事务已经没了

XactLockTableWait(xwait, relation, &oldtup.t_self, XLTW_Update);

这里会加transactionid的ShareLock模式。

  • 746事务自己拿了一个transactionid ExclusiveLock因为自己也更新了数据。

  • 745事务有写入数据,所以745已经拿到transactionid的ExclusiveLock。

  • 746事务去获取745的transactionid ShareLock,开始等锁。

这里等锁就发生了,保证了RC级别的隔离性。

postgres=# select * from pg_locks where pid <> pg_backend_pid() order by pid,locktype;
   locktype    | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction |  pid  |       mode       | granted | fastpath |
      waitstart
---------------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+-------+------------------+---------+----------+-------------------------------
 transactionid |          |          |      |       |            |           745 |         |       |          | 3/446              | 29044 | ExclusiveLock    | t       | f        |
 transactionid |          |          |      |       |            |           746 |         |       |          | 4/22               | 29246 | ExclusiveLock    | t       | f        |
 transactionid |          |          |      |       |            |           745 |         |       |          | 4/22               | 29246 | ShareLock        | f       | f        | xxxx-xx-xx 16:53:14.828479+08
  • 如果事务745发生了提交
    • 那么事务2就不应该更新3这条数据了。代码继续运行会检查oldtup.t_data,确认xmax到底有没有回滚。这里heap_update不会继续进行更新动作了,直接返回TM_Updated。外层函数ExecUpdate收到TM_Updated后,会调用EvalPlanQual重新读取这一行数据,如果还能看到就返回epqslot新元组下面重新更新;如果现在已经看不到这一行了,就返回NULL,这次的更新就结束了。
  • 如果事务745发生了回滚
    • 那么事务2就还能看到3这条数据。代码继续运行检查发现xmax已经回滚了,可以继续更新,所以在heap_update中完成了本次更新,返回TM_Ok。

http://www.niftyadmin.cn/n/5382580.html

相关文章

简化部署流程,提升开发效率:介绍 Electron Egg 打包优化

简化部署流程&#xff0c;提升开发效率&#xff1a;介绍 Electron Egg 打包优化 在开发桌面应用程序时&#xff0c;优化打包流程是至关重要的&#xff0c;可以帮助开发人员节省时间和精力&#xff0c;提高生产力。本文将介绍如何使用 Electron Egg 框架进行打包优化&#xff0…

未来从事鸿蒙开发?是否会有前景?

鸿蒙的未来发展肯定很有前景的&#xff0c;鸿蒙作为新出的国产操作系统。它不仅只是手机操作系统&#xff0c;鸿蒙的出发点就是万物互联。包含原生应用开发、车载、智能设备、数码、智能家居家电等等。如此大的市场分布&#xff0c;岗位需求至少是很多的。 鸿蒙的布局很广&…

【IOS】Xcode 15.2版本下载 iOS_17 Simulator失败

问题 Xcode更新15.2自身不带iOS_17 Simulator模拟器的&#xff0c;然而在使用自带的get下载安装会一直在40进度左右出现因为网络问题安装失败的情况&#xff0c;一般这种情况在断点处重试安装也还好&#xff0c;但是每次重试都是需要重新下载&#xff0c;因为本身下载又很慢&a…

初阶数据结构之---导论,算法时间复杂度和空间复杂度(C语言)

说在整个初阶数据结构开头 数据结构其实也学了挺长时间了&#xff0c;说着是要刷题所以才没怎么去写关于数据结构方面的内容。数据结构作为计算机中及其重要的一环&#xff0c;如果不趁着假期系统整理一下着实可惜&#xff0c;我这里构想的是将初阶数据结构和高阶数据结构&…

Hive切换引擎(MR、Tez、Spark)

Hive切换引擎(MR、Tez、Spark) 1. MapReduce计算引擎(默认) set hive.execution.enginemr;2. Tez引擎 set hive.execution.enginetez;1. Spark计算引擎 set hive.execution.enginespark;

20. 【Linux教程】emacs 编辑器

前面小节介绍了如何使用 vim 编辑器和 nano 编辑器&#xff0c;本小节介绍 emacs 编辑器&#xff0c;emacs 编辑器最开始是作为控制台的编辑器&#xff0c;并且 emacs 编辑器仍然提供最早的命令行模式。 1. 检查 Linux 系统中是否安装 emacs 编辑器 使用如何命令检查 emacs 编…

分布式锁的应用场景及实现

文章目录 分布式锁的应用场景及实现1. 应用场景2. 分布式锁原理3. 分布式锁的实现3.1 基于数据库 分布式锁的应用场景及实现 1. 应用场景 电商网站在进行秒杀、特价等大促活动时&#xff0c;面临访问量激增和高并发的挑战。由于活动商品通常是有限库存的&#xff0c;为了避免…

Golang - 使用CentOS 7 安装Golang环境

文章目录 操作步骤 操作步骤 为在CentOS 7上安装Go语言环境&#xff0c;可以按照以下步骤进行操作&#xff1a; 下载Go语言包&#xff1a; 从官方网站 https://golang.org/dl/ 下载适用于Linux的Go语言包。 解压缩Go语言包&#xff1a; 使用以下命令解压缩下载的Go语言包 […