我认为上述的隔离级别有很多相似之处。请问是否能给出一些好的例子来描述它们之间的主要区别?
我认为上述的隔离级别有很多相似之处。请问是否能给出一些好的例子来描述它们之间的主要区别?
读取提交是一种隔离级别,保证任何读取的数据在读取时都已经被提交。它简单地限制了读者看到任何中间的、未提交的“脏”读取。它不承诺如果事务重新发出读取,将找到相同的数据,数据在读取后可以自由更改。
可重复读是更高的隔离级别,除了读取提交级别的保证外,还保证任何读取的数据不能更改,如果事务再次读取相同的数据,它将在原地找到先前读取的数据,未更改且可供读取。
下一个隔离级别,可串行化,提供了更强的保证:除了可重复读取的所有保证外,它还保证后续读取不会看到任何新数据。
假设您有一个名为T的表格,其中包含一个C列并有一行,值为“1”。并考虑您有一个简单的任务如下:
BEGIN TRANSACTION;
SELECT * FROM T;
WAITFOR DELAY '00:01:00'
SELECT * FROM T;
COMMIT;
数据库的状态从事务开始时就被维护。如果你在session1中检索一个值,然后在session2中更新了该值,在session1中再次检索该值将返回相同的结果。读取是可重复的。
session1> BEGIN;
session1> SELECT firstname FROM names WHERE id = 7;
Aaron
session2> BEGIN;
session2> SELECT firstname FROM names WHERE id = 7;
Aaron
session2> UPDATE names SET firstname = 'Bob' WHERE id = 7;
session2> SELECT firstname FROM names WHERE id = 7;
Bob
session2> COMMIT;
session1> SELECT firstname FROM names WHERE id = 7;
Aaron
在事务的上下文中,您将始终检索到最近提交的值。如果您在session1中检索了一个值,在session2中更新它,然后再次在session1中检索它,您将获得作为session2中修改的该值。它会读取最后提交的行。
session1> BEGIN;
session1> SELECT firstname FROM names WHERE id = 7;
Aaron
session2> BEGIN;
session2> SELECT firstname FROM names WHERE id = 7;
Aaron
session2> UPDATE names SET firstname = 'Bob' WHERE id = 7;
session2> SELECT firstname FROM names WHERE id = 7;
Bob
session2> COMMIT;
session1> SELECT firstname FROM names WHERE id = 7;
Bob
懂了吗?
根据我阅读和理解这个线程以及@remus-rusanu的答案,简要回答如下:
有两个事务A和B。他们按照以下顺序执行以下操作。
这是一个已经有被接受答案的问题,但我认为可以从SQL Server中锁定行为的角度来考虑这两个隔离级别。对于像我一样调试死锁的人可能会有所帮助。
READ COMMITTED (默认)
在SELECT语句中获取共享锁,当SELECT语句完成时释放锁。这就是系统如何保证没有读取未提交数据的脏读。其他事务仍然可以在SELECT完成和您的事务完成之间更改底层行。
REPEATABLE READ
在SELECT语句中获取共享锁,直到事务完成后才释放锁。这就是系统如何保证在事务完成前读取的值不会更改(因为它们会一直保持锁定直到事务完成)。
这里有其他答案,但它们没有提供任何关于底层数据库架构的详细信息,这使得理解事务隔离级别的功能方式以及解决了哪些问题变得困难。
数据库系统允许多个并发连接。这导致了其他并发系统中出现的相同类型的问题。例如,在多线程环境中,互斥锁可以防止对内存的并发访问,从而解决了竞态条件问题,该问题可能导致数据损坏或无效。
类似地,因为数据库系统允许并发的CRUD操作(更新、插入、删除、选择),多个连接的并发操作可能会导致不良的观察行为。
请注意,数据库行操作的原子性可以防止直接数据损坏或不一致性,因此始终强制执行基本级别的事务隔离。
有关更多信息,请参见ACID。(原子性、一致性、隔离性、持久性。)简短的解释是,每行操作都是原子性的。这意味着防止数据损坏,因为它防止一种情况,即一个连接在另一个连接部分写入其数据之前,将一行数据的一部分写入,从而使另一个连接损坏该数据。 (对于熟悉多线程环境的人来说,这将更加直观。)
上述问题类似于多线程编程中出现的问题,其中一个线程开始向一块内存写入数据,然后另一个线程在第一个线程完成之前部分地将其数据写入相同的块,导致数据不一致。
首先了解行操作的原子性是很重要的,因为这已经提供了基本的保护级别。
我们将看一下以下事务隔离级别,这些级别在MariaDB和许多其他SQL数据库实现中都可用。
我们首先需要知道不同的隔离级别是什么:
在解释这些不同选项的作用之前,重要的是要了解每个选项解决的问题。以下是潜在的不良行为列表。
数据库操作通常被分组成一个事务。然后将此事务作为一组操作提交到数据库,或者执行回滚以丢弃该组操作。
如果一个连接开始了一系列操作作为一个事务,然后第二个连接开始从相同的表中读取数据,第二个连接可以从已经提交的数据库中读取数据,也可以读取作为打开但未提交的事务的一部分所做的更改。
这就定义了“读取已提交”和“读取未提交”的区别。
这在概念上是不寻常的,因为读取未提交的数据通常没有太多意义。事务的整个目的是确保数据库中的数据不会以这种方式发生更改。
总之:
鉴于上述情况,如果读取操作模式设置为“读取已提交”,则可能会发生不可重复读。此模式解决了脏读问题,因为只有提交的数据才会被读取。
可能的写入操作包括:
非重复读(Non-Repeatable Read)是指在读取一组行数据后,再次执行同样的操作,返回相同的行数据集合(相同的键),但是非键数据已经发生了变化。
例如:
where
子句。where
或其他过滤器子句中的选择条件定义。幻读是非重复读的扩展。插入或删除操作可能会更改返回的行数据集合。插入操作可能会向返回的行数据集合中添加新行。删除操作则可能相反,从返回的行数据集合中删除行。
总之:
在我们理解潜在的不良行为的基础上,这就是隔离级别所做的:
更高的隔离级别需要锁定数据库中更多的数据以防止并发访问。这可能是不理想的,因为如果DMBS在整个表上持有锁,则没有其他连接可以修改该数据。这可能导致需要访问数据库的其他进程停滞。
通常情况下,读已提交是最明智的选择。它确保您,数据库管理员,只看到已提交的数据(持久的数据,而不是短暂的数据),并且不会导致其他进程挂起。
进一步阅读:
Geeks for Geeks 隔离级别。(请注意,其中一些信息没有任何意义,例如“读取已提交”(Read Committed)的解释,它声称这会导致提交任何未提交的数据。这是不正确的,也没有意义。未提交的数据只能通过显式提交操作进行提交。)
我对初始接受的解决方案的观察。
在RR(默认mysql)下 - 如果一个tx是打开的并且已经触发了SELECT,那么另一个tx就不能删除任何属于先前READ结果集的行,直到先前的tx被提交(实际上,新tx中的删除语句将会挂起),但是下一个tx可以毫无问题地删除表中的所有行。顺便说一句,在先前的tx中进行的下一个READ操作仍将看到旧数据,直到它被提交。