1

给定以下 SQL 伪代码:

... open transaction ...
SELECT * FROM users WHERE users.id = 5
... some calculation here ...
INSERT INTO users (id) VALUES (5)
... commit transaction ...

是否保证REPEATABLE_READ 隔离可以防止Users[id=5]在查询和插入之间插入并发连接,还是我需要为此使用SERIALIZABLE隔离?

注意:如果可能的话,我正在寻找与数据库无关的解决方案。如果没有,请提供来自多个数据库的经验证据,以便我们可以反对某种行业共识。

更新:原始问题包含使用范围锁的不正确查询。由于这个问题的目的是关注不需要范围锁的查询所以我更新了这个问题。

4

2 回答 2

3

REPEATABLE_READ不会挡住桌子。它保证事务在任何时候都看到相同的行。让我通过一个例子来解释它:

Time  Transaction 1                    Transaction2
 1    Begin Tx 1                        
 2                                     Begin Tx 2
 4    Select count(*) from my_tab;        
 5                                     Select count(*) from my_tab;
 6    Insert into ... my_tab;
 7    Commit;
 8                                     Select count(*) from my_tab;
 9                                     Insert into ... my_tab;
 10                                     Select count(*) from my_tab;
 11                                    Commit;
 12   Begin Tx3
 13   Select count(*) from my_tab;

如果 my_tab 有 10 行,那么计数的结果将是:

  • 时间 4 : 10 行
  • 时间 5 : 10 行
  • 时间 8 : 10 行,因为表处于 repeateble_read 模式,如果事务模式为 read_commited 也将是 10 行。但如果 Tx 设置为 en read_uncommited,则行数将为 11。
  • 时间 10:由于它处于可重复读取模式,因此计数将为 11 行(十个原件加上当前事务中的一个插入)。如果 tx 模式为 read_commited,则行数将为 12(10 个原件加上 tx1 的 1 个插入和当前 tx 的 1 个插入)。
  • 时间 13:这里所有事务模式的行数都是 12。

在可序列化模式下,每个事务都以单一模式执行,因此当事务不以提交或回滚结束时,没有一个事务可以启动。如果这种模式保证数据库的一致性会有严重的性能问题。

编辑

来自http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt中托管的 SQL-92 标准(第 67 和 68 页)(从维基百科获得):

SQL 事务的隔离级别为 READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 或 SERIALIZABLE。SQL 事务的隔离级别定义了对该 SQL 事务中的 SQL 数据或模式的操作受并发 SQL 事务中的 SQL 数据或模式的操作影响的程度,并可能影响对 SQL 数据或模式的操作。SQL 事务的隔离级别默认为 SERIALIZABLE。级别可以由 .

在隔离级别 SERIALIZABLE 的并发 SQL 事务的执行保证是可序列化的。可串行执行被定义为并行执行 SQL 事务的操作的执行,其产生与那些相同 SQL 事务的某些串行执行相同的效果。串行执行是每个 SQL 事务在下一个 SQL 事务开始之前执行完成。

隔离级别指定并发 SQL 事务执行期间可能发生的现象类型。可能出现以下现象:

1) P1 ("Dirty read"): SQL-transaction T1 修改一行。SQL 事务 T2 然后在 T1 执行 COMMIT 之前读取该行。如果 T1 然后执行 ROLLBACK,则 T2 将读取从未提交的行,因此可能被认为从未存在过。

2)P2(“Non-repeatable read”):SQL-transaction T1 读取一行。SQL-事务 T2 然后修改或删除该行并执行 COMMIT。如果 T1 然后尝试重新读取该行,它可能会收到修改后的值或发现该行已被删除。

3) P3 ("Phantom"):SQL 事务 T1 读取满足 some 的行集 N。SQL 事务 T2 然后执行 SQL 语句,生成满足 SQL 事务 T1 使用的一个或多个行。如果 SQL 事务 T1 然后用相同的 重复初始读取,它将获得不同的行集合。

四个隔离级别保证每个 SQL 事务将被完全执行或根本不执行,并且不会丢失任何更新。对于现象 P1、P2 和 P3,隔离级别不同。表 9,“SQL 事务隔离级别和三种现象”指定了给定隔离级别可能和不可能的现象。

           Level                P1         P2         P3
    ---------------------------------------------------------
    | READ UNCOMMITTED     | Possible | Possible | Possible |
    ---------------------------------------------------------
    | READ COMMITTED       | Not      |          |          |
    |                      | Possible | Possible | Possible |
    ---------------------------------------------------------
    | REPEATABLE READ      | Not      | Not      |          |
    |                      | Possible | Possible | Possible |
    ---------------------------------------------------------
    | SERIALIZABLE         | Not      | Not      | Not      |
    |                      | Possible | Possible | Possible |
    ---------------------------------------------------------------
    |                                                             |
    | Note: The exclusion of these phenomena or SQL-transactions  |
    |       executing at isolation level SERIALIZABLE is a        |
    |       consequence of the requirement that such transactions |
    |       consequence of the be serializable.                   |
    ---------------------------------------------------------------

编辑 2

好的,您对锁和块感兴趣,此时 RDMS 提供程序如何实现这一点可能会有所不同(例如 SQL Server 太奇怪了),但对于全局而言,这可能很有用,以下解释适用于您尝试修改/读取相同的数据(行)。RDMS 执行事务时有两种类型的锁:

  1. 共享锁:这种锁允许其他事务访问数据。例如,许多事务可以同时读取相同的数据。
  2. 排他锁:这种锁会阻塞资源,避免其他事务访问它,例如两个事务不能同时修改相同的数据,尝试修改资源的事务必须检查是否没有排他锁资源。如果资源上没有排他锁,则事务获得排他锁,直到它释放锁,没有一个事务可以获得锁,必须等待排他锁的释放。

这里的第二点是:什么被锁定?通常有两种类型的锁:

  1. 表级锁:当一个事务试图修改数据时,该事务获得所有表的排他锁,其他事务必须等待该事务释放锁。
  2. 行级锁:当一个事务试图修改数据时,该事务获得了该事务所涉及的行的排他锁,其他想要修改同一行子集的事务必须等到第一个事务结束。

关于死锁的注意事项,想象以下场景:

Time  Transaction 1                        Transaction 2
 1    Begin Tx 1                        
 2                                         Begin Tx 2
 4    Update table X set... where id = 5        
 5                                         Update table X set ... where id = 5;
 6    Update table X set... where id = 6
 7    Commit;
 8                                         Commit;

如果数据库配置为行级锁,那么事务 2 将在时间 5 等待到时间 7,因为事务 1 首先获得排他锁。现在想象以下示例:

Time  Transaction 1                        Transaction 2
 1    Begin Tx 1                        
 2                                         Begin Tx 2
 4    Update table X set... where id = 5        
 5                                         Update table X set ... where id = 6;
 6    Update table X set... where id = 6
 7                                         Update table X set ... where id = 5;
 8    Commit;
 9                                         Commit;

这种情况被称为“死锁”,因为在时间 6 中,事务 1 将等待在时间 5 上由事务 2 获得的锁的释放,但在时间 7 中,事务 2 必须等待由事务 1 及时获得的锁4.不同的RDBMS管理死锁的方式不同,例如MySQL with InnoDB会为Transaction 2引发死锁异常,让Transaction 1正常完成。

有一些有趣的文章:

最好的祝福

于 2013-07-31T01:54:38.353 回答
2

不会。REPEATABLE_READ隔离级别仅保证如果您SELECT在同一事务中重复执行相同的数据,您将看到相同的数据,这意味着(SQL)实现可能会隐藏其他事务执行的事务插入。

唯一能够排除插入的隔离级别是SERIALIZABLE因为它保证如果您有多个事务同时运行,结果将就好像它们按特定顺序顺序运行一样。没有指定或报告每种情况下的具体顺序,但可以保证存在一个会产生相同最终结果的顺序。

该问题的条件之一是与数据库无关(我假设是高级答案),因此我不会提及不同隔离级别如何在不同的数据库实现中工作或它们如何转换为锁。

于 2013-09-02T10:31:22.983 回答