Maison >base de données >tutoriel mysql >Ma compréhension de MySQL Five : verrous et règles de verrouillage
La colonne
Le cinquième article de la série MySQL, le contenu principal est le verrouillage (Lock), y compris la classification granulaire des verrous, des verrous de ligne, des verrous d'espacement et des règles de verrouillage.
Le but de l'introduction des verrous par MySQL est de résoudre le problème de l'écriture simultanée, par exemple, deux transactions écrivent dans le même enregistrement en même temps, une écriture sale se produira. Il s'agit d'une situation anormale qui ne peut se produire à aucun niveau d'isolement. La fonction du verrou est de permettre l'exécution de deux opérations d'écriture simultanées dans un certain ordre pour éviter les problèmes d'écriture sale.
Tout d'abord, déclarez que les exemples utilisés dans cet articleCREATE TABLE `user` ( `id` int(12) NOT NULL AUTO_INCREMENT, `name` varchar(36) NULL DEFAULT NULL, `age` int(12) NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE, INDEX `age`(`age`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1;insert into user values (5,'重塑',5),(10,'达达',10),(15,'刺猬',15);复制代码
Les exemples décrits dans cet article sont tous sous le moteur de stockage MySQL InnoDB et le niveau d'isolement Repeatable Read.1. Classification granulaire des verrous Du point de vue de la granularité des verrous, les verrous dans MySQL peuvent être divisés en trois types : les verrous globaux, les verrous au niveau de la table et les verrous de ligne. 1.1 Verrouillage global Le verrouillage global verrouillera l'intégralité de la base de données. À ce stade, la base de données sera en lecture seule. Toutes les instructions qui modifient la base de données, y compris DDL (Data Definition). Language) et ajoutez. Toutes les instructions DML (Data Manipulation Language) supprimées seront bloquées jusqu'à ce que le verrouillage global de la base de données soit libéré. L'endroit le plus courant pour utiliser les verrous globaux est d'effectuer une
sauvegarde complète de la base de données Nous pouvons implémenter des opérations de verrouillage et de déverrouillage global à l'aide des instructions suivantes :
-- 加全局锁flush tables with read lock;-- 释放全局锁unlock table;复制代码<.>Si la connexion client est déconnectée, le verrouillage global sera automatiquement libéré.
1.2 Verrous au niveau de la table
, . Meta Data Lock (Meta Data Lock), Intention Lock (Intention Lock) et Auto-increment Lock (AUTO-INC Lock). 1.2.1 Verrouillage de la table
lock table tableName read/write;
Déverrouiller le verrouillage :unlock table;
lock table user read
Si un verrou en écriture au niveau de la table (
lock table user write
1.2.2 Verrouillage des métadonnées
(MDL, Metadata Lock sera accessible sur le client). Le verrou est automatiquement verrouillé lorsque le tableau est saisi et le verrou est libéré lorsque le client soumet la transaction. Cela évite les problèmes dans les scénarios suivants :
Comme indiqué dans le tableau ci-dessus,sessionA | sessionB |
---|---|
begin; | |
select * from user; | |
alter table user add column birthday datetime; | |
select * from user; |
ajoute un nouveau champ sessionA
à la table sessionB
, puis user
effectue une autre requête. S'il n'y a pas de verrouillage de métadonnées, il peut apparaître dans. la même transaction, avant et après. Le nombre de champs et de colonnes de table dans les enregistrements récupérés deux fois est incohérent, ce qui doit évidemment être évité. birthday
sessionA
L'opération DDL ajoute un verrou en écriture de métadonnées à la table, qui n'est pas compatible avec les verrous de lecture et d'écriture de métadonnées d'autres transactions ; l'opération DML ajoute un verrou en lecture de métadonnées à la table, qui est compatible avec le les métadonnées d'autres transactions. Les verrous de lecture sont partagés mais sont incompatibles avec les verrous d'écriture de métadonnées d'autres transactions.
1.2.3 Verrouillage d'intention
, ce qui signifie que la transaction souhaite acquérir des verrous sur certaines lignes d'une table. (serrure partagée ou serrure exclusive). Le verrouillage d'intention consiste à éviter la consommation du système d'une autre transaction demandant un verrouillage de table et analysant chaque ligne de la table pour voir si un verrou de ligne existe alors qu'un verrou de ligne existe déjà dans la table.
例如,sessionA
开启了一个事务,并对 id=5
这一行加上了行级排它锁,此时 sessionB
将对 user 表加上表级排它锁(只要 user 表中有一行被其他事务持有读锁或写锁即加锁失败)。
如果没有意向锁,sessionB
将扫描 user 表中的每一行,判断它们是否被其他事务加锁,然后才能得出 sessionB
的此次表级排它锁加锁是否成功。
而有了意向锁之后,在 sessionB
将对 user 表加锁时,会直接判断 user 表是否被其他事务加上了意向锁,若有则加锁失败,若无则可以加上表级排它锁。
意向锁的加锁规则:
第四种表级锁是自增锁,这是一种特殊的表级锁,只存在于被设置为 AUTO_INCREMENT
自增列,如 user 表中的 id 列。
自增锁会在 insert 语句执行完成后立即释放。同时,自增锁与其他事务的意向锁可共享,与其他事务的自增锁、共享锁和排它锁都是不兼容的。
行锁是由存储引擎实现的,从行锁的兼容性来看,InnoDB 实现了两种标准行锁:共享锁(Shared Locks,简称S锁)和排它锁(Exclusive Locks,简称X锁)。
这两种行锁的兼容关系与上面元数据锁的兼容关系是一样的,可以用下面的表格表示。
事务A\事务B | 共享锁(S锁) | 排它锁(X锁) |
---|---|---|
共享锁(S锁) | 兼容 | 冲突 |
排它锁(X锁) | 冲突 | 冲突 |
而从行锁的粒度继续细分,又可以分为记录锁(Record Lock)、间隙锁(Gap Lock)、Next-key Lock。
我们一般所说的行锁都是指记录锁,它会把数据库中的指定记录行加上锁。
假设事务A中执行以下语句(未提交):
begin;update user set name='达闻西' where id=5;复制代码
InnoDB 至少会在 id=5 这一行上加一把行级排它锁(X锁),不允许其他事务操作 id=5 这一行。
需要注意的是,这把锁是加在 id 列的主键索引上的,也就是说行级锁是加在索引上的。
假设现在有另一个事务B想要执行一条更新语句:
update user set name='大波浪' where id=5;复制代码
这时候,这条更新语句将被阻塞,直到事务A提交以后,事务B才能继续执行。
间隙锁,顾名思义就是给记录之间的间隙加上锁。
需要注意的是,间隙锁只存在于可重复读(Repeatable Read)隔离级别下。
不知道大家还记不记得幻读?
幻读是指在同一事务中,连续执行两次同样的查询语句,第二次的查询语句可能会返回之前不存在的行。
间隙锁的提出正是为了防止幻读中描述的幻影记录的插入而提出的,举个例子。
sessionA | sessionB |
---|---|
begin; | |
select * from user where age=5;(N1) | |
insert into user values(2, '大波浪', 5) | |
update user set name='达闻西' where age=5; | |
select * from user where age=5;(N2) |
sessionA
中有两处查询N1和N2,它们的查询条件都是 age=5,唯一不同的是在N2处的查询前有一条更新语句。
照理说在 RR 隔离级别下,同一个事务中两次查询相同的记录,结果应该是一样的。但是在经过更新语句的当前读查询后(更新语句的影响行数是2),N1和N2的查询结果并不相同,N2的查询将 sessionB
插入的数据也查出来了,这就是幻读。
而如果在 sessionA
中的两次次查询都用上间隙锁,比如都改为select * from user where age=5 for update
。那么 sessionA
中的当前读查询语句至少会将id在(-∞, 5)和(5, 10)之间的间隙加上间隙锁,不允许其他事务插入主键id属于这两个区间的记录,即会将 sessionB
的插入语句阻塞,直到 sessionA
提交之后,sessionB
才会继续执行。
也就是说,当N2处的查询执行时,sessionB
依旧是被阻塞的状态,所以N1和N2的查询结果是一样的,都是(5,重塑,5),也就解决了幻读的问题。
Next-key Lock 其实就是记录锁与记录锁前面间隙的间隙锁组合的产物,它既阻止了其他事务在间隙的插入操作,也阻止了其他事务对记录的修改操作。
不知道大家有没有注意到,我在行锁部分描述记录锁、间隙锁加锁的具体记录时,用的是「至少」二字,并没有详细说明具体加锁的是哪些记录,这是因为记录锁、间隙锁和 Next-key Lock 的加锁规则是十分复杂的,这也是本文主要讨论的内容。
关于加锁规则的叙述将分为三个方面:唯一索引列、普通索引列和普通列,每一方面又将细分为等值查询和范围查询两方面。
需要注意的是,这里加的锁都是指排它锁。
在开始之前,先来回顾一下示例表以及表中可能存在的行级锁。
mysql> select * from user; +----+--------+------+| id | name | age | +----+--------+------+| 5 | 重塑 | 5 | | 10 | 达达 | 10 | | 15 | 刺猬 | 15 | +----+--------+------+3 rows in set (0.00 sec)复制代码
表中可能包含的行级锁首先是每一行的记录锁——(5,重塑,5),(10,达达,5),(15,刺猬,15)。
假设 user 表的索引值有最大值 maxIndex 和最小值 minIndex,user 表还可能存在间隙锁(minIndex,5),(5,10),(10,15),(15,maxIndex)。
共三个记录锁和四个间隙锁。
首先来说唯一索引列的等值查询,这里的等值查询可以分为两种情况:命中与未命中。
当唯一索引列的等值查询命中时:
sessionA | sessionB |
---|---|
begin; | |
select * from user where id=5 for update; | |
insert into user values(1,'斯斯与帆',1),(6,'夏日阳光',6),(11,'告五人',11),(16,'面孔',16); | |
update user set age=18 where id=5;(Blocked) | |
update user set age=18 where id=10; | |
update user set age=18 where id=15; |
Le résultat de l'exécution de sessionB
dans le tableau ci-dessus est qu'à l'exception de l'instruction de mise à jour de la ligne id=5, qui est bloquée, les autres instructions sont exécutées normalement. L'instruction insert dans
sessionB
consiste à vérifier le verrouillage de l'espace, et l'instruction update consiste à vérifier le verrouillage de l'enregistrement (verrouillage de ligne). Les résultats de l'exécution montrent que tous les espaces dans la table utilisateur ne sont pas verrouillés et que seule la ligne avec id=5 est verrouillée dans le verrou d'enregistrement.
Ainsi, lorsque la requête équivalente de la colonne d'index unique arrive, ne verrouillera que l'enregistrement d'accès .
Lorsque la requête d'égalité pour la colonne d'index unique manque :
sessionA | sessionB | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
commencer ; | |||||||||||||||||||||
sélectionner * dans utilisateur où id=3 pour la mise à jour ; | |||||||||||||||||||||
insérer dans les valeurs utilisateur ( 2,'Reflector',2);(
) |
|||||||||||||||||||||
mettre à jour l'ensemble d'utilisateurs age=18 où id=5; | |||||||||||||||||||||
insérer dans les valeurs utilisateur (6,'Summer Sunshine',6); | |||||||||||||||||||||
mettre à jour l'ensemble d'utilisateurs age=18 où id=10 ; | |||||||||||||||||||||
|
insérer dans les valeurs de l'utilisateur (11, 'Sue cinq personnes',11); | ||||||||||||||||||||
mettre à jour l'ensemble d'utilisateurs age=18 où id=15 ; | |||||||||||||||||||||
insérer dans les valeurs de l'utilisateur (16, 'face', 16); td> |
sessionB
Le résultat de l'exécution du tableau ci-dessus est que l'insertion de l'enregistrement avec id=2 dans est bloquée, et d'autres instructions sont exécutés normalement. sessionA
D'après les résultats de l'exécution, nous pouvons savoir que le verrou ajouté par
Ainsi, lorsque la requête équivalente de la colonne d'index unique manque, ajoutera un verrou d'espacement
à l'espace où se trouve la valeur de l'identifiant. 2.2 Requête de plage de colonne d'index unique La requête de plage est plus complexe que la requête de valeur égale. Elle doit prendre en compte si la valeur limite existe dans la table et si la valeur limite est atteinte. Tout d'abord, regardons la situation dans laquelle la valeur limite existe dans le tableau mais est manquée :sessionA | sessionB |
---|---|
begin; | |
select * from user where id | |
insert into user values (1,'斯斯与帆',1);(Blocked) | |
update user set age=18 where id=5;(Blocked) | |
insert into user values (6,'夏日阳光',6);(Blocked) | |
update user set age=18 where id=10;(Blocked) | |
insert into user values (11,'告五人',11); | |
update user set age=18 where id=15; | |
insert into user values (16,'面孔',16) ; |
sessionA | sessionB |
---|---|
début ; | |
insérer dans les valeurs utilisateur (1,'Sisi et Sail',1);( Bloqué |
|
mise à jour l'utilisateur a défini age=18 où id=5;(id=5,id=10 Bloqué) |
|
insérer dans les valeurs utilisateur (6,' Summer Sunshine',6);( Bloqué |
|
mettre à jour l'âge défini par l'utilisateur= 18 où id =10;(Next-key Lock BloquéNext-key Lock ) |
|
insérer dans les valeurs utilisateur (11,' Poursuivre cinq personnes', 11 ); | |
mettre à jour l'ensemble d'utilisateurs age=18 où id=15 ; | tr>|
insérer dans les valeurs utilisateur (16,'face',16) ; |
Les verrous ajoutés à la table utilisateur sont des verrous d'enregistrement et des verrous d'espacement (minIndex,5), (5,10).
, donc la situation de verrouillage ci-dessus peut être considérée comme deux
: (minIndex, 5], (5,10], c'est-à-diresessionA | sessionB |
---|---|
begin; | |
select * from user where id | |
insert into user values (1,'斯斯与帆',1);(Blocked) | |
update user set age=18 where id=5;(Blocked) | |
insert into user values (6,'夏日阳光',6);(Blocked) | |
update user set age=18 where id=10;(Blocked) | |
insert into user values (11,'告五人',11);(Blocked) | |
update user set age=18 where id=15;(Blocked) | |
insert into user values (16,'面孔',16) ; |
sessionA | sessionB |
---|---|
commencer ; | |
sélectionnez * de l'utilisateur où id | |
insérer dans les valeurs utilisateur (1,'Sisi et Sail',1);(Bloqué) | tr>|
mettre à jour l'ensemble d'utilisateurs age=18 où id=5;(Bloqué) | |
insérer dans les valeurs utilisateur (6,'Summer Sunshine',6);(Bloqué) | |
mettre à jour l'ensemble d'utilisateurs age=18 où id=10;(Bloqué) | |
td> | insérer dans les valeurs de l'utilisateur (11,'Poursuivre cinq personnes',11);(Bloqué) |
td> |
mettre à jour l'ensemble d'utilisateurs age=18 où id=15;(Bloqué) |
|
insérer dans les valeurs utilisateur (16,'face',16); |
此时 sessionA
给 user 表加上的锁是Next-key Lock
—— (minIndex,15]。
当边界值不存在于表中时,不可能命中,故只有未命中一种情况:
sessionA | sessionB |
---|---|
begin; | |
select * from user where id | |
insert into user values (1,'斯斯与帆',1);(Blocked) | |
update user set age=18 where id=5;(Blocked) | |
insert into user values (6,'夏日阳光',6);(Blocked) | |
update user set age=18 where id=10;(Blocked) | |
insert into user values (11,'告五人',11); | |
update user set age=18 where id=15; | |
insert into user values (16,'面孔',16) ; |
此时 sessionA
给 user 表加上的锁是 Next-key Lock
—— (minIndex,10],与第一种情况一样。
综上所述,在对唯一索引进行范围查询时:
Next-key Lock
加到第一个边界之外的记录上)需要注意的是,第一条中所说的间隙指的是,边界值所在的间隙,如间隙为(5,10),查询条件为 id>7 时,这个间隙锁就是(5,10),而不是(7,10)。
第二条举例1:查询条件为 idNext-key Lock 锁会加到 id=10 的记录上,被锁住的范围是(minIndex,10]。
第二条举例2:查询条件为 idNext-key Lock 锁会加到 id=15 的记录上,被锁住的范围是(minIndex,15]。
第二条举例3:查询条件为 id>10,第一个边界之外的记录是 id=10,
Next-key Lock
锁会加到 id=10 的记录上,由于Next-key Lock
锁指的是记录以左的部分,所以被锁住的范围是(5,maxIndex]。
普通索引与唯一索引的区别就在于唯一索引可以根据索引列确定唯一性,所以等值查询的加锁规则也有不同之处。
给 user 表再加一条记录:
INSERT INTO user VALUES (11, '达达2.0', 10);复制代码
这时 user 表的索引 age 结构如下图所示:
在索引 age 中可能存在的行锁是4个记录锁以及5个间隙锁。
先来看索引 age 上的加锁情况:
sessionA | sessionB |
---|---|
begin; | |
select * from user where age=10 for update; | |
insert into user values (2,'达达',2); | |
update user set name='痛仰' where age=5; | |
insert into user values (6,'达达',6);(Blocked) | |
update user set name='痛仰' where age=10 and id=10;(Blocked) | |
update user set name='痛仰' where age=10 and id=16;)(Blocked) | |
insert into user values (17,'达达',10);(Blocked) | |
insert into user values (11,'达达',11);(Blocked) | |
update user set name='痛仰' where age=15; | |
insert into user values (16,'面孔',16) ; |
À en juger par les instructions et les résultats d'exécution dans le tableau ci-dessus, la situation de verrouillage sur l'âge d'index est :
C'est-à-dire que la zone de verrouillage sur l'âge d'index est (5, 15).
Étant donné que les index ordinaires ne peuvent pas déterminer le caractère unique des enregistrements, dans les requêtes de valeur égale de colonne d'index ordinaire, lors du verrouillage de l'âge de l'index, trouvera la première valeur avec un âge inférieur à 10 (c'est-à-dire 5) et la première la valeur d'âge est supérieure à 10 (c'est-à-dire 15), les espaces dans cette plage sont ajoutés avec des verrous d'espace et les enregistrements sont ajoutés avec des verrous d'enregistrement .
Il s'agit de la situation de verrouillage sur l'âge de l'index. Puisque l'instruction de requête consiste à interroger toutes les colonnes de l'enregistrement, selon les règles de requête, la valeur d'identification correspondante sur l'âge de l'index sera renvoyée à la clé primaire. arbre d'index pour les opérations de retour de table, et nous obtenons Toutes les colonnes, donc l'index de clé primaire sera également verrouillé. Ici, les ID de clé primaire des enregistrements qui satisfont age=10 sont respectivement 10 et 16, donc ces deux lignes seront également verrouillées exclusivement sur l'index de clé primaire.
C'est-à-dire que si une requête équivalente à une colonne d'index ordinaire doit être renvoyée à la table, la clé primaire correspondant à l'enregistrement qui remplit les conditions sera également ajoutée avec un verrou d'enregistrement .
Si la requête dans
sessionA
est modifiée enselect id from user where age=10 lock in share mode;
, l'opération de retour de table ne sera pas effectuée en raison de l'optimisation de l'index par écrasement, donc l'index de clé primaire ne sera pas verrouillé.
Ici, nous devons mentionner la syntaxe de limit Sa plage de verrouillage (seuls les index ordinaires sont discutés) est plus petite. 🎜>
sessionA | sessionB | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
commencer ; | |||||||||||||||||||||||||
sélectionnez * de l'utilisateur dont l'âge = 10 limite 1 pour la mise à jour ; |
|
||||||||||||||||||||||||
insérer dans les valeurs utilisateur (2,'Dada',2); | mettre à jour l'ensemble d'utilisateurs name='Pain Yang' où age=5 ; | ||||||||||||||||||||||||
td> |
insérer dans les valeurs utilisateur (6,'Dada',6);(Bloqué
|
||||||||||||||||||||||||
td> |
mettre à jour le nom de l'ensemble d'utilisateurs = 'Pain Yang' où age=10 et id=10;( Bloqué ) |
||||||||||||||||||||||||
mettre à jour l'ensemble d'utilisateurs name='Pain Yang' où age=10 et id=16 ;) | |||||||||||||||||||||||||
insérer dans les valeurs utilisateur (17,'Dada',10); | |||||||||||||||||||||||||
insérer dans les valeurs utilisateur (11 ,'Dada',11); | |||||||||||||||||||||||||
mettre à jour l'ensemble d'utilisateurs name='Pain Yang' où age=15; td > | |||||||||||||||||||||||||
insérer dans les valeurs utilisateur (16,'face',16) ; |
Vous pouvez voir que par rapport au fait de ne pas ajouter de limite, deux instructions d'insertion supplémentaires sont exécutées en douceur.
À en juger par les instructions et les résultats d'exécution dans le tableau ci-dessus, la situation de verrouillage sur l'âge de l'index est :
On peut voir que :
syntaxe limite Les verrous ne seront ajoutés qu'aux enregistrements qui remplissent les conditions2.5 Requête de plage de colonne d'index ordinaire
sessionA | sessionB |
---|---|
begin; | |
select * from user where age>8 and age | |
insert into user values (2,'达达',2); | |
update user set name='痛仰' where age=5; | |
insert into user values (6,'达达',6);(Blocked) | |
update user set name='痛仰' where age=10 and id=10;(Blocked) | |
update user set name='痛仰' where age=10 and id=16;(Blocked) | |
insert into user values (17,'达达',10);(Blocked) | |
insert into user values (11,'达达',11);(Blocked) | |
update user set name='痛仰' where age=15;(Blocked) | |
insert into user values (16,'面孔',16) ; |
sessionA | sessionB |
---|---|
commencer ; | |
sélectionner * de l'utilisateur dont l'âge est > 8 et age | |
insérer dans les valeurs utilisateur ( 2,'Dada',2) ; | |
mettre à jour l'ensemble d'utilisateurs name='Pain Yang' où age=5 ; td> | |
insérer dans les valeurs utilisateur (6,'Dada',6);(Bloqué) td> | |
mettre à jour l'ensemble d'utilisateurs name='Pain Yang' où age=10 et id=10;(Bloqué) | mettre à jour l'ensemble d'utilisateurs name='Pain Yang' où age=10 et id=16;(Bloqué) |
insérer dans les valeurs utilisateur (17,'Dada',10);(Bloqué) | |
insérer dans les valeurs utilisateur (11,'Dada',11);(Bloqué) | mettre à jour l'ensemble d'utilisateurs name='Pain Yang' où age=15;(Bloqué) |
insérer dans les valeurs utilisateur (16,'face',16) ; |
与普通索引列等值查询不同的是,范围查询比等值查询多了一个 age=15 的记录锁。
这个边界值与唯一索引列范围查询的原理是一样的,可以参照上文所述来理解,这里不多加赘述了。
《MySQL实战45讲》的作者丁奇认为这是一个 BUG,但并未被官方接收,如果要深究这个边界值的原理,可能就需要看 MySQL 的源码了。
select * from user where age=10 for update;select * from user where age>=10 and age=10 and age<blockquote><p><strong>更多相关免费学习推荐:</strong><a href="https://www.php.cn/course/list/51.html" target="_blank"><strong>mysql教程</strong></a><strong>(视频)</strong></p></blockquote>
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!