精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2007-09-19
了解了PG事务模式的特点后发现,PG的可串行化不是真正完全的可串行化, MVCC的可串行化在提供比传统封锁模型的串行化更小代价的同时,也有一些不足,主要是: 1. 串行化并发执行失败后,需要在应用层检测并按应用的要求决定是否重试事务。 一般情况下,重试是完全必要的,没有人希望看到事务因为意外的并发冲突而中止回滚。 然而解决这个问题并不困难,可以通过应用层自动检查到数据层串行化失败后,自动执行指定次数的事务重试(事务开销不大的条件下)。 2. 在传统封锁模型的可串行化下可行的事务(innoDB),在PG下面会遇到问题 比如类似这样一个简化了的并发事务: 开始事务: 步1:取符合条件的记录的field1的max值 步2:x = max+1 步3:写入新的一行,field1=x 结束事务。 希望看到的结果是:各行field1值 +1递增, 这在传统依赖表锁定的可串行化下没有问题, 请教各位,在PG串行化下,有没有等效的方法,实现类似这样的事务 ( 手工对表锁定、单个SQL语句 这样的方法除外的话) 也请各位谈谈 PG可串行化的其他特点,以及从传统可串行化模式转到PG下还需要注意哪些问题? 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2007-09-20
看你的问题,我感觉是不是你的 setAutoCommit 设置为true 了吧?
首先,setAutoCommit(fasle),然后用begin 启动事务,commit提交事务,rollback回滚事务 pg 没有你上述的问题,而且,看你的要求,也不需要 串行化 级别 -- 1. 串行化并发执行失败后,需要在应用层检测并按应用的要求决定是否重试事务。 -- 一般情况下,重试是完全必要的,没有人希望看到事务因为意外的并发冲突而中止回滚。 按照我上面的用法,正确使用事务 rollback后,本次事务所涉及所有数据库的操作全部回滚,不会存在你说的情况 -- 开始事务: -- 步1:取符合条件的记录的field1的max值 -- 步2:x = max+1 -- 步3:写入新的一行,field1=x -- 结束事务。 这个也同样, 不过就你这个要求不需要 串行化 级别的事务,读提交就够了, 如果你这个要求很常用,那么做一个function 这样更好,function中的所有语句, 是在一个事务中执行的 |
|
返回顶楼 | |
发表时间:2007-09-20
按照我上面的用法,正确使用事务 rollback后,本次事务所涉及所有数据库的操作全部回滚,不会存在你说的情况 再现这个问题,需要一定的事务并发量 串行化并发执行失败是PG特有的现象,是在检测到存在多个事务并发更新同一个记录等情况下,会导致一个事务自动失败回滚,而不是等待锁定解除。 -- 开始事务: -- 步1:取符合条件的记录的field1的max值 -- 步2:x = max+1 -- 步3:写入新的一行,field1=x -- 结束事务。 这是为了验证PG可串行化专门的一个例子而已 以下是我测试下来的一些看法: 由于PG特有的事务读永不封锁,造成了在一个事务过程中,如果不手动执行表锁,行锁等操作,就会导致严格依赖对步1的特定查询结果而进行的步3写入,在事务结束后依赖关系可能已经被破坏。 原因在于,在这个事务执行过程中,PG在执行步1的时候,不会锁定任何对象,其他事务仍然可能去更新步1相关的对象,导致步1的查询结果在之后可能就事实上已经不存在或者已被更改。 对比innoDB,在可串行化模式下,事务中第一步表操作(包括读)之前, 都会导致对表加锁,直到事务提交或者回滚,才解除锁定。是典型的悲观并发控制方式。相比PG来说,并发性能会差很多,但是可靠性更高,不会造成对依赖读的一致性在事务结束后就可能已经失效。 感觉你对PG的可串行化的特殊性,不是很了解,PG的可串行化,不是严格意义上完全的可串行化,原因就在于对读的永不封锁。这在大大提高事务并发性能的同时,也增加了潜在的风险。 如果是我的看法有问题,请进一步指正。 |
|
返回顶楼 | |
发表时间:2007-09-20
blowfisher 写道 按照我上面的用法,正确使用事务 rollback后,本次事务所涉及所有数据库的操作全部回滚,不会存在你说的情况 再现这个问题,需要一定的事务并发量 串行化并发执行失败是PG特有的现象,是在检测到存在多个事务并发更新同一个记录等情况下,会导致一个事务自动失败回滚,而不是等待锁定解除。 -- 开始事务: -- 步1:取符合条件的记录的field1的max值 -- 步2:x = max+1 -- 步3:写入新的一行,field1=x -- 结束事务。 这是为了验证PG可串行化专门的一个例子而已 以下是我测试下来的一些看法: 由于PG特有的事务读永不封锁,造成了在一个事务过程中,如果不手动执行表锁,行锁等操作,就会导致严格依赖对步1的特定查询结果而进行的步3写入,在事务结束后依赖关系可能已经被破坏。 原因在于,在这个事务执行过程中,PG在执行步1的时候,不会锁定任何对象,其他事务仍然可能去更新步1相关的对象,导致步1的查询结果在之后可能就事实上已经不存在或者已被更改。 对比innoDB,在可串行化模式下,事务中第一步表操作(包括读)之前, 都会导致对表加锁,直到事务提交或者回滚,才解除锁定。是典型的悲观并发控制方式。相比PG来说,并发性能会差很多,但是可靠性更高,不会造成对依赖读的一致性在事务结束后就可能已经失效。 感觉你对PG的可串行化的特殊性,不是很了解,PG的可串行化,不是严格意义上完全的可串行化,原因就在于对读的永不封锁。这在大大提高事务并发性能的同时,也增加了潜在的风险。 如果是我的看法有问题,请进一步指正。 -- 由于PG特有的事务读永不封锁,造成了在一个事务过程中,如果不手动执行表锁,行锁等操作,就会导致严格依赖对步1的特定查询结果而进行的步3写入,在事务结束后依赖关系可能已经被破坏。 在串行化下, 在这个事务没有commit之前,你能insert 或者 update 进去? 我不知道你是怎么搞的,我这里刚试过。我还是建议你去仔细看看什么叫 串行化。 我可以很肯定的告诉你,pg的串行化, 没有任何特殊性质,完全符合标准。 把你的要求写写清楚,否则我无法帮助你 |
|
返回顶楼 | |
发表时间:2007-09-20
其实 Oracle 也是 MVCC, 结果可能和 PG 一样.
看来 mikewang 确实没理解 blowfisher 的问题. 我对这个问题的理解是这样: 对于 SELECT MAX(field1) FROM ... 这样一个读取操作, 假如要保证事务 "绝对" 的 "可串行化" 也就是如果有其它一个或多个并发事务的执行会造成全表此字段最大值与此事务读到的值不同, 则它们和此事务不应有可能全部提交成功, 最多只能有一个成功提交而其它全部强制失败回滚. 但是考虑到条件判断可能无限复杂化, 要让数据库内核去细粒度的判断特定事务操作是否与其它并发事务有约束冲突, 基本是不现实的, 所以相对可行的方案是执行类似这种 MAX 操作的读取时就锁全表 (乐观锁情况下提交时发现全表有任何非本事务造成的改动就强制回滚). 但是这个办法在性能上很可能还得不偿失, 所以各种数据库内核应该都不会真的这么去做. 所以如果为了提高日常情况下的性能, 考虑乐观锁, MVCC等, 在现实中定义出 可串行化(SERIALIZABLE) 这个隔离级别与理想的绝对可串行化还是会有一定差距的. 而针对具体的常见问题, 一般也会给出另外的解决方案. 比如 blowfisher 这个例子, 放到 PG, ORACLE 里可能就用 SEQUENCE 解决了. 实在不想用SEQUENCE, 建一个新的专门存 MAX 值的表, 只有一行记录的, 也是一个 workaround. |
|
返回顶楼 | |
发表时间:2007-09-20
补充一下:
blowfisher 写道 以下是我测试下来的一些看法: 由于PG特有的事务读永不封锁,造成了在一个事务过程中,如果不手动执行表锁,行锁等操作,就会导致严格依赖对步1的特定查询结果而进行的步3写入,在事务结束后依赖关系可能已经被破坏。 我觉得这个说得不准确, 如果是直接读记录字段, 应该是会封锁的. 上例中不封锁应该是因为它是计算结果 (取MAX值), 并且不依赖于某个特定的记录行(而是全部记录行,需要封锁全表,包括排斥其他事务的插入和删除,权衡下最后不封锁). |
|
返回顶楼 | |
发表时间:2007-09-20
歆渊兄理解我的意思了~
引用 但是考虑到条件判断可能无限复杂化,..., 所以相对可行的方案是执行类似这种 MAX 操作的读取时就锁全表
事实上innoDB就是这样操作的。 如果查询条件复杂度超过一定限度,就会采用锁全表,如果是简单条件,会锁特定的行。这就是PG与innoDB最大区别的地方。 引用 我觉得这个说得不准确, 如果是直接读记录字段, 应该是会封锁的. 上例中不封锁应该是因为它是计算结果 (取MAX值), 并且不依赖于某个特定的记录行(而是全部记录行,需要封锁全表,包括排斥其他事务的插入和删除,权衡下最后不封锁).
PG在可串行模式事务下,对读记录,确实是不封锁的。要确保读取的记录不被并发的事务更新,需要手动lock 我的理解是这是PG的MVCC特性, 与ORACLE的MVCC有略微的区别。 innoDB也是基于MVCC,相比与ORACLE更接近些。 PG对读记录不封锁,这个测试可以很简单的证明这一点: [cucc2]表中ID: 1 - 10 -------------------------------------------------------------------------- public void execute(Connection conn) throws Exception { conn.setAutoCommit(false); conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); String SQL = "SELECT max(id) FROM cucc2"; PreparedStatement pstmt = conn.prepareStatement(SQL); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { System.out.println("MAX: " + rs.getLong(1)); // <----------- 1 break; } rs.close(); pstmt.close(); Thread.currentThread().sleep(15000); // <------------ 2 conn.commit(); } >> 1处查询得到记录id为10的行 >> 2处仍在事务执行过程中,这时如果你通过其他途径去删除 ID-10 这行,innoDB是锁定的(不是全表锁定,是行锁定,因为查询条件很简单),需要15s事务结束后才能删除,而PG则允许你删除! |
|
返回顶楼 | |
发表时间:2007-09-20
补充点:
http://www.pgsqldb.org/pgsqldoc-8.1c/applevel-consistency.html 这里几乎是原文,翻译的艰涩了点 引用 因为不管哪种隔离级别,对 PostgreSQL 的读动作不会锁定数据, 一个事务读取的数据可能被另一个事务覆盖。换句话说,如果一条 SELECT 返回了一行, 这并不意味着在返回该行时该行还存在(也就是说在语句完成或事务开始后的某时) 该行可能已经被一个在此事务开始之后提交的事务更新或者删除。 即使该行"现在"仍然有效,那它也可能在当前事务提交或者回滚之前被改变或者删除。
在 MVCC 环境下,全局有效性检查需要一些额外的考虑。。。 |
|
返回顶楼 | |
发表时间:2007-09-23
blowfisher 写道 ...而PG则允许你删除! 允许删除不代表允许两个事务全部同时成功提交, 某种意义上说乐观锁就是用 "强制回滚" 代替了 "等待锁定" 所以判断条件应该是两个事务是否最终全部成功提交. 另外上面例子里用的还是读MAX值, 如果是读特定记录行的数据(而不是MAX), 我觉得PG应该是会加乐观锁的, 也就是说最终会强制回滚后提交的事务. |
|
返回顶楼 | |
发表时间:2007-09-23
blowfisher 写道 补充点:
http://www.pgsqldb.org/pgsqldoc-8.1c/applevel-consistency.html 这里几乎是原文,翻译的艰涩了点 引用 因为不管哪种隔离级别,对 PostgreSQL 的读动作不会锁定数据, 一个事务读取的数据可能被另一个事务覆盖。换句话说,如果一条 SELECT 返回了一行, 这并不意味着在返回该行时该行还存在(也就是说在语句完成或事务开始后的某时) 该行可能已经被一个在此事务开始之后提交的事务更新或者删除。 即使该行"现在"仍然有效,那它也可能在当前事务提交或者回滚之前被改变或者删除。
在 MVCC 环境下,全局有效性检查需要一些额外的考虑。。。 这里 "不锁定" 应该是指不加悲观的排斥锁. 这些话的意思应该是告诉应用程序不要把PG事务里读出来的数据直接应用于其他资源(因为它们不会随PG事务回滚), 比如plain data file或者其它未经事务协调的数据系统. 这些数据用于同一PG事务的写操作时, 如果不能满足要求的隔离级别, PG把它强制回滚也就罢了, 但是对于PG管理外的资源, PG是无能为力的, 所以必须由应用来特别注意, 进行额外的考虑. |
|
返回顶楼 | |