深入分析解读MySQL锁,解决幻读问题


深入分析解读MySQL锁,解决幻读问题

文章插图
 
前言今天就为大家介绍一下MySQL中锁相关的知识 。本文在没有特别声明的情况下,均是默认InnoDB引擎,如涉及到其他引擎或者数据库则会特别指出 。
什么是锁锁是一种用于保证在并发场景下每个事务仍能以一致性的方式读取和修改数据的方式,当一个事务对某一条数据上锁之后,其他事务就不能修改或者只能阻塞等待锁的释放,所以锁的粒度大小一定程度上可以影响到访问数据库的性能 。
从锁的粒度上来说,我们可以将锁分为表锁和行锁 。
表锁顾名思义,表锁就是直接锁表,在MyISAM引擎中就只有表锁 。
表锁的加锁方式为:
LOCK TABLE 表名 READ;--锁定后表只读UNLOCK TABLE; --解锁行锁行锁,从名字上来看,就是锁住一行数据,然而,行锁的实际实现算法会相对复杂,有时候并不仅仅只是锁住某一条数据,这个后面再展开 。
正常的思路是:锁住一行数据之后,其他事务就不能来访问这条数据了,那么我们想象,假如事务A访问了一条数据,只是拿出来读一下,并不想去修改,正好事务B也来访问这条数据,也仅仅只是想拿出来读一下,并不想去修改,这时候如果因此阻塞了,就有点浪费性能了 。所以为了优化这种读数据的场景,我们又把行锁分为了两大类型:共享锁和排他锁 。
共享锁共享锁,Shared Lock,又称之为读锁,S锁,就是说一条数据被加了S锁之后,其他事务也能来读数据,可以共享一把锁 。
我们可以通过如下语句加共享锁:
select * from test where id=1 LOCK IN SHARE MODE;加锁之后,直到加锁的事务结束(提交或者回滚)就会释放锁 。
排他锁排他锁,Exclusive Lock,又称之为写锁,X锁 。就是说一条数据被加了X锁之后,其他事务想来访问这条数据只能阻塞等待锁的释放,具有排他性 。
当我们在修改数据,如:insert,update,delete的时候MySQL就会自动加上排他锁,同样的,我们可以通过如下sql语句手动加上排他锁:
select * from test where id=1 for update;在InnoDB引擎中,是允许行锁和表锁共存的 。
但是这样就会有一个问题,假如事务A给t表其中一行数据上锁了,这时候事务B想给t表上一个表锁,这时候怎么办呢?事务B怎么知道t表有没有行锁的存在,如果采用全表遍历的情况,当表中的数据很大的话,加锁都要加半天,所以MySQL中就又引入了意向锁 。
意向锁意向锁为表锁,分为两种类型,分为:意向共享锁(Intention Shared Lock)和意向排他锁(Intention Exclusive Lock),这两种锁又分别可以简称为IS锁和IX锁 。
意向锁是MySQL自己维护的,用户无法手动加意向 。
意向锁有两大加锁规则:
  • 当需要给一行数据加上S锁的时候,MySQL会先给这张表加上IS锁 。
  • 当需要给一行数据加上X锁的时候,MySQL会先给这张表加上IX锁 。
这样的话上面的问题就迎刃而解了,当需要给一张表上表锁的时候,只需要看这张表是否有对应的意向锁就可以了,无需遍历整张表 。
各种锁的兼容关系下面这张图是各种锁的兼容关系,参考自官网:
深入分析解读MySQL锁,解决幻读问题

文章插图
 
锁到底锁的是什么建立以下两张表,并初始化5条数据,注意test表有2个索引而test2没有索引:
CREATE TABLE `test` (`id` int(11) NOT NULL,`name` varchar(50) DEFAULT NULL,PRIMARY KEY (`id`),KEY `NAME_INDEX` (`name`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;INSERT INTO test VALUE(1,'张1');INSERT INTO test VALUE(5,'张5');INSERT INTO test VALUE(8,'张8');INSERT INTO test VALUE(10,'张10');INSERT INTO test VALUE(20,'张20');CREATE TABLE `test2` (`id` varchar(32) NOT NULL,`name` varchar(32) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;INSERT INTO test2 VALUE(1,'张1');INSERT INTO test2 VALUE(5,'张5');INSERT INTO test2 VALUE(8,'张8');INSERT INTO test2 VALUE(10,'张10');INSERT INTO test2 VALUE(20,'张20');举例猜测在行锁中,假如我们对一行记录加锁,那么到底是把什么东西锁住了,我们来看下面两个例子:
举例1(操作test表):
深入分析解读MySQL锁,解决幻读问题

文章插图
 
举例2(操作test2表):
深入分析解读MySQL锁,解决幻读问题

文章插图
 
从上面两个例子我们可以发现,test表好像确实是锁住了id=1这一行的记录,而test2表好像不仅仅是锁住了id=1这一行记录,实际上经过尝试我们就知道,test2表是被锁表了,所以其实MySQL中InnoDB锁住的是索引,当没有索引的时候就会锁表 。


推荐阅读