Mysql事务隔离级别与锁

来源:互联网 发布:淘宝美工入门教程 编辑:程序博客网 时间:2024/05/22 07:01

原文  http://blog.csdn.net/endlu/article/details/51531397

MVCC  (详见http://blog.csdn.net/endlu/article/details/51518377)
    InnoDB总为每一行后面加入了两个隐藏的列,来实现MVCC。这两个列分别纪录了数据最后一次被哪个事务创建、更新的事务号;该事物是否被删除,被删除的事务的事务号。事务号是递增的。虽然这格外增加的存储空间,每一行都要存储额外的历史版本,而且还要定期删除。但是多版本的实现,为大多数的读惭怍提供了方便:读数据只需要根据事务号来读取某一历史版本,不用再担心并发读写的问题而加锁,效率高,并且可以实现隔离级别中要求的只读取符合条件的值。我们称这种读叫快照读,相反如果读取当前的最新数据叫当前读。mysql在RC/RR下的普通select都为快照读,不用加锁。
    在RR级别下,各种操作在MVCC下会有怎么样的效果呢?
  • select时,读取版本号<=当前事务号并且删除版本号为空或>当前事务号的行。
  • insert,保存事务号为当前事务号。
  • delete,保存当前事务号为删除版本号。
  • update,创建新的一行,保存事务号为当前事务号。

   快照读/当前读
    说完MVCC,我们回到隔离级别。上面说的隔离级别的介绍中,关于RR不能解决幻读的问题,是耳熟能详的,到处都是这么写的。但是实际呢?经过实验,session A select * from test where age = 12; 然后Session B插入一个age=12的行,Session A再次select,并没有返回新插入的行。由此可见Mysql中幻读的读问题已经解决了。原理就是上文提到的快照读。但是,当前读的冲突问题呢?
  • 快照读:
    • select * from test where ... ;
  • 当前读:
    • select * from test where ... in share mode ;
    • select * from test where ... for update ;
    • insert
    • update
    • delete
    事务的隔离级别实际上是定义了当前读的级别。为了减少锁竞争,引入了快照读,使得普通的select不同加锁。其他操纵还是需要加锁的。
    为了解决当前读的幻读问题,Mysql使用了Next-key锁。就是GAP(间隙锁)和行锁的合并。行锁可以避免并发修改同一行带来的问题,但是插入操作呢?间隙锁GAP就是为了解决这个问题。
    在RC级别中,表test,age为主键:
  1. A:select * from test where age = 10;  返回一条记录
  2. A:update test set name = ‘a’ where age = 10;
  3. B:insert into test values(10,‘b’);
  4. B:commit;
  5. A:select * from test where age = 10;  返回两条记录
  6. A:commit ;
    在这个例子中,步骤二虽然进行了update,对age=10的记录加了行锁。但是session B插入了新纪录,并提交。A就可以读到。
    在RR级别中,重新进行这个实验,A的第二次读,仍然返回一条记录。因为在步骤二中,不止对age=10的行加了行锁,还有间隙锁。session B的插入将被阻塞,等待获取锁,A提交后才能被执行。
    
   InnoDB行锁
    上面说到,InnoDB当前读,会对行加锁,防止并发问题。这里有个前提:where条件是索引,可以通过索引来过滤行来对指定行加锁。如果不是索引,InnoDB会对所有行加锁,但是为了提供并发效率。等存储系统返回数据后,会过滤后再对不符合条件的行释放锁。还记得本文开头介绍两阶段锁时,说过有时为了效率会违反这个原则么。就是现在所说的这种情况。先获取全部锁,再释放掉多余的锁。

    特殊情况
    这里举一个例子:
  1. A:select * from test where age = 10;  返回一条记录
  2. B:insert into test values(10,‘b’);
  3. B:commit;
  4. A:update test set name = ‘qwe’where age = 10;
  5. A:select * from test where age = 10;  返回两条记录
    看到这个结果,有人不禁起了疑问:不是说InnoDB在RR级别解决了幻读问题么?怎么同一个事务中两次读读到了不同的行数。
    这里A的前后两次读,均为快照读,而且是在同一个事务中。但是B先插入直接提交,此时A再update,update属于当前读,所以可以作用于新插入的行,并且将改行的当前版本号设为A的事务号,所以第二次的快照读,是可以读取到的,因为同事务号。这种情况符合MVCC的规则,如果要称为一种幻读也非不可,算为一个特殊情况来看待吧。

0 0