[翻译]PostgreSQL中的死锁

原文

在讨论死锁之前,让我们看一下锁的类型以及它们在PostgreSQL中的获取方法。

锁的类型:

  • 表级锁 以及
  • 行级锁

表级锁:

  • AcessShareLock(访问共享锁):通过在一张表或多张表一条 SELECT 语句检索数据时就会自动获取。这个模式会阻塞在同一张表下的 ALTER TABLEDROP TABLE 以及 VACUUM (AccessExclusiveLock,访问排他锁)操作。

  • RowShareLock (行共享锁):通过一条SELECT...FOR UPDATE子句自动获取。它会在同一张表上阻塞并发的ExclusiveLock(排他锁)以及AccessExclusiveLock(访问排他锁)。

  • RowExclusiveLock (行排他锁):通过UPDATEINSERT,或者 DELETE命令自动获取。它会在同一张表上阻塞ALTER TABLEDROP TABLE,VACUUMCREATE INDEX命令。(ShareLock[共享锁],ShareRowExclusiveLock[共享行排他锁],ExclusiveLock[排他锁],AccessExclusiveLock[访问排他锁])。

  • ShareLock(共享锁): 通过CREATE INDEX命令自动获取。它会在同一张表上阻塞INSERT, UPDATE, DELETE, ALTER TABLE, DROP TABLE, 以及 VACUUM命令.(RowExclusiveLock[行排他锁], ShareRowExclusiveLock[共享行排他锁], ExclusiveLock[排他锁], 以及 AccessExclusiveLock[访问排他锁])

  • ShareRowExclusiveLock(共享行排他锁):这个锁模式与ExclusiveLock(排他锁)是一样的,但是它允许获取并发的 RowShareLock(行共享锁)

  • ExclusiveLock(排他锁):”每个事务在它的事务ID的整个时间里都会持有一个Exclusive Lock(排他锁)”。如果一个事务发现它需要特别地等待另一个事务,它就会尝试地在另一个事务ID上获取 Share Lock(共享锁)。这仅当另一个事务结束并释放它自己的锁时才会成功。(注意,冲突)。Exclusive Lock(排他锁)会在同一张表上阻塞INSERT, UPDATE, DELETE,CREATE INDEX, ALTER TABLE,DROP TABLE, SELECT...FOR UPDATE 以及 VACUUM命令。

  • AccessExclusiveLock(访问排他锁):通过ALTER TABLE, DROP TABLE, 或者 VACUUM命令来修改表时自动获取。从开始在表上获取锁时,会阻塞任何并发命令或者其他锁模式。

行级锁:

行级锁有两种类型:共享锁和排他锁。不要混淆锁的命名,你可以通过在视图pg_locks中的列lock_type来区分是表级锁,还是行级锁。

  • Exclusive lock(排他锁):当通过UPDATEDELETE命中行时就会自动获取该锁。锁会一直被持有,直到一个事务提交或回滚了。为了手动获取exclusive-lock(排他锁),可以使用SELECT FOR UPDATE

  • Share-Lock(共享锁):当通过SELECT...FOR SHARE命中行时就会自动获取该锁。

注意:在这两种情况下的行级锁,数据检索是一点也不会影响的。行级锁会阻塞“写”(即,“写”会阻塞“写”)。

死锁:

现在到死锁了,你已经知道锁的模式以及获取这些锁的方法,有些情况下事务会陷入死锁中。我相信应用程序设计是导致死锁的罪魁祸首。死锁大多数是由于ExclusiveLock(排他锁)例如UPDATEDELETE操作导致的。

什么是死锁?

进程A持有对象X的锁,并且正等待对象Y的锁。进程B持有对象Y的锁,并且正等待对象X的锁。在这时,这两个进程就会进入所谓的“死锁”,每个进程都想获取另一个进程持有的锁。在这个状态下,他们都在永远等待对方释放锁。他们之一必须放弃并释放自己拥有的锁。现在,死锁检测器就会检测到并且允许一个进程成功提交事务,而另一个则进行回滚。

为了解决死锁,要用这样一个方法来设计应用程序——任何事务“UPDATE”或“DELETE”都应该成功地取得表的所有权。通过SHARE UPDATE EXCLUSIVE 模式或者SELECT...FOR UPDATE,又或者ACCESS EXCLUSIVE 模式来锁表并且完成事务。在这个模型下,死锁检测器永远不会抛出它已经检测到一个EXCLUSIVE LOCK(排他锁)。

你可通过这个办法来解决上面的图的情况,你会看到死锁检测器永远不会抛出错误。

锁查询语句

1
\set locks 'SELECT w.locktype AS waiting_locktype,w.relation::regclass AS waiting_table,w.transactionid, substr(w_stm.current_query,1,20) AS waiting_query,w.mode AS waiting_mode,w.pid AS waiting_pid,other.locktype AS other_locktype,other.relation::regclass AS other_table,other_stm.current_query AS other_query,other.mode AS other_mode,other.pid AS other_pid,other.granted AS other_granted FROM pg_catalog.pg_locks AS w JOIN pg_catalog.pg_stat_activity AS w_stm ON (w_stm.procpid = w.pid) JOIN pg_catalog.pg_locks AS other ON ((w.\"database\" = other.\"database\" AND w.relation = other.relation) OR w.transactionid = other.transactionid) JOIN pg_catalog.pg_stat_activity AS other_stm ON (other_stm.procpid = other.pid) WHERE NOT w.granted AND w.pid <> other.pid;;'

关于锁的信息链接

http://www.postgresql.org/docs/9.0/static/sql-lock.html
http://developer.postgresql.org/pgdocs/postgres/explicit-locking.html

希望你有一个关于PostgreSQL锁的概念了。 希望在之后的博文中可以再次见到你。:)

—Raghav

By Raghavendra


我附:
现在的PG有8种表级锁了,除了上面作者提到的7种,还有一种是:

  • SHARE UPDATE EXCLUSIVE 锁

与”Share update exclusive,Share,Share row ,exclusive,exclusive,Access exclusive”模式冲突,这种模式保护一张表不被并发的模式更改和VACUUM;

“Vacuum(without full), Analyze ”和 “Create index concurrently”命令会获得这种类型锁。

资料:

http://www.postgresql.org/docs/9.0/static/explicit-locking.html