ACID原则

ACID原则是数据库事务正常执行的四个基本原则,分别指:原子性、一致性、独立性及持久性。

1、事务的原子性(Atomicity):是指一个事务要么全部执行,要么都不执行,也就是说一个事务不可能只执行了一半就停止了。比如取款机取钱,这个事务可以分成两个步骤:1划卡,2出钱,不可能划了卡,而钱却没出来,这两步必须同时完成,要么就不完成。

2、事务的一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。

3、独立性(Isolation): 也有称作隔离性,是指同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。

4、持久性(Durability): 是指事务执行成功以后,该事务所对数据库所作的更改便是持久的保存在数据库之中,不会无缘无故的回滚。

如何实现ACID

1、Write ahead logging(日志式方式)

中心思想是先把日志记录冲刷到永久存储器之后,再对数据(表数据+索引数据文件)做修改。

在出现崩溃的情况下,日志中存在但尚未附加到数据页中的记录,都将向前滚动恢复 - REDO;

那些未提交的事务做的修改将被从数据页中删除,这叫向后滚动恢复 - UNDO。

缺点:日志记录开销大、恢复速度慢

2、Shadow paging

缺点:事务提交时要输出多个块,这使得提交的开销很大,而且以块为单位,很难应用到允许多个事务并发执行的情况

事务的并发问题

1、脏读:事务A读取了事务B更新后的数据,事务B发生了回滚,那么事务A读到的是脏数据;

2、不可重复读:事务A多次读取同一数据,事务B在事务A读取过程中,对数据做了更新并提交,那事务A多次读取到的数据不一致;

3、幻读:事务A在对一批数据进行更新操作时,事务B又新增了一条未更新的记录,这时事务A更新完成后会发现多了一条未更新的记录;

小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

MySQL事务隔离级别(从低到高)

事务隔离级别

脏读

不可重复读

幻读

读未提交(read-uncommitted)

不可重复读(read-committed)

可重复读(repeatable-read)

串行化(serializable)

mysql默认的事务隔离级别为可重复读

1、事务隔离级别为读提交时,写数据只会锁住相应的行

2、事务隔离级别为可重复读时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key 锁;

如果检索条件没有索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。

3、事务隔离级别为串行化时,读写数据都会锁住整张表

4、隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。

为什么可重复读不会读到最新数据呢?

可重复读的隔离级别下使用了MVCC机制,select操作不会更新版本号,是快照读(历史版本);

insert、update和delete会更新版本号,是当前读(当前版本)。

MVCC机制

1.1 什么是MVCC

MVCC是一种多版本并发控制机制。

1.2 MVCC是为了解决什么问题?

大多数的MYSQL事务型存储引擎,如InnoDB、Falcon以及PBXT都不使用一种简单的行锁机制。

事实上,他们都和MVCC–多版本并发控制来一起使用

锁机制可以控制并发操作,但是其系统开销较大,而MVCC可以在大多数情况下代替行级锁,使用MVCC能降低其系统开销

1.3 MVCC实现

MVCC是通过保存数据在某个时间点的快照来实现的,不同存储引擎的MVCC实现是不同的,典型的有乐观并发控制和悲观并发控制

InnoDB如何实现MVCC

innoDB表中的每行记录后面都有隐藏的两列:创建事务id(create_id)、删除事务id(delete_id)  [英文是我自己瞎起的]

select时会查询满足以下两个条件的结果:

1、create_id <= 当前id

2、delete_id = undefined 或者 delete_id >= 当前id

update时会新增一条create_id为当前事务id,同时将原有的数据行delete_id更改为当前事务id

delete将原有的数据行delete_id更改为当前事务id

insert时新增一条create_id为当前事务id,delete_id为undefined的数据行

事务传播行为

1. 什么是事务传播行为?

事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。

用伪代码说明:

public void methodA(){

methodB();

//doSomething

}

@Transaction(Propagation=XXX)

public void methodB(){

//doSomething

}

methodA()方法嵌套调用了methodB()方法,methodB()的事务传播行为由@Transaction(Propagation=XXX)设置决定。

这里需要注意的是methodA()并没有开启事务,某一个事务传播行为修饰的方法并不是必须要在开启事务的外围方法中调用。

2. Spring中七种事务传播行为 范例

事务传播行为类型

说明

PROPAGATION_REQUIRED

如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。

PROPAGATION_SUPPORTS

支持当前事务,如果当前没有事务,就以非事务方式执行。

PROPAGATION_MANDATORY

使用当前的事务,如果当前没有事务,就抛出异常。

PROPAGATION_REQUIRES_NEW

新建事务,如果当前存在事务,把当前事务挂起。

PROPAGATION_NOT_SUPPORTED

以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

PROPAGATION_NEVER

以非事务方式执行,如果当前存在事务,则抛出异常。

PROPAGATION_NESTED

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。