首先说说什么是ACID:
它们分别是Atomicity(原子性),Consistency(一致性),Isolation(隔离性),Transaction(持久性)
原子性:
意为单个事务里的多个操作要么一起成功,要么一起失败.比如现在有三个插入操作,那么前两个成功,第三个失败了,此时,前两个也不再算数,数据库会回滚到事务开始之前的状态.
innodb靠着undo缓冲区实现,也就是当事务开始的时候,首先将会受到影响的行当前的状态保存到undo缓冲区中,一旦失败,则回滚.
一致性:
单个事务对数据库的影响必须要保证是从一个状态到另一个状态的,这个怎么理解呢?将它理解为对原子性的补充就行了,也就是说,你一个事务里三个操作,如果其中一个失败了,其它的也不能对我的数据库造成影响,必须要三个一起成功才行.
这个的实现就是原子性的实现
隔离性:
这个是靠数据库锁实现的,数据库锁实在太繁杂,我这里就说说怎么mysql是怎么保证了在可重复读的情况下就避免了不可重复读和幻读吧.
首先,mysql的读操作在innodb下的读提交和可重复读的情况下都是使用了Mvcc,这个Mvcc将它理解为一个快照就可以了,而读已提交在一个事务里多次读的情况下,每次读都会读最新的已提交的数据.由此也就出现了幻读和不可重复读
比如A线程在一个事务里连续两次select
在第一次select后,B线程插入了一条数据,并且提交了事务.
此时A线程因为每次都是读最新的已提交的数据自然就读到了B线程插入的数据.
而在可重复读的情况下呢?
首先可重复读的情况下读Mvcc每次都是读第一次select的数据,由此就避免了可重复读和幻读的产生,因为B线程插入的数据对线程A的事务没有任何影响,但这样就代表没问题了吗?
假设这样一个场景,线程A要拿到当前最大的ID然后自增之后插入
假设现在数据库最大ID为10
线程A查询后,自增为11,并准备插入
但此时线程B插入一条数据,并且ID为11.
此时线程A插入,然后报错.
由此可见,此时还是会有问题.
那么怎么解决呢?很简单,使用select max(id) from table for update
因为在可重复读的情况下,使用排它锁,会默认升级为间隙排他锁,什么叫间隙排他锁呢?就是会锁离你最近的两个行数据,并且还有他们之间,不让别人进行增删改查操作,如果没有则取无限集.
那么此时,10右区间无限集就被锁起来了,线程B自然没法查询数据(当然也有可能是A,看谁先拿到锁).此时线程A就插入成功,然后B开始查询,之后插入(这里说下为什么是排他锁而不是共享锁呢?因为线程A和线程B在并发的情况下查了max(id)后,他们拿到的都是相同的数据,也就会在插入的时候报错,所以此时直接让他们其中一个堵在select那里,这样另一方拿到数据后,就会更新id,另一方也能拿到最新值了)
持久性:
事务提交后,事务里的数据必须要进行刷盘,也就是强同步
这里要插一句的是,当事务提交后,数据本身不会马上刷盘,而是由一个redo缓冲区保存了插入日志,进行刷盘,为什么要这样做呢?因为redo是日志数据,所以可以顺序写,而数据刷盘则是要找到文件里数据所在的索引,也就是随机写,而随机写比顺序写慢很多,毕竟多了硬盘寻址的操作.