mysql 事务机制
一、事务四大特性(ACID)原子性、一致性、隔离性、持久性
原子性(Atomicity):原子性是指,事务包含的所有操作要么成功,要么全部失败回滚。因此事务的操作如果成功,就必须全部应用到数据库,如果操作失败就不能对数据库有影响。
一致性(Consistency):事务必须从一个一致性状态变成换另一个一致性状态,也就是说一个事务执行之前和执行之后必须处于一致性。比如A向B转账¥100,A少了100,B需要多了100,加起来是100才行。
隔离性(Isolation):当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作干扰。
持久性(Durability):
持久性是指,一个事务一旦提交,那么对数据库的改变是永久性的,即使在数据库系统遇到故障的时候 也不会丢失事务的操作。
二、事务隔离级别和实现原理:
MySQL 事务都是指在 InnoDB 引擎下,MyISAM 引擎是不支持事务的
1、读未提交。(read uncommitted)
2、读提交。(read committed)
3、可重复读(重复读到的值一样)。(repeatable read)在可重复读的事务隔离级别下,读取的是快照数据,总是读取当前事务开始时的行数据版本。
commit 事务以后,再提交才会更新。
4、串行化。(serializable)
隔离级别注意:MySQL解决了 可重复读的 幻读问题。详见下文
脏读:脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
不可重复读(重复读到的值会不一样):是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
使用数据库时候需要使用业务,必须先开启业务:
start transaction;
提交事务:
commit;
取消事务(回滚):
rollback;
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
MySQL默认隔离级别,repeatable-read(重复读) :
select @@tx_isolation
修改隔离级别的语句是:set [作用域] transaction isolation level [事务隔离级别],
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}。
把数据库的模式改成读(read uncommitted)未提交:
mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
全局:
set global transaction isolation level read uncommitted;
注意:一定要设置成一样的,事务类型(read uncommitted)。不同终端,不能通用(global, session都不行),必须都得设置才能同步。
读未提交
设置A、B事务级别 查询A、B表不开启事务,read uncommitted无法实现效果,改变了就改变了,rollback没有效果。
开启事务前,更新操作开启事务后,操作执行时间线(出现读未提交):【红线代表时间线操作】
操作时间轴 另一个例子读未提交,其实就是可以读到其他事务未提交的数据,但没有办法保证你读到的数据最终一定是提交后的数据,如果中间发生回滚,那就会出现脏数据问题,读未提交没办法解决脏数据问题。更别提可重复读和幻读了,想都不要想。
读提交
另一个例子设置read committed/uncommitted时,global不可用,必须用session才可以。
时间线,提交完之后才后被其他线程读取到。
每个 select 语句都有自己的一份快照,而不是一个事务一份,所以在不同的时刻,查询出来的数据可能是不一致的。
读提交解决了脏读的问题,但是无法做到可重复读,也没办法解决幻读。
可重复读
可重复读操作注意:测试可重复读时,必须开启两个事务。两个线程都要 start transaction;
可重复读:事务A启动后修改了数据,事务B在事务开始和事务A提交之后两个时间节点都读取的数据相同。
可重复读(自己定义):事务A做修改操作,事务B在事务A修改前和修改后,读取到的值一样。注意:事物B读取的事务在commit之后,读取到的就是修改后的值了。
不开启事务时:
不开启事务开启事务时,插入不成功:
当你在 MySQL 中测试幻读的时候,并不会出现上图的结果,幻读并没有发生,MySQL 的可重复读隔离级别其实解决了幻读问题。
在事务A提交之前,事务B的插入操作只能等待在事务A提交之前,事务B的插入操作只能等待,这就是间隙锁起得作用。
间隙锁解决幻读等问题不仅插入 age = 10 的记录需要等待事务A提交,age<10、10<age<30 的记录页无法完成,而大于等于30的记录则不受影响,这足以解决幻读问题了。
这是有索引的情况,如果 age 不是索引列,那么数据库会为整个表加上间隙锁。所以,如果是没有索引的话,不管 age 是否大于等于30,都要等待事务A提交才可以成功插入。
串行化:
串行化是4种事务隔离级别中隔离效果最好的,解决了脏读、可重复读、幻读的问题,但是效果最差,它将事务的执行变为顺序执行,与其他三个隔离级别相比,它就相当于单线程,后一个事务的执行必须等待前一个事务结束。
MySQL 中是如何实现事务隔离的:
首先说读未提交,它是性能最好,也可以说它是最野蛮的方式,因为它压根儿就不加锁,所以根本谈不上什么隔离效果,可以理解为没有隔离。
再来说串行化。读的时候加共享锁,也就是其他事务可以并发读,但是不能写。写的时候加排它锁,其他事务不能并发写也不能并发读。
最后说读提交和可重复读。这两种隔离级别是比较复杂的,既要允许一定的并发,又想要兼顾的解决问题。
解决的原理,锁:
并发写问题的解决方式就是行锁,而解决幻读用的也是锁,叫做间隙锁,MySQL 把行锁和间隙锁合并在一起,解决了并发写和幻读的问题,这个锁叫做 Next-Key锁。