隔离事务远比想象的要复杂
在SQL标准中订立了四种隔离级别
READ UNCOMMITTED(未提交读)
在READ UNCOMMITTED级别,事务的修改即使没有commit,对其他事务也是可见的,也被称为脏读(DirtyRead)。
READ COMMITTED(提交读)
大部分数据库默认的隔离级别为READ COMMITTED(但MySQL不是)。和UNCOMMITTED相对,在没有commit之前所有的修改对其他事务都是不可见的,也被称作不可重复读(nonrepeatable read)。
REPEATABLE READ(可重复读)
REPEATABLE READ解决了脏读的问题。该级别保证了同一个事务中多次读取同样的记录结果是一致的。但理论上该级别无法解决幻读问题。
所谓幻读,当某个事物在读取某个范围的记录是,另一个失误又在该范围内插入了新的纪录,当之前的食物再次读取该范围的时候,会产生幻读(Phantom Row)。InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC)解决了幻读的问题。
SERIALIZABLE(可串行化)
SERIALIZABLE是最高的隔离级别。他通过强制事务串行执行,避免了前面所说的幻读的问题。也就是说,SERIALIZABLE会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题。只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑使用。
死锁
死锁是两个或者多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。假设如下事务:
事务1
START TRANSACTION;
UPDATE StockPrice SET colse=45.50 WHERE stock_id=4 and date='2002-05-01';
UPDATE StockPrice SET colse=19.80 WHERE stock_id=3 and date='2002-05-02';
COMMIT;
事务2
START TRANSACTION;
UPDATE StockPrice SET high=20.12 WHERE stock_id=3 and date='2002-05-02';
UPDATE StockPrice SET high=47.20 WHERE stock_id=4 and date='2002-05-02';
COMMIT;
如果凑巧,两个事物都执行了第一条UPDATE语句,更新了一行数据,同时也锁定了该行数据,借着每个事物都尝试去执行第二条UPDATE语句,却发现该行已经被对防锁死,然后都在等待对方释放锁,同时又相互持有对方需要的锁,则陷入死循环。
为了解决这种问题,数据库系统实现了各种死锁检测和超时机制。一般会在检测到死锁后立即返回一个错误。
死锁的出现有时候是由于存储引擎的实现方式导致的,而有的则是业务中真正的数据冲突,而且基本无法避免。死锁发生后只有回滚其中的一个事务才能打破死锁。所以程序设计的时候必须考虑如何处理死锁。
事务日志
事务日志可以帮助提高事务的效率,它的原理是在修改表的数据的时候只需要修改其内存拷贝,再把修改行为记录持久化到硬盘上的事务日志,事务日志持久以后,内存中被修改的数据在后台可以慢慢刷回到磁盘。如果出现断电等情况,下一次启动时,存储引擎会根据事务日志,恢复数据。
因为事务日志是追加方式写入,所以写操作是磁盘上一小块区域的顺序I/O,不同于直接写入数据库磁盘的随机I/O,所以要快很多。