事务

ACID 的含义

说到事务,大家第一反应是 Theo Härder 和 Andreas Reuter 于 1983 提出的 ACID。虽然 ACID 最初提出是为了为数据库中的容错保证给出一种相对精确的描述,但不同数据库对 ACID 的支持并不相同,尤其是 Isolation —— 隔离性。如今,ACID 更多的沦为一个 PR 术语。

下面,将逐一探究 Atomicity、Consistency、Isolation 和 Durability 的精确含义,以此来对事务所要做出的保证建立一个基本的认识。

 

原子性(Atomicity)

“原子”一般指最小单位,不可再分。在并发编程中,原子性通常和可见性关联,即一个线程无法看到另一个线程执行的原子操作的中间结果。

一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成。

 

一致性( Consistency)

在事务开始之前和事务结束以后,数据库的完整性没有被破坏。

 

隔离性(Isolation)

多个客户端并发访问数据库时,如果访问的数据没有交集,是可以随意并发的。但如果有交集,则会产生并发问题,或称竞态条件(race condition)。

隔离级别脏读不可重复读幻读
读未提交(Read uncommitted) 可能 可能 可能
读已提交(Read committed) 不可能 可能 可能
可重复读(Repeatable read) 不可能 不可能 可能
可串行化(Serializable ) 不可能 不可能 不可能

 

脏读(dirty reads):

设想一个事务已经将一些数据写入数据库,但事务还没有提交或中止。另一个事务可以看到未提交的数据吗?如果是的话,那就叫做脏读(dirty reads)。

脏写(dirty write):

事务a修改数据x=3,尚未完成的时候,事务b修改数据x=4,并修改完成之后,事务a才完成,这个时候,x的最终数据就是3,对于数据x来说,就会拿到脏数据。

不可重复读(nonrepeatable read):

是指在数据库访问中,一个事务范围内两个相同的查询却返回了不同数据。这是由于查询时系统中其他事务修改的提交而引起的。

幻读:

一个事务中的写入改变另一个事务的搜索查询的结果,被称为幻读。

幻读和不可重复读的区别:不可重复读是由于另一个事务对数据的更改所造成的,而幻读是由于另一个事务插入或删除引起的。

 

事务隔离级别的描述

1、读未提交(Read uncommitted)

在一个事务a执行过程中可以读取到其它事务中编辑后的数据,尽管其它事务还没有提交。

当事务a中读取到了事务b编辑后的数据后,事务b做了回滚操作。那么事务a读取到的数据就是脏数据(脏读);

当事务a在执行过程中对同一条查询语句执行了多次,但由于事务b在事务a多次查询过程中新增(insert)了数据,造成事务a多次查询的结果不一致,那么事务a的多次查询结果不一致的情况就是幻读;

当事务a在执行过程中对同一条查询语句执行了多次,但由于事务b在事务a多次查询过程中修改(update)或者删除(delete)了数据,造成事务a多次查询的结果不一致,那么事务a的多次查询结果不一致的情况就是不可重复读;

2、读已提交(Read committed)

在一个事务a执行过程中可以读取到其它事务编辑并提交(commit)后的数据,如果其它事务没有提交,则获取不到。

当事务a在执行过程中对同一条查询语句执行了多次,但由于事务b在事务a多次查询过程中修改(update)或者删除(delete)了数据,造成事务a多次查询的结果不一致,那么事务a的多次查询结果不一致的情况就是不可重复读;

在将MySql的事务隔离级别改为读已提交(Read committed)的时候,需要修改my.cnf配置文件,将二进制日志(bin log)存储格式改成ROW。并且隔离锁将失效。

3、可重复读(Repeatable read)

是MySql的默认事务隔离级别,在一个事务a执行过程中对同一条查询sql语句执行结果都是一样的,不受其它事务的影响。

当事务a在执行过程中对同一条查询语句执行了多次,但由于事务b在事务a多次查询过程中新增(insert)了数据,造成事务a多次查询的结果不一致,那么事务a的多次查询结果不一致的情况就是幻读;

where 范围操作的时候会产生隔离锁。 例如:update student set name = '小王' where id > 5 and id < 10;

隔离锁锁住的是id小于5的哪条数据 至 id大于10之间的数据。例如id在表中的值有(1,2,20,30),那么上面的sql语句锁住的范围未2 - 20之间。对于insert的隔离锁没有这种限制。

可使用(排他锁)for update或者共享锁(lock in share mode)来避免幻读:select * from table where id = 1 for update ; 或者select * from table where id = 1 lock in share mode;

4、可串行化(Serializable )

于可重复度(Repeatable read)比较接近,当autocommit被禁用后,innodb存储引擎会将每个select语句后面都加上 lock in share mode(共享锁);

持久性(Durability)

持久性是一种保证,即事务一旦提交,即使服务器宕机重启、甚至发生硬件故障,已经提交的事务所写入的数据就不会丢失。

在单机数据库中,持久性意味着以数据页(Page)或日志形式(WAL)写入了非易失性存储。在多副本(Replication)数据库中,持久性意味着写入了多数节点。

但,持久性都只能做到某种程度的保证,而非绝对保证,比如:

  1. 对于单机,可以容忍宕机。但磁盘坏了就完犊子。
  2. 对于多机,可以容忍少数副本损坏,但是多数副本完后也没辙。

因为在现实世界中,存储所涉及到的所有环节,都不是完美的:

  • 写入磁盘后宕机,虽然数据没丢,但机器修复或磁盘转移前,数据服务都是不可用的。但冗余(Replication)系统可以解决该问题。
  • 一个关联性故障,如软件 bug 或者机房断电,可以同时摧毁一个机房中的所有副本。因此多副本的内存数据库仍要定期持久化到外存。
  • 异步复制系统中,当主副本不可用时,由于没来得及同步到多数节点,最近的写入主副本成功的数据可能会丢失。
  • 当突然断电时,固态硬盘可能不能保证数据已完整刷盘,甚至用户显式调用 fsync 也无济于事。此外,硬盘驱动可能也会有 bug。
  • 磁盘上的数据可能会随着时间逐渐损坏,甚至副本数据也可能同时损坏,此时就只能依赖于备份了。

在实践中,要通过多种手段,比如强制刷盘、校验码、异地复制、定时备份等多种手段来保证数据的持久性,但也只能做到大概率的保证(比如五个九),而非绝对保证。

posted on 2022-06-12 11:24  torotoise512  阅读(117)  评论(0)    收藏  举报