事务是数据库管理系统(DBMS)执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。
------维基百科的定义
MySQL中 InnoDB支持事务,这个也是它成为默认的存储引擎的一个重要原因,对于需要事务支持的业务场景有更好的适用性
数据库事务
事务的四大特性
事务的四大特性:ACID
1.原子性,Atomicity 原子(atom)是化学反应中不可再分的基本微粒,顾名思义,这也就说明对数据库的一系列的操作,是不可分割的整体,要么都是成功,要么都是失败,不可能出现部分成功或者部分失败的情况。
原子性,在InnoDB里面是通过undo log来实现的,它记录了数据修改之前的值(逻辑日志),一旦发生异常,就可以用undo log来实现回滚操作。
2.一致性,consistent 所说的是数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态。比如主键必须是唯一的,字段长度需要符合要求。除了数据库自身的完整性约束,还有一个是用户自定义的完整性。
以转账场景来举例,A账户余额减少1000,B账户余额只增加了500,这个时候就没有满足一致性。
用户自定义的完整性通常要在代码中控制。
3.隔离性,Isolation 在数据库里面会有很多的事务同时去操作同一张表或者同一行数据,必然会产生一些并发或者干扰的操作。所谓的隔离性,就是多个事务,对表或者行的并发操作,应该是互相不干扰的。这样来可以保证业务数据的一致性。
4.持久性,Durable 所谓持久性就是或我们对数据库的任意的操作,增删改,只要事务提交成功,那么结果就是永久性的,不会因为我们系统宕机或者重启了数据库的服务器,又恢复到原来的状态了。
数据库崩溃恢复(crash-safe)是通过redo log和double write双写缓冲来实现的,我们操作数据的时候,会先将其写到内存的buffer pool里面,同时记录redo log,如果在数据刷盘之前出现异常,在重启后就可以读取redo log的内容,写入到磁盘,保证数据的持久性。当然,恢复成功的前提是数据页本身没有被破坏,是完整的,这个通过双写缓冲(double write)保证。
事务的开启方式
事务开启分为两种方式,手动开启提交和自动开启提交。
我们平时写一个SQL:update user set name='chenpp' where id=1;这里就已经自动开启了一个事务,并且提交了。
InnoDB里面有一个autocommit的参数(分成两个级别, session级别和global级别),可以通过set session autocommit = on/off; -- 设定事务是否自动开启 。平时是默认开启的
手动开启事务也有两种方式,一种是使用begin;另一种是用start transaction。
结束事务也有两种方式,提交一个事务commit;或者回滚 rollback
还有一种情况,客户端的连接断开的时候,事务也会结束。
事务并发会造成的问题
当多个事务并发操作数据库的表或者行的时候,如果没有事务的Isolation隔离性,会带来哪些问题呢?
事务并发的三大问题:脏读,不可重复读,幻读
脏读
这种读取到其他事务未提交的数据的情况,我们把它叫做脏读。
不可重复读
这种一个事务读取到了其他事务已提交的数据导致前后两次读取数据不一致的情况,我们把它叫做不可重复读。
幻读
由于其他事务插入数据造成一个事务前后两次读取数据数据不一致,这种情况我们把它叫做幻读。
不可重复读和幻读的区别在于 不可重复读是由于修改或者删除导致,幻读是由于插入导致的
无论是脏读,还是不可重复读,幻读,都是数据库的读一致性的问题,都是在一个事务里面前后两次读取出现了不一致的情况。
读一致性的问题,必须要由数据库提供一定的事务隔离机制来解决
SQL92 标准
http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt
Possible代表在这个隔离级别下,这个问题有可能发生
第一个隔离级别:Read Uncommitted(未提交读),一个事务可以读取到其他事务未提交的数据,可能出现脏读,它没有解决任何的问题。
第二个隔离级别:Read Committed(已提交读),一个事务只能读取到其他事务已提交的数据,不能读取到其他事务未提交的数据,它解决了脏读的问题,但是可能会出现不可重复读的问题。
第三个隔离级别:Repeatable Read (可重复读),它解决了不可重复读的问题,就是说在同一个事务里面多次执行同样的SQ其返回的数据结果是一样的,但是在这个级别下,没有解决幻读的问题。
第四个隔离级别:Serializable(串行化),在这个隔离级别里面,所有的事务都是串行执行的,也就不存在事务的并发操作问题了,所以它解决了所有的问题。
MySQL InnoDB 对隔离级别的支持
在MySQL InnoDB里面,不需要使用串行化的隔离级别去解决所有问题。
看下MySQL InnoDB里面对数据库事务隔离级别的支持程度是什么样的
注意一下,InnoDB在Repeatable Read(RR)的级别就解决了幻读的问题。这个也是InnoDB默认使用RR作为事务隔离级别的原因,既保证了数据的一致性,又支持较高的并发度。
解决读一致性的实现方案
LBCC
要保证前后两次读取数据一致,那么直接在读取数据的时候,锁定读取的数据,不允许其他的事务修改就行了。这种方案我们叫做基于锁的并发控制Lock Based Concurrency Control(LBCC)。
如果仅仅是基于锁来实现事务隔离,一个事务读取的时候是不允许其他事务修改的,那就意味着不支持并发的读写操作,这样会极大地影响操作数据的效率,毕竟我们的大多数应用都是读多写少的
MVCC
另一种解决方就是使用快照,就是在修改数据的时候给它建立一个备份或者快照,后面在本事务里再次执行的时候只要读取这个快照就行了。这种方案我们叫做多版本的并发控制 Multi Version Concurrency Control(MVCC)。MVCC使得InnoDB的事务隔离级别下执行一致性读操作有了保证。简单说就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值
MVCC的核心思想是: 我可以查到在我这个事务开始之前已经存在的数据,即使它在后面被修改或者删除了。而在我这个事务开始之后新增的数据,我是查不到的。
为了实现MVCC , InnoDB为每行记录都实现了两个隐藏字段:
DB_TRX_ID,6字节:插入或更新该行的最后一个事务的事务ID,事务编号是自动递增的(可以理解为类似创建版本号)
DB_ROLL_PTR,7字节:回滚指针(可以理解为删除版本号,当数据被删除或记录为旧数据的时候,当前的事务ID)。
看下MVCC在不同场景下的使用:
1)新增数据
事务1插入了2条数据并提交,事务2此时查询了当前表,可以看到2条记录,此时事务3又插入了一条新的记录,之后事务2又查询了一次
MVCC的查找规则:只能查找创建时间小于等于当前事务ID的数据,和删除时间大于当前事务ID的行(或未删除)。
也就是说不能查到在 本次事务开始之后插入的数据,Bob的创建事务ID大于2,所以还是只能查到两条记录。
2)删除数据
事务3删除了id=1的数据,事务2此时又查询了当前表
Bob的创建版本大于2不满足,chenpp的删除版本大于2仍旧满足,所以查询到的还是id=1和id=2两条记录
3)修改数据
此时事务5执行了一个更新SQL update user set name='Mon' where id=2; ,之后事务2又查询了一次
Mon,Bob创建版本大于2不满足,CoCo删除版本大于2仍旧满足,所以还是2条记录
在InnoDB中,MVCC是通过Undo log实现的。
快照读和当前读
- 快照读:读取的是快照版本,也就是历史版本
- 当前读:读取的是最新版版
在InnoDB中,MVCC和锁是协同使用的:在RR隔离级别下,普通的select使用快照读(snapshot read),底层使用MVCC来实现。
加锁的 select(select ... in share mode / select ... for update)以及更新操作update, delete 等语句使用当前读(current read),底层基于锁实现(LBCC)。