死锁检测,是pg的一种自动检测机制,可以发现两个或者多个session之间对互斥资源的申请造成的死锁,并且可以随机将其中一个事务回滚掉,以解除死锁。
PG的死锁时间由deadlock_timeout参数控制,默认是1s,当发生死锁超时,会随机将一个事务回滚掉,同时记录到数据库日志中。
deadlock_timeout
死锁检测之前在一个锁上等待的总时间。
This is the amount of time to wait on a lock before checking to see if there is a deadlock condition.
deadlock_timeout设置为1s,也即意味着,锁出现的一秒后,系统才会触发死锁检测机制,如果检测到死锁。会随机将其中任一事务回滚掉。
这个值越大,锁触发死锁检测等待的时间越长。这个值一般要超过大多数事务执行的时间。
特殊情况下,如果我们想定位死锁问题,我们可以先调大这个值,让因死锁而“挂起“的会话,维持“挂起”状态的时间更长点,以便我们定位具体SQL。
当死锁发生后,你可能在服务器日志或者会话报错中看到如下形式的日志信息:
- ERROR: deadlock detected
- DETAIL: Process 18446 waits for ShareLock on transaction 976; blocked by process 18478.
- Process 18478 waits for ShareLock on transaction 975; blocked by process 18446.
- HINT: See server log for query details.
- CONTEXT: while updating tuple (0,2) in relation "t_data"
从上图中的日志信息中可以看到服务器是产生了循环依赖的,Process 18446 waits for process 18478, Process 18478 waits for 18446,process 18446和process 18478都被挂起,无法向前推进。
当死锁发生后,我们可以从pg_stat_activity表中,获取当前挂起的会话中找到对应的client_addr、client_port,以及具体query等信息。
- SELECT client_addr, client_port, query
- FROM pg_stat_activity
- WHERE state = 'active'
- AND wait_event_type = 'Lock'
- ORDER BY state_change
-
一般我们希望能找到最初挂起的会话,因此我们按state_change排序pg_stat_activity,因为其他会话稍后可能卡在最初导致死锁的会话后面。
扩展
pg_stat_activity视图中wait_event_type,这字段表示等待事件的类型。 wait_event_type的取值可为:LWLockNamed、LWLockTranche、Lock、BufferPin。其中Lock表示backend后台进程等待重量级的锁,通常是指 relation、tuple、page、transactionid 等子类型锁。
我们知道死锁会带来很多问题:产生死锁的会话无法继续推进,同时它占用的资源无法释放,时间长了之后可能会导致系统中出现更多的锁,进而导致服务雪崩。所以一般来说,我们是不希望在生产环境中发生死锁的。死锁的检测和解除,如上文所示可以通过设置合理的deadlock_timeout来解决。除此之外,我们使用一些预防策略,来防治死锁的产生。
死锁的预防
死锁只有在锁存在的情况下才会发生,所以缓解死锁的一个策略就是减少锁的使用,比如说避免大事务,尽量减少显式锁的使用,减小锁的粒度等。
一般我们要确保使用数据的所有应用程序,以一致的顺序获取多个对象上的锁,防止出现互相等待的情况。
另外,业务侧也需要实现重试机制,通过重试来恢复因死锁而中止的事务操作。
参考:
https://www.cybertec-postgresql.com/en/postgresql-understanding-deadlocks/ https://www.cybertec-postgresql.com/en/debugging-deadlocks-in-postgresql/
该篇已首发到公众号PostgreSQL运维技术,欢迎来踩~
悄悄放一张:
PostgreSQL运维技术