ACID
原子性(Atomicity)
事务包含的所有数据库操作,要么全部成功,要么全部失败回滚
一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态,变换到另一个一致性状态。一致性规定事务提交前后只存在两个状态,不会有中间状态
用通俗一点的话说,就是:一致性是指数据处于一种语义上的有意义且正确的状态。一致性是对数据可见性的约束,保证在一个事务中的多次操作的数据中间状态对其他事务不可见的。因为这些中间状态,是一个过渡状态,与事务的开始状态和事务的结束状态是不一致的
隔离性(Isolation)
当多个用户并发访问数据库时,比如同时操作一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离
从用户的角度,对于任意两个并发的事务
但在实践中,隔离性并没有被严格遵守,具体的隔离性与隔离级别有关,这是并发与安全的trade off
持久性(Durability)
一个事务一旦被提交了,那么对数据库中的数据改变就是永久性的,即便是在数据库系统遇到故障的情况下,也不会丢失提交事务的操作
事务并发时存在的问题
脏读(Dirty Read)
脏数据,就是未提交的数据,而脏读是指在一个事务处理过程中,读取了另一个未提交的事务中的数据,比如:
......(修改了A) | ...... |
获取A | |
......(触发回滚,反悔了对A的修改) | ......(使用了A) |
脏读的危害:我拿到了一个数据
类似的,还有脏写:我写入了一个数据
......(修改了A: |
...... |
...... | ......(修改了A: |
......(触发回滚,返回了对A的修改: |
......(发现A变成 |
不可重复读(Non-repeatable Read)
一个事务先后读取同一条记录,而在事务两次读取之间,该数据被其他事务所修改,则两次读取的数据不同
...... | ...... |
...... | 获取A,值为 |
修改A,并commit | ...... |
获取A,值为 |
|
...... |
幻读(Phantom Read)
一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据
...... | ...... |
...... | 修改全部数据行 |
插入一行新数据 |
...... |
...... | 发现 |
脏读(读取到未提交的数据)
不可重复读(单点查询操作,发现在自己未修改的情况下,两次查询结果不同)
幻读(集合查询操作,发现在自己未修改的情况下,加入了新元素)
四个隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 | 应用 |
---|---|---|---|---|
读未提交 | F | F | F | 没什么实际应用 |
读已提交 | T | F | F | |
可重复读 | T | T | F | MySQL默认级别 |
串行化 | T | T | T | 通常不会使用 |
读未提交(Read Uncommitted)
所有事务都能看到其他未提交事务的执行结果
读已提交(Read Commited)
一个事务能看见已提交事务做出的改变,可以防止脏读问题
实现方式
- 加锁:数据库通过加锁来实现读已提交,当一个事务开始读取数据时,数据库会对相应的数据行或者表进行锁定,以防止其他事务对该数据进行修改。只有在事务提交之后,其他事务才能访问并修改这些数据
- 多版本并发控制(MVCC):MVCC为每个事务创建一个可见版本来实现隔离。当一个事务开始时,它只能看见已经提交的版本。其他事务对数据的修改会创建新版本,并且只有在事务提交后,其他事务才能看到这些修改
可重复读(Repeatable Read)
这是MySQL默认的事务隔离级别,它确保一个事务的多个实例在并发读取数据时,会看到同样的数据行
在一个事务中,多个实例指的是同一种事务在不同的情况下的多次发生。事务可以是指某种事件、活动、任务或过程。每当该事务发生一次,就会产生一个新的实例
例如,假设有一个名为"购买商品"的事务。每当一个人购买商品,就会创建一个新的实例。如果有多个人在同一时间内购买商品,就会有多个实例同时进行
每个实例都是独立的、具有自己的上下文和特定的属性,但它们都属于同一个事务。每个实例可以具有不同的参数、变量和结果,取决于特定的情况和上下文。多个实例的存在可以帮助我们跟踪和管理事务的不同状态,同时处理多个实例可以提高效率和并行性
实现方式
- 快照隔离:事务开始时获取一个数据的快照,该快照代表了事务开始时表的状态。当其他事务对数据进行修改时,事务仍然使用最初的快照来读取数据,而不会看到其他事务的修改
串行化(Serializable)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突。它可以解决所有并发问题,但可能导致大量的超时现象和锁竞争,通常数据库不会用这个级别,我们需要其他的机制来解决这些问题:乐观锁和悲观锁
实践指南
生产环境下大多使用RC隔离级别:
- 在RR隔离级别下,存在间隙锁,导致出现死锁的几率比RC大得多
- 在RR隔离级别下,条件列未命中索引会锁表,而在RC隔离级别下,只锁行
也就是说,RC的并发性高于RR
并且大部分情况下,不可重复读的问题是可以接受的,毕竟数据都已经提交了,读出来“落后于时代的值”,又读出来一个“新值”,是可以进行人为取舍的