浅析一个postgresql的死锁问题
之前写过一篇文章(浅析一个sql server数据库事务死锁问题),简单分析了一个sql server数据库的死锁问题及索引对执行计划的影响;这里继续分享一个postgresql的死锁问题。
一般来说,数据库死锁问题都是由于两个或多个复杂事务产生了对锁的循环依赖造成的。最简单的例子就是,事务1执行两个update语句:
UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 11111;
UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 22222;
事务2以相反的顺序执行同样的update语句:
UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 22222;
UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 11111;
在高并发的情况下,就容易产生死锁。
其实不光是update+update语句会产生死锁;正如之前文章中提到的例子,insert+select语句也会产生死锁,原因是由于索引的创建方式不一样,导致锁的粒度不一样。而这里要分享的死锁问题是两个insert语句产生的。
问题原型
同样的,抽象问题的原型如下:
有一张学生表,表结构如下(仅有四个字段加id主键约束):
CREATE TABLE public.student (
id int4 NOT NULL,
address varchar(255) NULL,
email varchar(255) NULL,
"name" varchar(255) NULL,
CONSTRAINT student_pkey PRIMARY KEY (id)
);
接下来有两个事务执行批量插入操作,事务1先插入student3,再插入student4,如下:
begin transaction
INSERT INTO public.student(ID, NAME, ADDRESS, EMAIL) VALUES('3','Jack','Dallas, TX','jack@gmail.com');
INSERT INTO public.student(ID, NAME, ADDRESS, EMAIL) VALUES('4','Jack','Dallas, TX','jack@gmail.com');
commit
而事务2先插入student4,再插入student3,如下:
begin transaction
INSERT INTO public.student(ID, NAME, ADDRESS, EMAIL) VALUES('4','Jack','Dallas, TX','jack@gmail.com');
INSERT INTO public.student(ID, NAME, ADDRESS, EMAIL) VALUES('3','Jack','Dallas, TX','jack@gmail.com');
commit
在两个事务并行执行的场景下,有时候会产生死锁问题,具体的错误如下:
SQL Error [40P01]: ERROR: deadlock detected
Detail: Process 55 waits for ShareLock on transaction 569; blocked by process 57.
Process 57 waits for ShareLock on transaction 568; blocked by process 55.
Hint: See server log for query details.
Where: while inserting index tuple (0,14) in relation "student_pkey"
问题原因
后面发现,当上面两个事务(事务1和事务2)交叉并行执行时,就会发生死锁。具体流程是:
事务1先执行完第一条insert语句;接着事务2开始执行,事务2第一条insert语句执行没有问题,执行第二条insert语句时会pending住;这时,事务1继续执行第二条inset语句,死锁就发生了。
那么,为什么事务2执行第二条insert语句会pending住呢?
原因是,事务1先插入了student3,事务2先插入了student4,它们都没有commit,但是由于id上有一个index,index存储会把没有commit的数据也存起来;当事务2执行第二条insert语句时,发现student3在index里面已经有了,于是就等待事务1结束;这时事务1继续执行第二条insert语句,同样的,发现student4在index里面也有了,于是就等待事务2结束;在这种情况下,死锁就产生了。具体可参考下面解释:
A Postgres index stores not only committed data, but also stores data written by ongoing transactions. Postgres will look for the tuple we’re attempting to insert in both the committed and “dirty” (not-yet-committed) sections of the index. https://rcoh.svbtle.com/postgres-unique-constraints-can-cause-deadlock
通过查看数据库lock情况也可以看出,当事务2执行第二条insert语句时pending在获取一个sharelock上。
(事务1和事务2都执行完第一条语句时的lock情况)
(事务2执行完第二条语句时的lock情况)
延申
- 由于JDBC事务是基于连接实现,如果用DBeaver测试上面死锁问题,你可能需要设置成打开一个editor tab就打开一个新的连接,如下:
- postgresql里面死锁的发生可能跟下面几个timeout参数的设置都有关系,请注意: deadlock_timeout lock_timeout statement_timeout
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 搭建prometheus+grafana监控SpringBoot应用入门
- 掘金15W沸点简单分析(二)
- 老生常谈SpringAop日志收集与处理做的工具包
- 线程间通信wait---notify
- Ubuntu安装Oracle Java8
- CentOS7安装Git
- RocketMQ-环境搭建(单master模式-ubuntu版)
- 使用Web.xml配置SpringMvc(同时使用xml配置文件)
- 第七节:Activiti6.0——Task的变量使用
- 第八节:Activiti6.0——启动流程相关
- 第九节:Activiti6.0——ReceiveTask接收信号、中间信号捕获事件和中间消息捕获事件的流程推进
- SpringBoot——全局异常捕获和自定义异常
- RTSP协议视频平台EasyNVR接入到EasyNVS管理平台后无法显示RTMP及RTSP视频流地址问题
- SpringBoot——配置logback日志
- Istio 运维实战系列(2):让人头大的『无头服务』-上