MySQL数据库性能优化(2) (7)

为了进一步详细说明各种典型的加锁过程,本小节为读者准备了几个实例场景,并使用图文混合的方式从索引逻辑层面上进行说明。后续的几种实例场景都将以以下数据表和数据作为讲解依据:

CREATE TABLE `myuser` ( `Id` int(11) NOT NULL AUTO_INCREMENT, `user_name` varchar(255) NOT NULL DEFAULT \'\', `usersex` int(9) NOT NULL DEFAULT \'0\', `user_number` int(11) NOT NULL DEFAULT \'0\', PRIMARY KEY (`Id`), UNIQUE KEY `number_index` (`user_number`), KEY `name_index` (`user_name`) )

这张表中有三个索引,一个是以id字段为依据的聚簇索引,一个是以user_name字段为依据的非唯一键非聚簇索引,最后一个是以user_number字段为依据的唯一键非聚簇索引。我们将在实例场景中观察唯一键索引和非唯一键索引在加锁,特别是加GAP锁的情况的不同点。这张数据表中的数据情况如下图所示:

示例数据

4-3-2-1、 行锁加锁过程

首先我们演示一个工作在InnoDB引擎下的数据表只加行锁的情况。

begin; update myuser set user_name = \'用户11\' where id = 10; commit;

以上事务中只有一条更新操作,它直接使用聚簇索引作为检索条件。聚簇索引肯定是一个唯一键索引,所以InnoDB得出的加锁条件也就不需要考虑类似“insert into myuser(id,………) values(10,………)”这样的字段重复情况。因为如果有事务执行了这样的语句,就会直接报错退出。那么最终的加锁结果就是:只需要在聚簇索引上加X锁。

这里写图片描述

 
(额~~~你要问我为什么树结构会是连续遍历的?请重读B+树的介绍)

其它事务依然可以对聚簇索引上的其它节点进行操作,例如使用update语句更新id为14的数据:

begin; update myuser set user_name = \'用户1414\' where id = 14; commit;

当然,由于这样的执行过程没有在X锁临近的边界加GAP锁,所以开发人员也可以使用insert语句插入一条id为11的数据:

begin; insert into myuser(id,user_name,usersex,user_number) values (11,\'用户1_1\',1,\'110110110\'); commit; 4-3-2-2、间隙锁加锁过程

工作在InnoDB引擎下的数据表,更多的操作过程都涉及到加间隙锁(GAP)的情况,这是因为毕竟大多数情况下我们定义和使用的索引都不是唯一键索引,都在“可重复读”的事务级别下存在“幻读”风险。请看如下事务执行过程:

begin; update myuser set usersex = 0 where user_name = \'用户8\' commit;

这个事务操作过程中的update语句,使用非唯一键非聚簇索引’name_index’进行检索。InnoDB引擎进行分析后发现存在幻读风险,例如可能有一个事务在同时执行以下操作:

begin; insert into myuser(id,user_name,usersex,user_number) values (11,\'用户8\',1,\'110110110\'); # 或者执行以下插入 # insert into myuser(id,user_name,usersex,user_number) values (11,\'用户88\',1,\'110110110\'); commit;

所以InnoDB需要在X锁临近的位置加GAP锁,避免幻读:

这里写图片描述

以上示意图有一个注意点,在许多技术文章中对GAP锁的讲解都是以int字段类型为基准,但是这里讲解所使用的类型是varchar。所以在加GAP锁的时候,看似’用户8’和’用户9’这两个索引节点没有中间值了。但是字符串也是可以排序的,所以’用户8’和’用户9’这两个字符串之间实际上是可以放置很多中间值的,例如’用户88’、’用户888’、’用户8888’等。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgffsf.html