PostgreSQL并发控制
http://www.wohedb.com/db_html_doc/sql/sql_9_concurrency.htmwww.wohedb.com 中文数据库管理系统 作者:闫华 postgres_fan@yahoo.com.cn
第九章 并发控制
本章介绍PostgreSQL的并发控制机制。当两个或多个用户同时访问同一个数据行时,需要使用并发控制机制来维护数据的完整性和一致性。
9.1 概述
PostgreSQL使用的是多版本并发控制机制(Multiversion Concurrency Control, MVCC)。多版本并发控制机制的意思是数据库中的每个事务在查询数据时,看到的是数据的快照(一个历史版本),而不是数据的当前状态。在多版本并发控制机制中,读操作不用等待写操作,写操作不用等待读操作,只有在两个事务试图同时更新同一个数据行时,才会有等待出现。多版本并发控制机制可以减少数据库中的锁争用,减少用户的等待时间,提高数据库的性能。
PostgreSQL同时也提供了接口让应用程序显式对数据进行加锁操作。这些接口支持表级锁和行级锁。此外,还提供了建议锁(advisory lock)这样的锁机制,使得应用程序能够完全自主地控制得到和释放锁的时间。
9.2事务的隔离级别
SQL标准定义了四个事务隔离级别,表9-1列出了所有的隔离级别,事务在不同的隔离级别可以中看到的数据是不相同的。
表9-1. SQL 事务隔离级别
隔离级别
脏读(dirty read)
不可重复读(nonrepeatable read)
影子读(Phantom Read)
Read uncommitted
可能
可能
可能
Read committed
不可能
可能
可能
Repeatable read
不可能
不可能
可能
Serializable
不可能
不可能
不可能
脏读(dirty read)
一个事务可以读取其它还未提交的事务修改的数据。
不可重复读(nonrepeatable read)
一个事务重新读取以前读过的数据时,发现该数据被修改过。
影子读(phantom read)
一个事务两次执行同一个查询,发现第二次查询得到的数据行比第一次查询得到的数据行多。
PostgreSQL只提供了两种事务隔离级别,分别是Read Committed 和Serializable。使用命令SET TRANSACTION来设置事务的隔离级别。应用程序可以将事务的隔离级别设为Read Uncommitted,但系统会自动将隔离级别设为Read Committed。应用程序也可以将事务的隔离级别设为Repeatable Read,但系统会自动将隔离级别设为Serializable。这样的规则是符合SQL标准的。
9.2.1 Read Committed隔离级别
Read Committed是PostgreSQL的默认隔离级别。如果一个事务运行在这个隔离级别,事务发出的SELECT命令只能看见在SELECT命令开始执行以前提交的数据。同一个事务的两个先后执行的 SELECT命令可能会看到不同的数据,因为在其它并发执行的事务可能在第一个SELECT命令执行的过程中被提交。另外,事务发出的SELECT命令可以看到该事务以前发出的更新命令(包括INSERT、DELETE和UPDATE)修改过的数据。
命令UPDATE、 DELETE、SELECT FOR UPDATE和SELECT FOR SHARE可以看到的数据和SELECT命令是一样的。如果一个事务执行命令UPDATE(DELETE、SELECT FOR UPDATE或SELECT FOR SHARE),该命令发现一个数据行R1满足自己的搜索条件,同时有另外一个事务T2已经锁住了数据行T1(T2可能正在删除或更新R1),那么T1将进入等待状态,直到T2执行结束(回滚或提交),T1才能继续运行,根据T2的执行结果,T1有两种执行方式:
(1)如果T2被回滚,T2对R1做的更新将被取消,T1会使用R1原来的值继续运行命令。
(2)如果T2被提交,那要分两种情况:
(a)T2删除了数据行R1,那么T1继续运行时,将忽略R1, R1对T1正在执行的命令是不可见的。
(b)T2修改了数据行R1, 那么T1继续运行时,T1正在执行的命令将看见R1的新的值,同时会重新检查R1的新值是否符合该命令的WHERE子句的搜素条件, 如果符合,将使用R1的新值作为搜索结果,如果不符合,将忽略R1的新值。
Read Committed隔离级别值保证一个事务的单个命令看到的数据是一致的,不能保证一个事务发出的所有命令看到的数据都是一致的。
Read Committed隔离级别可以满足大部分应用程序的需要,它使用简单,比serializable隔离级别要快速。但那些使用复杂的查询和更新操作的应用可能需要数据库提供更加严格数据的一致性,这种情况下应该使用Serializable隔离级别。
9.2.2 Serializable隔离级别
Serializable是最严格的隔离级别。在这种隔离级别下,所有并发执行的事务的执行结果和单个事务一个一个地执行的结果是一样的。在这种隔离级别下运行的应用程序的逻辑要复杂一些,在事务不符合可串行化要求的而被终止的情况下,应用程序应该能够重新创建被终止的事务并再次请求数据库执行该事务。
在Serializable隔离级别下运行的应用程序,一个事务发出的SELECT命令只能看见事务开始运行以前已经提交的数据,看不见在事务开始运行以前没有提交的数据和在事务执行过程中其它并发执行的事务提交的数据。同一个事务的两个先后执行的 SELECT命令看到的数据是一样的。但事务的SELECT命令可以看到该事务以前发出的更新命令(包括INSERT、DELETE和UPDATE)修改过的数据。
命令UPDATE、 DELETE、SELECT FOR UPDATE和SELECT FOR SHARE可以看到的数据和SELECT命令是一样的。这些命令只能看到事务开始运行以前已经提交的数据。但是如果一个事务执行命令UPDATE(DELETE、SELECT FOR UPDATE或SELECT FOR SHARE),该命令发现一个数据行R1满足自己的搜索条件,同时有另外一个事务T2已经锁住了数据行T1(T2可能正在删除或更新R1),那么T1将进入等待状态,直到T2执行结束(回滚或提交),T1才能继续运行,根据T2的执行结果,T1有两种执行方式:
(1)如果T2被回滚,T2对R1做的更新将被取消,T1会使用R1原来的值继续运行命令。
(2)如果T2被提交,而且T2删除或者更新了R1,T1将被回滚,数据库会发出下面的提示信息:
错误:当前事务运行在可串行化模式下,与其它并发执行的事务冲突,将被回滚。
如果应用程序收到上面的错误信息,应该终止执行当前事务,并重新从头开始执行这个事务。注意,只有含有更新操作的事务可能遇到上面的错误而被终止,只读事务永远都不会被被终止。
Serializable隔离级别保证每个事务在执行过程中看到完全一致的数据。但应用程序的逻辑会变得很复杂,多个事务若同时更新同一个数据行,其中有一个事务就可能失败,应用程序必须重新执行失败的事务,对数据库的压力就会变大。如果事务的更新逻辑非常复杂,导致在Read Committed隔离级别下可能出现不正确的结果,应该使用Serializable隔离级别。通常如果一个事务的几个连续执行的命令需要看到一样的数据,就应该使用Serializable隔离级别。
9.2.3 Serializable隔离级别和真正的可串行化
Serializable隔离级别并没有保证真正的数学上的可串行化,它只是保证事务在执行时不会出现脏读、不可重复读和Phantom Read。
例如,假设有一个叫mytab的表,它的内容如下:
class | value
-------+-------
1 | 10
1 | 20
2 | 100
2 | 200
有一个事务T1:
BEGIN
SELECT SUM(value) FROM mytab WHERE class = 1;
INSERT INTOmytab VALUES(2, SUM(value));
--注意SUM(value)表示将第一查询的结果插入到表中,这不是正确的INSERT命令语法。
COMMIT
有另一个事务T2:
BEGIN
SELECT SUM(value) FROM mytab WHERE class = 2;
INSERT INTOmytab VALUES(1, SUM(value));));--注意SUM(value)表示将第一查询的结果插入到表中,这不是正确的INSERT命令语法
COMMIT
假设T1和T2在Serializable隔离级别下被执行。
如果T1和T2并发执行,T1得到的结果是30,同时mytab中会多一个数据行(2,30)。T2得到的结果是300,同时mytab中会多一个数据行(1,30)。
如果T1先执行,T1结束后,T2再执行,T1得到的结果是30,同时mytab中会多一个数据行(2,30)。T2得到的结果是330,同时mytab中会多一个数据行(1,330)。
如果T2先执行,T2结束后,T1再执行,T1得到的结果是330,同时mytab中会多一个数据行(2,330)。T2得到的结果是300,同时mytab中会多一个数据行(1,300)。
从上面的例子可以看出在Serializable隔离级别下,并发执行的事务的执行结果与事务串行执行的结果并不相同。
如果想保证真正的数学上的可串行化,数据库必须使用谓词锁(predicate lock,SQL Server叫range lock),意思是如果一个事务T1正在执行一个查询,该查询的的WHERE子句存在一个条件表达式E1,那么另外一个事务T2 就不能插入或删除任何满足E1的数据行。例如,一个事务A正在执行一个查询SELECT ... WHERE class = 1,另外一个事务就不能插入、更新或删除任何满足“class=1”的数据行。只有在A提交以后,B才能进行这样的操作。
实现谓词锁的代价非常高,而且事务会经常处理等待的状态,增加了查询的响应时间。而且绝大大不部分的应用都不需要数据库保证真正的数学上的可串行化。所以PostgreSQL并没有实现谓词锁,如果应用程序需要数据库保证真正的数学上的可串行化,可以使用PostgreSQL提供的命令对数据显示地进行加锁操作,后面的小节将会讨论如何对数据进行加锁操作。
页:
[1]