Loading... ### ACID名词解释 * 原子性(`Atomicity`):事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么全部不起作用。 * 一致性(`Consistency`):执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额是保持不变的。 * 隔离性(`Isolation`):并发访问数据库时,一个用户的事务不被其他事务所干扰,各种并发事务之间的数据库是独立的。 * 持久性(`Durability`):一个事务被提交以后,它对数据库中的改变是持久的,即使是数据库发生故障也不应该对其有任何影响。 ### 并发事务带来的问题 users | id | name | age | | -- | ---- | --- | | 1 | Joe | 20 | | 2 | Jill | 25 | **脏读**(`Dirty read`):当一个事务正在访问数据并且对数据进行了修改,而这种修改`还没有提交到数据库中`,这时另一个事务也访问了这个数据,然后使用了这个数据。英文这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是`脏数据`,对脏数据进行的一系列操作都是不正确的。 <div class="flex-column"><div class="flex-block" style="flex:50%"> 事务1 ```sql /* Query 1 */ SELECT age FROM users WHERE id = 1; /* will read 20 */ ``` ```sql ``` ```sql /* Query 1 */ SELECT age FROM users WHERE id = 1; /* will read 21 */ ``` </div><div class="flex-block" style="flex:50%"> 事务2 ```sql ``` ```sql /* Query 2 */ UPDATE users SET age = 21 WHERE id = 1; /* No commit here */ ``` ```sql ROLLBACK; /* lock-based DIRTY READ */ ``` </div></div> > 当一个事务允许读取另外一个事务修改但未提交的数据时,就可能发生脏读(dirty reads)。 > > 脏读和不可重复读类似,不同点在于事务2不需要提交就能造成语句1两次执行的结果不同。在未提交读隔离级别唯一禁止的是更新混乱,即早期的更新可能出现在后来更新之前的结果集中。 > > 在我们的例子中,事务2修改了一行,但是没有提交,事务1读了这个没有提交的数据。现在如果事务2回滚了刚才的修改或者做了另外的修改的话,事务1中查到的数据就是不正确的了。 > > 在这个例子中,事务2回滚后就没有id是1,age是21的数据行了。 **不可重复读**(`Lost to modify`):指在一个事务内多次读统一数据。在这个事务还没结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复度。 <div class="flex-column"><div class="flex-block" style="flex:50%"> 事务1 ```sql /* Query 1 */ SELECT * FROM users WHERE id = 1; ``` ```sql ``` ```sql /* Query 1 */ SELECT * FROM users WHERE id = 1; COMMIT; /* lock-based REPEATABLE READ */ ``` </div><div class="flex-block" style="flex:50%"> 事务2 ```sql ``` ```sql /* Query 2 */ UPDATE users SET age = 21 WHERE id = 1; COMMIT; /* in multiversion concurrency control, or lock-based READ COMMITTED */ ``` ```sql ROLLBACK; /* lock-based DIRTY READ */ ``` </div></div> > 在这个例子中,事务2提交成功,因此他对id为1的行的修改就对其他事务可见了。但是事务1在此前已经从这行读到了另外一个“age”的值。在可序列化(SERIALIZABLE)和可重复读的隔离级别中,数据库在第二次SELECT请求的时必须返回更新之前的值。在提交读和未提交读中,返回的是更新之后的值,这个现象就是不可重复读。 > > 有两种策略可以避免不可重复读。一个是要求事务2延迟到事务1提交或者回滚之后再执行。这种方式实现了**T1, T2** 的串行化调度。串行化调度可以支持可重复读。 > > 另一种被用在多版本并发控制的策略是允许事务2先提交,这样能得到更好的并发性能。但因为事务1在事务2之前开始,事务1必须在其开始执行时间点的数据库的快照上面操作。当事务1最终提交时候,数据库会检查其结果是否等价于**T1, T2**串行调度。如果等价,则允许事务1提交,如果不等价,事务1需要回滚并抛出个串行化失败的错误。 > > 使用基于锁的并发控制,在可重复读的隔离级别中,ID=1的行会被锁住,在事务1提交或回滚前一直阻塞语句2的执行。在提交读的级别,语句1第二次执行,age已经被修改了。 > > 在多版本并发控制机制下,可序列化(SERIALIZABLE)级别,两次SELECT语句读到的数据都是事务1开始的快照,因此返回同样的数据。但是,如果事务1试图UPDATE这行数据,事务1会被要求回滚并抛出一个串行化失败的错误。 > > 在提交读隔离级别,每个语句读到的是语句执行前的快照,因此读到更新前后不同的值。在这种级别不会有串行化的错误(因为这种级别不要求串行化),事务1也不要求重试。 **幻读**(`Phantom read`):幻读与不可重复读类似。它发生在一个事务读取了几行数据,接着另一个并发事务出啊如了一些数据。在随后的查询中,第一个事务就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。 <div class="flex-column"><div class="flex-block" style="flex:50%"> 事务1 ```sql /* Query 1 */ SELECT * FROM users WHERE age BETWEEN 10 AND 30; ``` ```sql ``` ```sql /* Query 1 */ SELECT * FROM users WHERE age BETWEEN 10 AND 30; ``` </div><div class="flex-block" style="flex:50%"> 事务2 ```sql ``` ```sql /* Query 2 */ INSERT INTO users VALUES ( 3, 'Bob', 27 ); COMMIT; ``` ```sql ``` </div></div> > 需要指出的是事务1执行了两遍同样的查询语句。如果设了最高的隔离级别,两次会得到同样的结果集,这也正是数据库在可序列化(SERIALIZABLE)隔离级别上需要满足的。但是在较低的隔离级别上,第二次查询可能会得到不同的结果集。 > > 在可序列化隔离级别,查询语句1在age从10到30的记录上加锁,事务2只能阻塞直至事务1提交。在可重复读级别,这个范围不会被锁定,允许记录插入,因此第二次执行语句1的结果中会包括新插入的行。 **丢失修改**(`Lost to modify`):指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 <div class="flex-column"><div class="flex-block" style="flex:50%"> 事务1 ```sql /* Query 1 */ UPDATE users SET age = 21 WHERE id = 1; /* No commit here */ ``` ```sql ``` ```sql /* Query 1 */ SELECT * FROM users WHERE id = 1; /* will read 25 ,21 is lost*/ ``` </div><div class="flex-block" style="flex:50%"> 事务2 ```sql ``` ```sql /* Query 2 */ UPDATE users SET age = 25 WHERE id = 1; /* No commit here */ ``` ```sql /* Query 2 */ SELECT * FROM users WHERE id = 1; /* will read 25 */ ``` </div></div> 最后修改:2022 年 12 月 09 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得我的文章对你有用,请随意赞赏