写点什么

PostgreSQL 出现死锁怎么办?

  • 2022 年 5 月 21 日
  • 本文字数:1994 字

    阅读完需:约 7 分钟

PostgreSQL出现死锁怎么办?

什么是数据库死锁

在操作系统领域当中,死锁指的是两个或者两个以上的进程在运行的过程中,因为争夺共同的访问资源而相互等待阻塞,最终导致进程继无法续执行的一种阻塞现象。那么在数据库领域当中死锁又是怎样的表现形式呢?数据库死锁又会带来怎样的问题呢?


在理解数据库死锁之前,我们先来明确下数据库的锁到底是什么?有过 Java 编程经验的同学都知道,Java 中的锁是为了解决共享数据的并发访问安全问题,防止并发访问导致的共享数据出现错乱。那么在数据库领域,数据库中的锁又是来干什么的呢?实际上在数据库中所也是解决并发问题。假如在同一时刻,可能存在多个事务对同一张表的同一个字段进行数字的加减操作,如果没有任何的控制措施也同样会导致各种各样的数据一致性问题。因此数据库的锁实际上也是为了保证数据一致性的一种手段,对可能存在的并发操作进行控制。


下面以一个例子来进行说明,假设有这样两个事务,事务 A 中包含如下语句:

UPDATE user SET name = '小慕' where id = 1UPDATE product SET price = price * 10 WHERE id = 2
复制代码

事务 B 中包含如下语句:

UPDATE product SET price = price * 100 WHERE id = 2UPDATE user SET name = '小枫' WHERE id = 1
复制代码

如果这两个事务并发执行,那么他们可能存在如下的执行情况,当事务 A 执行的时候,首先运行了查询语句:

UPDATE user SET name = '小慕' where id = 1
复制代码

相当于事务 A 给 id 为 1 的数据行加上了排他锁,但是事务并没有执行完也就是说此时事务 A 持有 user 表的 id 为 1 的排他锁,排他锁的特性就是此时其他事务不能对数据进行删除和修改,因此只有等待事务结束释放锁之后才能重新获取。


此时事务 B 执行更新语句获取了 product 表 id 为 2 的排他锁,接着事务 B 开始执行 user 表的 update 语句,需要获取 user 表的 id 为 1 的排他锁。但是此时事务 A 并未提交,因此事务 A 持有表 user 的 id 为 1 的排他锁,事务 B 只有乖乖阻塞等待事务 A 释放锁。而此时事务 A 执行 update 语句,需要获取 product 的 id 为 2 的排他锁,但是此时事务 B 持有该排他锁,因此也需要等待事务 B 锁释放。


UPDATE product SET price = price * 10 WHERE id = 2
复制代码


事务 A 在等待事务 B 结束释放锁,而事务 B 又在等待事务 A 释放锁,最终陷入了互相等待的情况也就是所谓的死锁。

那么数据库出现死锁又会导致什么问题呢?数据库死锁会导致严重的性能问题,可能平台因为数据库死锁而导致运行缓慢,严重影响用户正常使用业务,因此如果出现数据库死锁情况需要及时发现以及解决。

定位死锁

//先确定数据库有没有死锁情况发生select * from pg_stat_activity where datname = 'product_db';
//查询可能锁了的表的oidselect oid from pg_class where relname='product';
//查询对应的pidselect pid from pg_locks where relation='oid' //上面查询出来的oid
//取消或者终止对应的进程破坏死锁条件select pg_cancel_backend(pid);select pg_terminate_backend(pid);
复制代码

死锁可能原因及解决办法

以上分析了 PostgreSQL 出现死锁后如何定位分析,那么接下来就需要总结分析分析下 PostgreSQL 出现死锁情况的原因以及一般的应对解决办法。


1、索引使用不当导致的死锁问题

索引使用存在问题的话会导致死锁问题,假设在一个数据查询的事务当中,进行数据检索的时候没办法按照 SQL 中的 where 条件进行查询,因此导致了全表扫描,那么此时数据库表的行级锁会上升为表级锁。如果此时有多个未能按照 where 条件进行数据查询的事务存在,那么就容易导致数据库死锁问题。也就是说在数据库表数据量比较大的时候,对应进行数据查询的表没有建立索引或者说索引创建的不合理导致无法通过索引进行数据查询,只能通过全表索引,这样的场景下就容易产生死锁。


如何避免:

在进行数据查询的时候,对应的 SQL 语句不宜太过复杂,也就是说尽量避免多张表的关联查询。


2、不同事务之间的访问顺序问题

当用户 A 访问数据库表 A 时,此时对表 A 加了共享锁,然后又访问数据库表 B。而此时另一个用户 B 访问表 B,对表 B 加了共享锁,然后试图访问表 A。但是用户 A 由于用户 B 已经锁住表 B,它必须等待用户 B 释放表 B 才能继续,同样用户 B 要等用户 A 释放表 A 才能继续,也就是说互相等待对方释放资源,从而导致了死锁的发生。


如何避免:

这种情况在实际项目中遇到的可能比较多,主要还是需要通过控制代码的执行逻辑,避免多表操作时同时锁住多个资源。


避免死锁的建议

(1)如果平台中存在大事务,尽量将其拆分为小事务。因为大事务一般操作的数据库表或者数据都比较多,因此造成死锁或者阻塞的概率就会相对较大。


(2)为数据库表设计合理的索引,尽量避免数据查询时索引未覆盖或者索引失效的情况,因为全表扫描会会导致给表中的数据行上锁,大大增加了数据库产生死锁的概率。


(3)如果业务允许,我们可以尝试将隔离级别调低,比如将隔离级别从 RR 调整为 RC,可以避免掉很多因为 gap 锁造成的死锁。


(4)在我们自己的代码中,尽量以一致的顺序获取对象上的锁,避免事务中 SQL 交互执行,从而降低死锁发生的概率。


发布于: 刚刚阅读数: 4
用户头像

真正的大师永远怀着一颗学徒的心 2018.09.18 加入

InfoQ签约作者、阿里云专家博主,一线大厂高级开发工程师,专注Java后端以及分布式架构,在通往CTO的道路上不断前行。关注公众号:慕枫技术笔记。

评论

发布
暂无评论
PostgreSQL出现死锁怎么办?_数据库_慕枫技术笔记_InfoQ写作社区