事务
事务就是保证一组数据库操作,要么全部成功,要么全部失败。
原子性
一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节,而且事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样,就好比买一件商品,购买成功时,则给商家付了钱,商品到手;购买失败时,则商品在商家手中,消费者的钱也没花出去。
一致性
是指事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态。
隔离性
数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致,因为多个事务同时使用相同的数据时,不会相互干扰,每个事务都有一个完整的数据空间,对其他并发事务是隔离的。也就是说,消费者购买商品这个事务,是不影响其他消费者购买的。
持久性
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
隔离性
举个例子
mysql> create table T(c int) engine=InnoDB;
insert into T(c) values(1);
MySQL服务端允许多个客户端连接,这意味着MySQL会出现同时处理多个事务的情况。可能出现脏读、不可重复读、幻读的问题。
- 脏读:一个事务读到了另一个未提交事务修改过的数据
- 不可重复读:一个事务内多次读取同一个数据,如果前后两a次读到的数据不一样,就是不可重复读
- 幻读:前后两次查询到的记录不一样
隔离级别
为了解决上面三个现象,提出了四种隔离级别:
- 读未提交:一个事务还没有提交就可以被其他的事务看到
- 读提交:一个事务提交之后,他的变更才会被其他事务看到
- 可重复读:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。
- 串行化:对于同一行记录,写会加写锁,读会加读锁。
- 若隔离级别是“读未提交”, 则V1的值就是2。这时候事务B虽然还没有提交,但是结果已经被A看到了。因此,V2、V3也都是2。
- 若隔离级别是“读提交”,则V1是1,V2的值是2。事务B的更新在提交后才能被A看到。所以,V3的值也是2
- 若隔离级别是“可重复读”,则V1、V2是1,V3是2。之所以V2还是1,遵循的就是这个要求:事务在执行期间看到的数据前后必须是一致的。
- 若隔离级别是“串行化”,则在事务B执行“将1改成2”的时候,会被锁住。直到事务A提交后,事务B才可以继续执行。所以从A的角度看, V1、V2值是1,V3的值是2。
配置方式:将启动参数transaction-isolation的值设置成READ-COMMITTED。
读提交和可重复读的区别
- 读提交:是在每个 select 都会生成一个新的 Read View,也意味着,事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。
- 可重复读:是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View,这样就保证了在事务期间读到的数据都是事务启动前的记录。
事务隔离的实现
在MySQL中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通
过回滚操作,都可以得到前一个状态的值。
当前值是4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的read-view。如图中看到的,在视图A、B、C里面,这一个记录的值分别是1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于read-viewA,要得到1,就必须将当前值依次执行图中所有的回滚操作得到。同时你会发现,即使现在有另外一个事务正在将4改成5,这个事务跟read-viewA、B、C对应的
事务是不会冲突的。
图片来自小林coding
MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象,解决方案有两种:
- 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
- 针对当前读(select … for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select … for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。
事务的启动方式
- 显式启动事务语句, begin 或 start transaction。配套的提交语句是commit,回滚语句是rollback。
- set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个select语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行commit或rollback语句,或者断开连接。
建议使用set autocommit=1,通过显式语句的方式来启动事务,如果执行commit则提交事务。如果执行commit work and chain,则是提交事务并自动启动下一个事务,这样也省去了再次执行begin语
句的开销。同时带来的好处是从程序开发的角度明确地知道每个语句是否处于事务中。
查询长事务
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60