PostgreSQL 锁解密

前端之家收集整理的这篇文章主要介绍了PostgreSQL 锁解密前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

锁机制在 Postgresql 里非常重要 (对于其他现代的 RDBMS 也是如此)。对于数据库应用程序开发者(特别是那些涉及到高并发代码的程序员),需要对锁非常熟悉。对于某些问题,锁需要被重点关注与检查。大部分情况,这些问题跟死锁或者数据不一致有关系,基本上都是由于对 Postgres 的锁机制不太了解导致的。虽然锁机制在 Postgres 内部很重要,但是文档缺非常缺乏,有时甚至还是错误的,与文档所指出的结果不一致。我会告诉你精通 Postgres 的锁机制需要知道的一切,要知道对锁了解的越多,解决与锁相关的问题就会越快。


文档里都说了些什么?


Postgres 有 3 种锁机制:表级锁,行级锁和建议性锁。表级和行级的锁可以是显式的也可以是隐式的。建议性锁一般是显式的。显式的锁由显式的用户请求(通过特殊的查询获取,隐式的锁是通过标准的 sql 命令来获取


除了表级行级的锁,还有页级共享/排除锁,用于控制对共享缓存池里表页的访问。在一行数据被读取或者更新后,这些锁会立即被释放。应用程序开发者通常不需要关注页级的锁。


锁机制会不时的变动,所以我们这里只针对 Postgres 9.x 的版本。9.1 和 9.2 基本上是差不多的,9.3 和 9.4 跟它们有些区别,主要涉及行级锁。


表级锁


大多数的表级锁是由内置的 sql 命令获得的,但他们也可以通过锁命令来明确获取。可使用的表级锁包括

  • 访问共享(ACCESS SHARE) - SELECT 命令可在查询中引用的表上获得该锁一般规则是所有的查询中只有读表才获取此锁。

  • 行共享(ROW SHARE) - SELECT FOR UPDATE 和 SELECT FOR SHARE 命令可在目标表上获得该锁(以及查询中所有引用的表的访问共享锁)

  • 行独占(ROW EXCLUSIVE) - UPDATE、INSERT 和 DELETE 命令在目标表上获得该锁(以及查询中所有引用的表的访问共享锁)。 一般规则是所有修改表的查询获得该锁。

  • 共享更新独占(SHARE UPDATE EXCLUSIVE) - VACUUM(不含FULL),ANALYZE,CREATE INDEX CONCURRENTLY,和一些 ALTER TABLE 的命令获得该锁。

  • 共享(SHARE) - CREATE INDEX 命令在查询中引用的表上获得该锁。

  • 共享行独占(SHARE ROW EXCLUSIVE) - 不被任何命令隐式获取

  • 排他(EXCLUSIVE) - 这个锁模式在事务获得此锁时只允许读取操作并行。它不能由任何命令隐式获取

  • 访问独占(ACCESS EXCLUSIVE) - ALTER TABLE,DROP TABLE,TRUNCATE,REINDEX,CLUSTER 和 VACUUM FULL 命令在查询中引用的表上获得该锁。此锁模式是 LOCK 命令的默认模式。


重要的是要知道,所有这些锁都是表级,即使它们名称里有ROW)字。


每个锁模式的最重要的信息是与彼此冲突的模式列表。在同一时间同一个表中,2 个事务不能同时保持相冲突的锁模式。事务永远不会与自身发生冲突。 非冲突的锁可以支持多事务并发。同样重要的是要知道有的模式和自身冲突。一些锁模式在 获得后会持续到事务结束。但如果锁是在建立一个保存点后获得,保存点回滚后锁会被立刻释放。 下面的表格展示了哪些模式是互相冲突的:


行级锁


在 Postgres 9.1 和 9.2 有两种行级锁模式,但在 Postgres 9.3 和 9.4 有四种行级锁模式。


Postgres 不会记住修改的行在内存中的任何信息,所以一次锁定的行的数目没有限制。然而,锁定一行可能会导致磁盘写入,例如,SELECT FOR UPDATE 修改选定的行并标记它们锁定,所以会导致磁盘写入。


Postgres 9.1 和 9.2 中的行级锁


在这两种版本中,只有 2 种行级锁:排他或共享锁。当行更新或删除时,会自动获得排他行级锁。行级锁不阻止数据查询,它们只阻止同一行写入。 排他行级锁可由 SELECT FOR UPDATE 命令明确获得,即使行没有实际更改。


共享行级锁可由 SELECT FOR SHARE 命令获得。一个共享锁并不阻止其他事务获取同样的共享锁。然而,当任何其他事务持有共享锁时,事务的更新、删除或排他锁都不被允许


Postgres 9.3 和 9.4 中的行级锁


在 Postgres 9.3 和 9.4 中有四种类型的行级锁:

  • 更新(FOR UPDATE) - 这种模式导致 SELECT 读取的行的更新被锁定。这可以防止它们被其他事务锁定,修改删除。即尝试 UPDATE、DELETE、SELECT FOR UPDATE、SELECT FOR NO KEY UPDATE、SELECT FOR SHARE 或 SELECT FOR KEY SHARE 的其他事务将被阻塞。删除一行,更新一些列也可以获得到此种锁模式(目前的列集是指那些具有唯一索引,并且可被用作外键 - 但将来这可能会改变)。

  • 无键更新(FOR NO KEY UPDATE) - 这种模式与 FOR UPDATE 相似,但是更弱 - 它不会阻塞SELECT FOR KEY SHARE 锁模式。它通过不获取更新锁的 UPDATE 命令获得。

  • 共享(FOR SHARE) - 这种模式与无键更新锁类似,除了它可以获取共享锁(非排他)。一个共享锁阻止其他事务在这些行上进行 UPDATE,DELETE,SELECT FOR UPDATE 或 SELECT FOR NO KEY UPDATE 操作,但并不阻止它们进行 SELECT FOR SHARE 或 SELECT FOR KEY SHARE。

  • 键共享FOR KEY SHARE- 行为类似于共享,但该锁是较弱的:阻止了 SELECT FOR UPDATE,但不阻止 SELECT FOR NO KEY UPDATE。一个键共享锁阻止其他事务进行 DELETE 或任何更改该键值的 UPDATE,但不妨碍任何其他 UPDATE、SELECT FOR NO KEY UPDATE、SELECT FOR SHARE 或者SELECT FOR KEY SHARE。


行级锁冲突:


劝告锁


Postgres提供创建具有应用定义的锁的方法,这些被称为劝告锁(advisory locks),因为系统并不支持其使用,其取决于应用对锁的正确使用。


Postgres中有两种途径可以获得一个劝告锁:会话层级或事务层级。一旦在会话层级获得劝告锁,会一直保持到被显式释放或会话结束。不同于标准的锁请求,会话层级的劝告锁请求并不遵守事务语义:事务被回滚后锁也会随着回滚保持着,同样地即使调用锁的事务之后失败了,解锁请求仍然是有效的。一个锁可以被拥有它的进程多次获取;对于每个完成的锁请求,在锁被真正释放前一定要有一个对应的解锁请求。


另一方面,事务层级的锁请求表现得更像普通的锁请求:它们在事务结束时会自动释放,并且没有显式的解锁操作。对于短暂地使用劝告锁,这种特性通常比会话层级更方便。可以想见,会话层级与事务层级请求同一个劝告锁标识符会互相阻塞。如果一个会话已经有了一个劝告锁,它再请求时总会成功的,即使其他会话在等待此锁;不论保持现有的锁和新的请求是会话层级还是事务层级,都是这样。文档中可以找到操作劝告锁的完整函数列表。


文章后续有大量代码,不建议在微信查看,请点击【阅读原文】查看更多内容,谢谢支持:)

猜你在找的Postgre SQL相关文章