MySQL事务
MySQL事务四大特性
原子性:
事务操作要么同时成功,要么同时失败,原子性由undo log日志来实现;
一致性:
使用事务的最终目的,由其它三个特性以及业务代码正确逻辑来实现;
隔离性:
在事务并发来执行时,它们内部的操作不能互相干扰,隔离性由MySQL的各种锁以及MVCC机制来实现;
隔离级别:
Read uncommit(读未提交):
脏读
读到了别的事务未提交的数据;
Read commit(读已提交):
不可重复读
同一个事务中,如果其它事务对数据做修改了,那么每次读到的数据不一样;
对于读已提交,因为同一个事务中,读取到的值可能不一致,所以可以在数据库表中,添加版本号字段,根据版本号,先查询出来需要的数据,以及版本号,然后更新数据的时候,条件多添加一个版本号,外层进行循环,只有版本号一致的时候,才会进行提交;
Repeatable read(可重复读):
幻读
同一个事务中,第一次读的时候就相当于当前事务对读取的整个数据库的所有表有一个快照,在当前事务中,无论如何去读,都是和最开始的结果一致;
每开启一个可重复读的事务,它第一次读数据时,都会对数据库的所有表数据生成一个新的当时的快照;
而这种情况会出现脏写的问题,因为无论何时去读取数据库,都是最开始的快照数据,所以这种脏写问题,就是不要把数据拿出来,在java层面去操作,而是在数据库SQL语句上去操作,
这里的update更新操作它就是读取当前数据库已经提交的最新值(当前读),修改之后当前事务再去读,就不是快照了,就是当前已经修改的值(仅限于当前修改行);
而update(insert,delete都会)会添加行锁(悲观锁),当前事务如果进行update后,没有提交,其它事务就不能update;
幻读
操作了当前事务之前没有查询到的数据,当前第一次查询是三条数据,后来其它事务提交了一条数据,当天事务进行update的时候,因为是当前读,它就对新增的数据做了修改,但是我们只查询到了三条,却对第四条做了修改,这就是幻读
Serilizable(串行):
解决所有问题,包括脏写
在当前事务中对数据进行写操作时,其它事务不能对当前数据进行读和写;
在当前事务对数据进行读的时候,其它事务可以执行读,但是不能执行写操作;
这里不得不提到数据库的读锁和写锁,读锁和写锁是互斥的,写锁和写锁也是互斥的,就是对当前数据添加读锁后,就不能添加写锁了,对当前数据添加写锁,其它事务也不能对它再添加写锁;
读锁:
select * from table1 where id=1 lock in share mode;lock in share mode就是添加读锁
Serilizable隔离级别,就是MySQL在底层的读上添加了lock in share mode;
读锁是共享的,多个事务可以同时读取一个资源,但是不允许其它事务修改;
写锁:
写锁是排它锁,会阻塞其它读锁和写锁,update,delete,insert都会默认添加写锁,select ....for update也是添加写锁;
串行化之前的三种隔离级别默认都不会添加读锁;所有的写操作都会添加写锁;
数据库MVCC机制
数据库多版本并发控制,为了支持并发情况下的读和写操作不阻塞,主要用undo_log来实现;
读已提交(RC)它的原理是,在数据的undo日志链中,总是读时总是指向有提交标记的那条数据,具体是通过一套算法来实现;
可重复读(RR)它是在数据库的undo日志链中,总是能够读到它第一次读的那个版本编号的数据,具体通过算法来实现;
当我们操作数据时,数据库除了有我们定义的字段,数据库底层还维护了trx_id和roll_pointer;
trx_id是事务版本编号,每一个事务都有唯一的事务版本编号;
roll_pointer是undo_log的指针,指向undo_log,也就是当前操作执行之前的日志,可以用来回滚的;当第二次对同一个数据进行操作的时候,它的roll_pointer其实指向的是第一次的操作日志;
每条数据都有一个数据提交标记;
查询操作方法需要使用事务吗?
这个要根据数据库的设置的隔离级别,和业务场景来进行判断,如果说数据库设置的隔离级别为RC读已提交,而且我们比如先后查询两条数据,然后业务操作是要对两条数据做计算,那么最好是使用事务,并把当前事务的隔离级别改为RR可重复读,这样就能保证做运算的两条数据是在同一时间维度的;
如果用默认的RC,那么第一次查询结果出来,再进行下一次查询的时候,刚好有事务提交了(这个事务中,对第一次查询的数据做了修改,对第二次查询的数据也做了修改),那么第二条数据查询出来的就是已经提交的数据,这个时候我们做计算,就是拿第一条数据未修改的数据和第二条数据已经修改的数据做计算(而且是同一个事务修改的),从业务角度上来讲,这不太合理;
并发要求高,用RC,做报表等追求数据的一致性,用RR可重复读;
持久性:
一旦提交了事务,它对数据库的改变就是永久性的,它是通过redo log日志来实现的;
当我们修改数据的时候,MYSQL会在先在innodb引擎的缓存中去拿数据,如果没有则去磁盘文件idb中去拿到缓存中,拿到之后在缓存中进行修改,然后更新binlog日志,和redo日志文件,然后提交完成,更新完然后修改的数据在通过修改数据的IO线程,在系统空闲时间写入磁盘;如果在磁盘io的时候,数据库挂了,我们就可以通过redo日志来恢复;
为什么要建立redo日志这一步操作?
因为在写redo日志文件的时候,是使用的磁盘顺序写,也就是在最后追加,而我们更新数据库表中的数据,它在磁盘上的位置是已经确定了的,比如同时操作十条数据,它们在磁盘上的位置不同,而磁盘顺序写的效率远高于写库,所以为了同时保证性能,和数据不丢失添加了redo日志文件;
固态硬盘的顺序写和随机写性能差不多,但是顺序写也是会稍微快一点;而且MYSQL出来的时候都是数据都是存在机械硬盘中;
固态硬盘
固态硬盘的内部分为主控和存储单元,还有个接口。存储单元就是用来存储数据的,主控是用来记录数据存储位置和进行数据操作的,接口是用来与计算机进行数据交换的。而决定固态的速度的主要是接口和控制器,主控单元负责我们的数据的存储,他会优先选择我们存储单元中用的比较少的位置进行数据的存储,从而保证我们存储单元寿命的一致。
机械硬盘
就把他当作光驱和CD就行了。影响存取速度的主要因数为转速,我们常说的转速就是磁盘的每分钟转的圈数。理论上,只要磁盘转的够快,我们的磁头读到的数据就会足够的多,足够快,但是,他是通过记录数据存储的扇区来进行数据存取,所以存在着一个寻道时间,这也是他为什么速度比不上固态的主要原因。
事务的优化
大事务的影响
- 并发情况下,数据库连接池容易被撑爆,连接占用
- 锁定太多数据,造成大量的阻塞和锁超时;
- 执行时间长,容易造成主从延迟;
- 回滚也比较长
- Uodo_log的日志链会一直膨胀
- 容易导致死锁
事务优化实践原则
- 将查询等数据准备操作放到事务外;比如数据库设置的rc级别,在事务内查询和事务外查询结果都是一致的;RR可重复读,必须放在事务内,才能保证RR的隔离级别,每次读到的数据都是一样的(当前事务修改之前);
- 事务中避免远程调用,远程调用设置超时时间,防止等待太久;
- 事务中避免一次性处理太多数据,可以分批次处理;
- 更新等涉及加锁(更新是在原有数据上加锁,insert是在新的数据上加锁)的操作尽可能放在事务靠后的位置,所以insert尽量放在update之前;
- 能异步就异步
- 不用事务的话,数据库就不需要记录undo_log等操作,性能更高,也可以在代码层面来进行保证事务,比如在catch自己写回滚代码;这种业务简单和对性能要求高的时候可以尝试;