数据库并行产生的问题
- A事务撤销时,将B事务更改的数据撤销。
- A事务提交时,将B事务更改的同行数据覆盖。
- 脏读:A事务读取到了B事务未提交的数据。
- 不可重复读:A事务中同查询语句不幂等,读到已更新数据。
- 幻读:A事物中同查询语句不幂等,读到新插入数据。
数据库事务隔离级别
- 串行化(Serializable):一个事务执行过程中完全看不到其他事务,但如果其他事务在操作,只能停等;可避免以上五个问题。
- 可重复读(Repeated Read, RR):一个事务可以看到其他事务已提交的新插入记录,但不能看到其他事务对已有行的更新记录;允许幻读。
- 读已提交数据(Read Committed, RC):一个事务可以看到其他事务已提交的记录,包括插入和更新;允许幻读和不可重复读。
- 读未提交(Read Uncommited):一个事务可以看到其他事务未提交的数据;仅能避免A事务撤销影响B事务。
InnoDB中的事务实现
MySQL的引擎是innoDB时,使用的默认隔离级别为,可重复读(RR级);大部分数据库使用的默认隔离级别为读已提交(RC级)。
实现RC级的事务时,仅需要采用行锁。InnoDB的行锁采用乐观锁的形式实现事务,InnoDB在每行数据后加入两个字段:创建版本号,删除版本号,在事务为默认的RR级以及RC级时,InnoDB事务隔离实现如下:
- select 读取版本号<=当前事务版本号,删除版本号为空或大于当前事务版本号。
- insert 保存当前事务版本号为行的创建版本号。
- delete 保存档前事务版本号为行的删除版本号。
- update 插入一条新的记录,保存当前事务版本号为行的创建版本号,同时保存当前事务版本号到原来的行。
需要注意的是,基于上述原理,mysql事务级别为可重复读时,select操作的结果不会有幻读记录,实际上,这是因为mysql的select是一种“快照读”,而非“当前读”。因此,事务为RR级和RC级时,mysql的select是不需要加锁的,但一旦涉及到insert/update/delete,则需要当前读,就需要加锁。
MySQL事务使用了Next-Key锁,Next-Key锁是行锁和间隙锁的合并。行锁负责给操作行加锁,在RC级中,使用行锁已经足够。而间隙锁,则基于数据作为索引结构的B+树,将数据所在块的邻接块一同加锁,因此可能会锁住不必要的区间,如果不使用索引,则会给全表加锁。
需要注意的是级别是串行的话,即便是select操作也是加锁的。