事务隔离级别与表上的锁的关系

Posted

技术标签:

【中文标题】事务隔离级别与表上的锁的关系【英文标题】:Transaction isolation levels relation with locks on table 【发布时间】:2013-04-16 06:07:09 【问题描述】:

我已经阅读了大约 4 个隔离级别:

Isolation Level       Dirty Read    Nonrepeatable Read  Phantom Read  
READ UNCOMMITTED      Permitted       Permitted           Permitted
READ COMMITTED              --        Permitted           Permitted
REPEATABLE READ             --             --             Permitted
SERIALIZABLE                --             --              --

我想了解每个事务隔离对表的锁

READ UNCOMMITTED - no lock on table
READ COMMITTED - lock on committed data
REPEATABLE READ - lock on block of sql(which is selected by using select query)
SERIALIZABLE - lock on full table(on which Select query is fired)

以下是事务隔离中可能出现的三种现象 脏读- 无锁 Nonrepeatable Read - 没有脏读作为对已提交数据的锁定 幻读 - 锁定sql块(使用select查询选择)

我想了解我们在哪里定义这些隔离级别:仅在 jdbc/hibernate 级别或在 DB 中也

PS:我已经浏览了Isolation levels in oracle 中的链接,但它们看起来很笨拙,并且在谈论特定于数据库的内容

【问题讨论】:

这完全取决于数据库。不同的数据库可能对隔离级别使用不同的算法。有些可能使用 MVCC(选择查询不加锁),有些使用严格的 2 阶段锁定(共享锁和排他锁)。 【参考方案1】:

我想了解每个事务隔离对表的锁定

例如,您有 3 个并发进程 A、B 和 C。A 启动事务、写入数据和提交/回滚(取决于结果)。 B 只是执行SELECT 语句来读取数据。 C 读取和更新数据。所有这些过程都在同一张表 T 上工作。

READ UNCOMMITTED - 表上没有锁。您可以在写表的同时读取表中的数据。这意味着 A 写入数据(未提交),B 可以读取此未提交数据并使用它(用于任何目的)。如果 A 执行回滚,B 仍然读取并使用了数据。这是处理数据的最快但最不安全的方式,因为可能会导致在物理上不相关的表中出现数据漏洞(是的,在实际应用中,两个表可以在逻辑上但在物理上不相关 =\)。 READ COMMITTED - 锁定已提交的数据。您可以读取仅提交的数据。这意味着 A 写入数据而 B 无法读取 A 保存的数据,直到 A 执行提交。这里的问题是 C 可以更新在 B 上读取和使用的数据,而 B 客户端将没有更新的数据。 REPEATABLE READ - 锁定一个 SQL 块(使用 select 查询选择)。这意味着B在某些条件下读取数据,即WHERE aField > 10 AND aField < 20,A插入aField值在10到20之间的数据,然后B再次读取数据并得到不同的结果。 SERIALIZABLE - 锁定一个完整的表(在该表上触发 Select 查询)。这意味着,B 读取数据并且没有其他事务可以修改表上的数据。这是处理数据的最安全但最慢的方式。另外,由于一个简单的读操作锁定了,这会导致生产上的严重问题:假设 T 表是 Invoice 表,用户 X 想知道当天的发票,用户 Y 想创建一张新发票,所以当 X 执行发票读取时,Y 不能添加新发票(当涉及到钱时,人们真的很生气,尤其是老板)。

我想了解我们在哪里定义这些隔离级别:仅在 JDBC/休眠级别或也在 DB 中

使用 JDBC,您可以使用 Connection#setTransactionIsolation 定义它。

使用休眠:

<property name="hibernate.connection.isolation">2</property>

在哪里

1:读取未提交 2:已提交阅读 4:可重复读取 8:可序列化

Hibernate 配置取自 here(抱歉,它是西班牙语)。

顺便说一句,您也可以在 RDBMS 上设置隔离级别:

mysql isolation level, SQL Server isolation level Informix isolation level(个人注:我永远不会忘记SET ISOLATION TO DIRTY READ这句话。)

不断……

【讨论】:

此外,为了节省使用 SET TRANSACTION 语句开始每个事务的网络和处理成本,您可以使用 ALTER SESSION 语句为所有后续事务设置事务隔离级别:ALTER SESSION SET ISOLATION_LEVEL SERIALIZABLE ; ALTER SESSION SET ISOLATION_LEVEL READ COMMITTED; 关于 REPEATABLE READ - 我认为一个更好的例子来证明它如下:B 开始一个事务,读取 sql 块上的数据 WHERE aField > 10 AND aField @LuiggiMendoza 作为一般概念,隔离级别大约是Dirty ReadNon-Repeatable ReadPhantom Rows .锁 (S2PL) 或 MVCC 是针对不同供应商的实现。 @LuiggiMendoza - 我不准确,应该是这样的 - B 读取的数据没有改变,但是 B 进行的后续选择可能会返回更多行。这是因为 A 不能修改 B 已经读取的行,直到 A 释放它们。但是,A 可以插入符合 where 条件的新行(因此,下次 A 将执行选择时,它会得到具有更多行的不同结果 - 幻读)。 @NitinBansal - 是的,这是错字。应该是“A 不能修改 B 已经读取的行,直到 B 释放它们”。【参考方案2】:

正如 brb tea 所说,取决于数据库实现和他们使用的算法:MVCC 或两相锁定。

CUBRID(开源RDBMS)explains这两种算法的思路:

两相锁定 (2PL)

第一个是T2事务试图改变A记录的时候, 它知道 T1 事务已经改变了 A 记录并且 等待直到 T1 事务完成,因为 T2 事务 无法知道 T1 事务是否将被提交或滚动 背部。这种方法称为两阶段锁定(2PL)。

多版本并发控制 (MVCC)

另一个是允许它们中的每一个,T1 和 T2 事务, 有自己的更改版本。即使 T1 交易有 将 A 记录从 1 更改为 2,T1 事务离开 原值 1 并写入 T1 交易版本 A 的记录是 2。那么,下面的 T2 事务改变了 A 记录从 1 到 3,而不是从 2 到 4,并写道 T2 A记录的交易版本为3。

T1事务回滚时,2、 T1 交易版本,不适用于 A 记录。后 即,如果 T2 事务被提交,则 3、T2 事务 版本,将应用于 A 记录。如果 T1 交易是 在 T2 事务之前提交,A 记录更改为 2, 然后在提交 T2 事务时到 3。决赛 数据库状态与执行每个的状态相同 交易独立,不影响其他交易。 因此,它满足 ACID 属性。这个方法被称为 多版本并发控制 (MVCC)。

MVCC 允许并发修改,但会增加内存开销(因为它必须维护相同数据的不同版本)和计算(在 REPETEABLE_READ 级别,您不能松动更新,因此它必须检查数据的版本,就像 Hiberate 对 Optimistick Locking 所做的那样。

在 2PL Transaction isolation levels control the following:

读取数据时是否加锁,请求什么类型的锁。

读锁持有多长时间。

一个读操作是否引用了另一个事务修改的行:

阻塞直到行上的排他锁被释放。

检索语句或事务开始时存在的行的提交版本。

读取未提交的数据修改。

选择事务隔离级别不会影响锁 被收购以保护数据修改。交易总是得到 对它修改的任何数据的排他锁并持有该锁直到 事务完成,无论为 那笔交易。对于读操作,事务隔离级别 主要定义免受影响的保护级别 其他交易进行的修改。

较低的隔离级别增加了许多用户访问的能力 同时数据,但增加了并发数 影响,例如脏读或丢失更新,用户可能 相遇。

SQL Server 中锁和隔离级别之间关系的具体示例(使用 2PL,除了 READ_COMMITED 和 READ_COMMITTED_SNAPSHOT=ON)

READ_UNCOMMITED:不发出共享锁以防止其他事务修改当前事务读取的数据。 READ UNCOMMITTED 事务也不会被排他锁阻塞,排他锁会阻止当前事务读取已修改但未由其他事务提交的行。 [...]

READ_COMMITED:

如果 READ_COMMITTED_SNAPSHOT 设置为 OFF(默认值):使用共享锁来防止其他事务在当前事务正在运行读取操作时修改行。共享锁还阻止语句读取其他事务修改的行,直到其他事务完成。 [...] 在处理下一行之前释放行锁。 [...] 如果 READ_COMMITTED_SNAPSHOT 设置为 ON,则数据库引擎使用行版本控制为每个语句提供一个事务一致的数据快照,因为它在语句开始时就存在。锁不用于保护数据不被其他事务更新。

REPETEABLE_READ:共享锁被放置在事务中每个语句读取的所有数据上,并一直保持到事务完成。

SERIALIZABLE:范围锁放置在与事务中执行的每个语句的搜索条件匹配的键值范围内。 [...] 范围锁一直保持到事务完成。

【讨论】:

【参考方案3】:

锁总是在 DB 级别获取:-

Oracle 官方文档:- 为了避免事务期间的冲突,DBMS 使用锁,这是一种阻止其他人访问事务正在访问的数据的机制。 (请注意,在自动提交模式下,每条语句都是一个事务,只有一个语句持有锁。)设置锁后,它一直有效,直到事务被提交或回滚。例如,DBMS 可以锁定表的一行,直到提交更新。这个锁的作用是防止用户进行脏读,也就是说,在一个值被永久化之前读取它。 (访问尚未提交的更新值被视为脏读,因为该值有可能回滚到其先前的值。如果您读取的值稍后回滚,则您将读取无效值。 )

如何设置锁取决于所谓的事务隔离级别,范围可以从根本不支持事务到支持执行非常严格的访问规则的事务。

事务隔离级别的一个示例是 TRANSACTION_READ_COMMITTED,它不允许在提交值之前访问它。换言之,如果事务隔离级别设置为 TRANSACTION_READ_COMMITTED,则 DBMS 不允许发生脏读。接口 Connection 包含五个值,代表您可以在 JDBC 中使用的事务隔离级别。

【讨论】:

以上是关于事务隔离级别与表上的锁的关系的主要内容,如果未能解决你的问题,请参考以下文章

mysql事务隔离级别与锁的关系

性能优化|MVCC通俗理解与事务隔离级别实战操作

Innodb中的事务隔离级别和锁的关系

Innodb中的事务隔离级别和锁的关系

MySql事务隔离级别

白话Mysql的锁和事务隔离级别!死锁间隙锁你都知道吗?