为什么InnoDB可以实现行锁,MyISAM不行?InnoDB行锁锁住的又是什么?
先来解释为什么MyISAM不能实现行锁,这取决于它的数据存储结构,我们知道,MyISAM存储引擎中,数据的存储方式是索引和数据分开存储在不同的内存空间中,索引节点的key是索引值,value是指向对应行数据的指针。如果我们在这种情况下加行锁,第一个问题,我们锁住的是什么?锁索引值吗?肯定不行,一个表可能有多个索引,锁住索引值没有任何意义,锁住指针指向的地址?这样子当别的事务访问这个地址时,就会发现加了锁,然后阻塞等待。貌似可以,但是有问题,因为数据行的地址不是一成不变的,可能会因为各种原因改变地址,此时,假如事务A对0X667这个地址加了锁(假设a行数据的地址是0X667),经过一段时间,可能a行数据的地址变了!比如0X666位置的数据被删除了,a行数据移动到了0X666,但是注意,事务A锁的是地址,也就是说,虽然a行数据移动了,但是事务锁的地址没有改变,这种情况下,问题很多,一个是锁住了一个本该可以进行操作的地址,一个是本该被锁住的a行数据没有被锁住,其它事务可以对其进行操作。那为什么不在行数据地址改变的时候实时更新索引的value呢?首先,改了也没用,因为一定是a行数据的地址先发生改变,那么在更新索引的value并重新为更新后的地址加上锁的时间段里,可能会有其它事务对a行数据进行操作。其次,性能太差,我们知道MyISAM的索引都是二级索引,如果要更新地址,那么所有索引的value都要更新,并重新加锁。那锁行数据呢?首当其冲的问题就是性能差,其次就是锁行数据实际上锁的也就是地址,一样存在锁和数据不对应的问题。
那为什么InnoDB可以实现行锁呢?同样也是取决于它的数据存储结构,我们知道InnoDB有主键索引(不管你有没有手动创建主键索引,没有创建的话会自动帮你创建),而且主键索引是和数据存储在一起的,也就是说,主键索引的B+Tree的叶子节点上,存储的value不再是地址指针,而是数据本身。而对于二级索引,索引节点的key是二级索引的值,value是对应行数据对应的主键值。基于这种数据存储结构,InnoDB是怎样实现行锁的呢?首先我们要知道锁住的是什么,在InnoDB中,如果是基于二级索引加锁,那么会先根据value去锁住对应主键,然后再锁住对应的二级索引,如果是基于主键加锁,那么就是直接对主键加锁,那这样子加锁之后,会不会存在上面MyISAM的问题呢?下面进行分析:首先就是当数据位置发生改变时,因为我们锁住的不是地址,所以数据位置改变没有影响。并且不管是通过主键索引还是通过二级索引还是不通过索引对数据进行操作,都会经过主键,那么我们锁住了主键,就避免了其他事务对该行数据的修改。那为什么要锁住二级索引呢?首先我们知道高并发场景下会出现三种问题,幻读,脏读,不可重复读,脏读可以通过控制事务隔离级别解决,锁住了主键,该行数据也不会被修改,不可重复读也不会发生,但是幻读呢?对于select * from tablename where xx between 1 and 3,如果我们只锁住了主键,而没有锁住间隙,那么其它事务就可以往间隙中插入数据,说不定下一次查就会出现幻读现象,所以锁二级索引的意义是锁住间隙,避免幻读现象的发生。行级锁有Recard lock 和 Next-Key lock,所以最后我们可以知道,加在主键上的是Recard lock,加在二级索引上的是Next-key lock