事务:一组操作要么全部成功,要么全部失败,目的是为了保证数据最终的一致性。
事务四大特性:
A:原子性(Atomicity):当前事务的操作要么同时成功,要么同时失败,原子性由undo log日志来实现。
undo log:就是比如你是插入操作,日志里面就是删除此条数据,就是回滚时方便,保证原子性。
C:一致性(Consistency):使用事务的最终目的,由其他3个特性以及业务代码正确逻辑来实现。
I:隔离性(Isolation):在事务并发执行时,他们内部的操作不能互相干扰,隔离性由mysql的各种锁以及MVCC机制来实现。
D:持久性(Durability):一旦提交了事务,它对数据库的改变就应该是永久性的,持久性有redo log 日志来实现。
Innodb引擎中,定义了四种隔离级别供我们使用,级别越高事务隔离性越好,但性能就越低,而隔离性是由mysql的各种锁以及MVCC机制来实现的。
- read uncommit(读未提交):脏读:事务A读到了事务B已经修改但未提交的数据。
设置当前session的隔离级别:
begin;开启事务
rollback; #回滚
commit;提交事务
- read commit(读已提交):不可重复读
不可重复读:事务A内部的相同查询语句在不同时刻读出的结果不一致,受到了其他事务的影响。
底层实现原理:
- repeatable read(可重复读): 幻读。
别的事务的提交不会影响我自己事务的查询结果。
只要事务开启之后,有一条查询语句,就可认为数据库中所有的记录,包括所有表,都有一个状态,之后的查询语句查到都是第一次查询语句的记录(相当于一次快照数据)。但是如果对一条数据进行了修改,那之后查询这条数据就是最新的数据,而其他没修改的还是读的第一次快照数据。
新开启一个事务肯定查的是数据库中的最新数据。
底层实现原理:
rc:读已提交,rr:可重复读,两边在读,中间在写入,实现了读写并行执行,就是mvcc起作用。
其中trx_id是事务id,roll_pointer是回滚指针,指向undo log记录,比如你插入一条数据,指针指向的就是删除这条记录的undo log。
roll_pointer后来指的是上一版本的数据,每一条记录当有修改时都会有这样数据版本链。
幻读: 事务A读取到了事务B提交的新增数据。(注意是新增数据)
可重复读+间隙锁可以有效解决幻读现象。
oracle默认隔离级别是read commit, mysql默认隔离级别是repeatable read可重复读。
更新丢失或脏写?
上面三种读的是其他事务的数据或者本事务的快照版本数据,不是数据库的真实数据,然后java将这些数据重新计算后写入数据库就导致有bug。
当两个或多个事务选择同一行数据修改,有可能发生更新丢失问题,即最后的更新覆盖了由其他事务所做的更新。
怎么解决脏写问题?
1. 悲观锁:
2. 乐观锁:
增加一个字段version,每次修改数据这个版本号加1:
- serializable(串行):解决上面所有问题,包括脏写。幻读,脏读。
对同一条数据的读和写只能串行执行,a事务在执行读时,b事务对同一事务就不能执行更改操作
串行化的实现原理:
在select语句mysql底层会在后面加上lock in share mode; 读锁与写锁是互斥的,写锁与写锁之间也是互斥的。
读锁(共享锁,S锁):select... lock in share mode;
读锁是共享的,多个事务可以同时读取同一个资源,但不允许其他事务修改。
写锁(排它锁,X锁):select ... for update;
写锁是排他的,会阻塞其他的写锁和读锁,update,delete,insert都会加写锁。
MVCC(multi-version concurrency control)多版本并发控制,就可以做到读写不阻塞,且避免了类似脏读这样的问题,主要通过undo日志链来实现
select操作是快照读(历史版本)
insert, update和delete是当前读(当前版本)
read commit(读已提交),语句级快照
repeatable read(可重复读),事务级快照
面试题:只有查询操作方法,没有修改方法需要使用事务吗?
如果只有一个select语句,那就不需要加事务
如果有多个select语句,需要加读锁readonly=true, (写锁是readonly=false)
当隔离级别是rr时,需要注意在同一时间维度内,可重复读处理是同一时期的数据,(比如报表处理数据)
传统公司用的大部分是rr,同一时间维度,oa系统,erp系统,这种查询同一时间的数据,如果不对重新查询
互联网公司用的是rc,这种性能更高,
持久性:
一旦提交了事务,它对数据库的改变就应该是永久性的,持久性由redo log日志来实现。
磁盘顺序写(性能很高):写磁盘文件(ibd)是无法支持磁盘顺序写的,写redo日志文件是可以支持磁盘顺序写的。就是对应redo日志文件,比如十个sql,操作不同的数据表,这些表在磁盘文件中是不同的十个文件,而在redo日志文件是一个就可以追加日志文件编写。实现了磁盘顺序写。
疑问:redo日志文件中有很多记录,关联多张表,在写入磁盘文件时是怎么关联的?
事务的优化:
针对倒数第三条:更新放在事务靠后的位置:
分析:insert和update:两种update放在后面因为update操作是已经存在的数据,可能有别的事务也在操作这条数据,就会等待他提交完才能操作,insert操作的新数据,其他事务没有这条数据就无法操作。
针对最后一条:利用代码来实现回滚,对性能要求很高。适用于业务比较简单的
疑问:
mysql什么时候会用到事务?