事务隔离级别
SQL的标准定义里面,一共有四种级别:
- read uncommited 读取未提交的数据。就是其他事务已经修改但还未commit的。这种情况叫:脏读。
- read commited 读取已提交的数据。query2会跟query1读取的数据不一样。这种情况叫 “不可重复读”
- repeatable read 可重复读取。它确保同一事务中多次读取某条数据时读取的数据一样。也就是说读取的都是已经提交的数据。但会出现幻读:当用户读取某一范围内的行时,另一事务在该范围内插入或删除了新行。用户再次读取该范围行时结果和之前的结果一样,但真实的数据已经被别人修改,他读取到的结果其实已经不存在或已变更。即幻影行。 这是 MySQL 默认的隔离级别。
- serializable 序列化。强制对各事务进行排序,使之不能互相冲突,从而解决幻读。但会出现排队,超时,实际中很少用。
SQL 标准用三个必须在并行的事务之间避免的现象定义了四个级别的事务隔离。
这些不希望发生的现象是:
-
脏读(dirty reads):一个事务读取了被另一个事务改写但还没提交的数据.
-
不可重复读(non-repeatable reads):一个事务重新读取前面读取过的数据, 发现该数据已经被另一个已提交的事务修改过(一个事务执行相同的查询两次或两次以上,但每次查询结果都不同时。这由于另一个并发事务在两次查询之间更新(update)了数据).
-
幻读(phantom read):在两次查询同一时间点数据时,发现数据数量发生改变。(当一个事务读取几行记录后,另一个并发事务插入(insert,delete)一些记录)
不同隔离级别出现上面几种现象的可能性是:
死锁
两个或多个事务在同一资源上互相占用,并请求加锁时,导致恶性循环,如: 事务一:
start transaction;
update table set column1 = 1 where id = 4;
update table set column1 = 2 where id = 3;
commit;
事务二:
start transaction;
update table set column1 = 3 where id = 3;
update table set column1 = 4 where id = 4;
commit;
两个事务执行第一条语句时都没问题,而且分别取得了两行的写锁。但是执行第二条语句时都不会成功,因为锁都被对方占用了。事务就会不停的等待对方释放资源。进而进入死循环。
为了解决这种问题,数据库系统实现了各种死锁检测及死锁超时机制。InnoDB 引擎则可以预知这种循环的相关性,并立刻返回错误,并回滚拥有最少排他锁的一个事务(因为这个事务最容易回滚)。
事务日志
通常,我们更新数据的过程是:执行SQL语句,数据库更新磁盘中表的数据。而事务则不这样处理,存储引擎可以先更新数据在内存中的拷贝,这非常快。然后存储引擎将数据改变写入事务日志中,日志在磁盘上。这样就让数据有了持久性,这个过程也相对较快(相对于更新表数据)。最后,存储引擎才会在某个时间更新到磁盘表中。
如果数据更新已经写入日志,但还没来得及写入表中,但这时候系统崩溃,存储引擎会在重启后自动去恢复事务日志中的数据。当然,这个功能也是引擎自己的功能,不是 MySQL 自身的功能。
混合引擎的事务
由于事务的功能不是 MySQL 自身提供的,而是引擎提供的。
所以,如果事务中的多个表,有的是 MyISAM, 有的是 InnoDB,那就要格外小心了。因为 InnoDB 可以通过 rollback 回滚,但 MyISAM 是 autocommit 的,数据自动提交且无法回滚。
如果事务处理一切顺利,那没问题。如果有问题,错误就无法挽回了,所以如果要用事务,就尽量全部涉及的表都用事务型引擎吧。