postgre 事务隔离

https://github.com/angelfan/DayDayUp/blob/master/note/transaction_isolation.mdgit

 

事务隔离

MVCC的实现方法有两种:github

1.写新数据时,把旧数据移到一个单独的地方,如回滚段中,其余人读数据时,从回滚段中把旧的数据读出来;sql

2.写数据时,旧数据不删除,而是把新数据插入。数据库

PostgreSQL数据库使用第二种方法,而Oracle数据库和MySQL中的innodb引擎使用的是第一种方法。ruby

与racle数据库和MySQL中的innodb引擎相比较,PostgreSQL的MVCC实现方式的优缺点以下。并发

优势:oracle

1.事务回滚能够当即完成,不管事务进行了多少操做;spa

2.数据能够进行不少更新,没必要像Oracle和MySQL的Innodb引擎那样须要常常保证回滚段不会被用完,也不会像oracle数据库那样常常遇到“ORA-1555”错误的困扰;code

缺点:事务

1.旧版本数据须要清理。PostgreSQL清理旧版本的命令成为Vacuum;

2.旧版本的数据会致使查询更慢一些,由于旧版本的数据存在于数据文件中,查询时须要扫描更多的数据块。

(本段转自《PostgreSQL修炼之道》)

脏读(dirty reads)

一个事务读取了另外一个未提交的并行事务写的数据。

不可重复读(non-repeatable reads)

一个事务从新读取前面读取过的数据, 发现该数据已经被另外一个已提交的事务修改过。

幻读(phantom read)

一个事务从新执行一个查询,返回一套符合查询条件的行, 发现这些行由于其余最近提交的事务而发生了改变。

BEGIN;
SELECT title FROM books WHERE id = 1; -- 'Old' gap SELECT titoe FROm books WHERE id = 1 -- '???'
UPDATE books SET title 'New' WHERE id = 1

读未提交 Read Uncommitted

另外一个事务中只要更新的记录, 当前事务就会读取到更新的数据 (脏读)

读已提交 Read Committed

另外一个事务必须提交, 当前事务才会读取到最新数据 (不可脏读 可是可重复读)

可重复读 Repeatable Read

即便另外一个事务几条, 当前事务也不会读取到新的数据 (不可重复读) 若是另外一事务作插入操做, 当前事务是能够取得数量上的变化(select count(*) from table)(能够幻读)

可串行化 Serializable

另外一事务作了插入操做, 当前事务不会取得数量上的变化(select count(*) from table)(不能够幻读)

隔离级别 脏读 不可重复读 幻读
读未提交 可能 可能 可能
读已提交 不可能 可能 可能
可重复读 不可能 不可能 可能
可串行化 不可能 不可能 不可能

读已提交隔离级别( BEGIN TRANSACTION ISOLATION LEVEl READ COMMITTED; )

当一个事务运行在这个隔离级别时: SELECT查询(没有FOR UPDATE/SHARE子句)只能看到查询开始以前已提交的数据而没法看到未提交的数据或者在查询执行期间其它事务已提交的数据 (仅读当时数据库快照)。 不过,SELECT看得见其自身所在事务中前面更新执行结果,即便它们还没有提交。 (注意:在同一个事务里两个相邻的SELECT命令可能看到不一样的快照,由于其它事务会在第一个SELECT执行期间提交。)

分析

要是同时有两个事务修改同一行数据会怎么样? 这就是事务隔离级别(transaction isolation levels)登场的时候了。 Postgres支持两个基本的模型来让你控制应该怎么处理这样的状况。默认状况下使用读已提交(READ COMMITTED), 等待初始的事务完成后再读取行记录而后执行语句。若是在等待的过程当中记录被修改了,它就从头再来一遍。 举一个例子,当你执行一条带有WHERE子句的UPDATE时,WHERE子句会在最初的事务被提交后返回命中的记录结果, 若是这时WHERE子句的条件仍然能获得知足的话,UPDATE才会被执行。 在下面这个例子中,两个事务同时修改同一行记录,最初的UPDATE 语句致使第二个事务的WHERE不会返回任何记录,所以第二个事务根本没有修改到任何记录

Order.transaction do Order.where(ship_code: '123').update_all(ship_code: '123456') sleep 20 end # 在第一个事务执行完update_all后提交以前执行 Order.transaction do Order.where(ship_code: '123').update_all(ship_code: '1234567') end # ship_code => 123456 # Order.where(ship_code: '123').update_all(ship_code: '1234567') 的时候已经找不到 where(ship_code: '123') Order.transaction do Order.where(ship_code: '123').first.update(ship_code: '1234567') end # ship_code => 1234567 # 这种状况下会修改记录是由于 它先执行的是Order.where(ship_code: '123').first # 由于默认的隔离级别是读已提交, 因此这个时候 # Order.where(ship_code: '123') 是能够拿到数据的 # 执行的sql语句是 ## 先拿到order#id ## UPDATE "orders" SET "ship_code" = 1234567 WHERE "orders"."id" = order#id # repeatable Order.transaction do old_ship_code = Order.where(id: 2).pluck(:ship_code) sleep 10 new_ship_code = Order.where(id: 2).pluck(:ship_code) p [old_ship_code, new_ship_code] # 数据是不同的 end Order.where(id: 2).update_all(ship_code: 123456123) 

可重复读隔离级别

ActiveRecord::Base.isolation_level(:repeatable_read) do Order.transaction do old_ship_code = Order.where(id: 2).pluck(:ship_code) sleep 10 new_ship_code = Order.where(id: 2).pluck(:ship_code) p [old_ship_code, new_ship_code] # 数据是同样的 end end

可串行化隔离级别

可串行化级别提供最严格的事务隔离。这个级别为全部已提交事务模拟串行的事务执行, 就好像事务将被一个接着一个那样串行(而不是并行)的执行。不过,正如可重复读隔离级别同样, 使用这个级别的应用必须准备在串行化失败的时候从新启动事务。 事实上,该隔离级别和可重复读但愿的彻底同样,它只是监视这些条件,以全部事务的可能的序列不一致的(一次一个)的方式执行并行的可序列化事务执行的行为。 这种监测不引入任何阻止可重复读出现的行为,但有一些开销的监测,检测条件这可能会致使序列化异常 将触发序列化失败。

分析:若是你须要更好的“两个事务同时修改同一行数据”控制这种行为,你能够把事务隔离级别设置为 可串行化(SERIALIZABLE) 。 在这个策略下,上面的场景会直接失败,由于它遵循这样的规则:“若是我正在修改的行被其余事务修改过的话,就再也不尝试”, 同时 Postgres会返回这样的错误信息:因为并发修改致使没法进行串行访问 。 捕获这个错误而后重试就是你的应用须要去作的事情了,或者不重试直接放弃也行,若是那样合理的话。

ActiveRecord::Base.isolation_level(:serializable) do Order.transaction do old_ship_code = Order.where(id: 2).update_all(ship_code: 123) sleep 10 end end Order.where(id: 2).update_all(ship_code: 123) # PG::TRSerializationFailure: ERROR: could not serialize access due to concurrent update # ActiveRecord::TransactionIsolationConflict
相关文章
相关标签/搜索