8000字长文,MySQL中的锁机制解密

引言

MySQL,作为最流行的开源关系数据库管理系统之一,被广泛应用于各种应用程序和网站。

MySQL的锁则是MySQL在高并发场景下保证数据的一致性和完整性的重要机制。

学习和理解MySQL的锁机制,有助于更好的通过MySQL实现更高性能、更可靠的业务系统。

本文实验环境

本文会结合一些例子来介绍这些概念,如果没有特别说明,基于以下环境

MySQL:5.7
存储引擎: InnoDB
隔离级别: REPEATABLE-READ
MySQL官方文档: https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html

本文基础测试数据

| t_user | CREATE TABLE `t_user` (
  `id` int(10unsigned NOT NULL,
  `name` varchar(100NOT NULL,
  `age` int(11NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_age` (`age`)
ENGINE=InnoDB DEFAULT CHARSET=utf8 |

mysql> select * from t_user;
+----+--------+-----+
| id | name   | age |
+----+--------+-----+
|  1 | user01 |  21 |
|  3 | user03 |  23 |
|  6 | user06 |  26 |
| 10 | user10 |  30 |

基本概念

锁的定义

在数据库中,锁是一种用于控制多个事务并发访问数据库中相同数据的机制。它可以帮助保证在任何给定的时间,只有一个事务可以访问特定的数据项。

锁的作用和目的

锁的主要作用是保证数据的一致性和完整性。在并发环境中,如果多个事务试图同时修改同一条数据,可能会导致数据的不一致性。

通过使用锁,我们可以确保在任何时候只有一个事务能够访问特定的数据,从而避免数据的不一致性和冲突。此外,锁也可以用于实现某些复杂的业务逻辑。

锁的分类

在MySQL中(innoDB存储引擎),锁的相关概念如下图所示:

8000字长文,MySQL中的锁机制解密

这也是本文将重点介绍的内容

乐观锁和悲观锁(思想/策略)

乐观锁和悲观锁是处理并发操作时常用的两种策略,它们并不是真正意义上的锁,而是一种设计理念或者说是思想。

乐观锁(Optimistic Locking):

乐观锁假设数据通常情况下不会造成冲突,所以在数据进行提交更新时,才会真正的进行锁定,这样就避免了长时间的等待,提升了并发性能。

乐观锁在MySQL中并没有特定的实现,但可以通过版本号、时间戳等方式实现。

在InnoDB的MVCC机制中,就运用了乐观锁的思想。MVCC机制中个,通过隐藏列DB_TRX_ID存储记录被insertupdate的最后一个事务的事务ID。(delete在内部也被视为update)。当有事务进行update动作时,只需要判断记录的事务ID与自己一致性读快照中的ID是否一致。

8000字长文,MySQL中的锁机制解密

悲观锁(Pessimistic Locking):

悲观锁假设数据往往会造成冲突,所以在数据处理前,就会进行加锁操作,以确保数据处理的排他性。悲观锁适用于写操作多的场景,防止数据被其他事务修改。在MySQL中,可以通过SELECT ... FOR UPDATE语句实现行级的悲观锁。

8000字长文,MySQL中的锁机制解密

锁的级别/粒度

锁的级别或粒度主要有三种:行级锁、页级锁和表级锁。

行级锁(Row-level Locking):

行级锁是最小的锁定粒度,它可以精确到数据库表中的一行数据。这种锁定级别可以提供最高的并发处理能力,并且在大多数需要高并发读写的OLTP(在线事务处理)系统中,行级锁是最常用的锁定策略。

行级锁的优点是并发性高,因为当多个用户访问多行数据时,只有当用户访问相同数据行时才会产生锁冲突。但是,行级锁的缺点是开销大,因为对于每一行数据都需要存储和管理锁信息,这在大量数据操作时可能会消耗大量的系统资源。

页级锁

页锁是一种特殊的锁定策略,它允许多个事务同时对不同的数据页进行读写操作。页锁的粒度介于表锁和行锁之间,它锁定的是数据库页,即一组连续的数据行。这种锁定策略在处理大量数据时,可以提供比行锁更好的并发性,同时也减少了锁定所需的资源和管理开销。

表级锁(Table-level Locking):

表级锁是MySQL中的一种更粗粒度的锁定机制,它会锁定整个数据库表。这种锁定级别在读取大量数据,且不需要进行数据修改的OLAP(在线分析处理)系统中更为常见。

表级锁的优点是开销小,因为无论表中有多少数据,MySQL都只需要为整个表存储和管理一把锁。但是,表级锁的缺点是并发性较差,因为当一个用户对表进行写操作时,其他用户的读写操作都会被阻塞,直到写操作完成。

下面是表级锁的常见场景:

  1. **批量插入:**当你需要在短时间内向数据库表中插入大量数据时,可能需要使用表锁。这是因为在这种情况下,行锁可能会导致过多的开销。通过锁定整个表,你可以一次性插入所有数据,然后再解锁表,这样可以提高效率。
  2. **全表更新:**如果你需要更新表中的所有行,那么使用表锁可能会更有效。这是因为在这种情况下,行锁可能会导致大量的锁定冲突和开销。通过锁定整个表,你可以一次性完成所有更新,然后再解锁表。
  3. **数据库维护:**在进行某些数据库维护任务(如备份、优化或重建索引)时,可能需要锁定整个表,以防止在维护过程中发生数据更改。ALTER TABLE table_name ADD column_name column_type;
  4. **高并发读:**在高并发读的场景下,如果不需要考虑数据的实时更新,可以使用表锁,将整个表锁定,这样可以避免频繁的行锁开销。

共享锁与排它锁

共享锁与排它锁

InnoDB实现了两种类型的行级锁。

  • 共享锁(读锁)-S: 共享锁允许持有该锁的事务能够读取锁定行。
  • 排它锁(写锁)-X: 排它锁允许持有该锁的事务能够更新和删除锁定行。

共享锁与排它锁的兼容性如下:

  • 当一个事务T1持有共享锁时,另一个事务T2可以被授予共享锁不可以被授予排它锁
  • 当一个事务T1持有排它锁时,另一个事务T2不可以被授予共享锁排它锁

8000字长文,MySQL中的锁机制解密

意向锁

意向锁简介

InnoDB中的意向锁是一种表级锁,用于指示稍后将对表中的行施加那种锁类型(共享锁或排它锁)。

意向锁的主要目的是为了在一个事务试图获取一个锁时,能够知道有其他事务是否已经在该数据对象上持有锁。这样可以避免死锁的发生,提高数据库的并发性能。意向锁是用于提升表级锁(共享锁、排它锁)的加锁效率的

InnoDB支持多粒度锁(表级锁、行级锁),如果没有意向锁,当我们要加表级锁是,很可能需要扫描表中所有的行,检查这些行是否有行级别的与要施加的表级锁互斥的行级锁,如果表级锁的加锁效率就十分糟糕了。

我们看下意向锁如果提升表级锁的加锁效率。

意向锁可以分为两类

  • 共享意向锁-IS:指示事务将对表中的各行施加共享锁。
  • 排它意向锁-IX: 指示事务将对表中的各行施加排它锁。

这两种锁都不会阻止其他事务访问被锁定的数据,但它们会阻止其他事务在被锁定的数据上获取更高级别的锁。

什么时候会设置意向锁

  • 当一个事务要对表中的设置共享锁时,它需要先对这个施加共享意向锁或者排它意向锁。
  • 当一个事务要对标中的设置排它锁时,它需要先对这个施加排它意向锁

意向锁的兼容性

意向锁和表级共享/排它锁的兼容性如下表所示

8000字长文,MySQL中的锁机制解密

虽然意向锁是表级锁,但是因为施加行锁前需要先设置表级意向锁,因此意向锁不仅影响了表级锁的设置过程,也会影响行级锁的设置过程。

具体的影响可以小结为以下3点

  1. 对其它意向锁设置过程的影响:

    意向锁之间都是互相兼容的。也就是说,一个事务对表施加了意向锁,并不会阻塞其他事务对该表施加任意一种意向锁

  2. 对表级共享锁、排它锁设置过程的影响:

    事务请求表级共享锁、排它锁时,需要先检测该表上是否被设置了与之互斥的意向锁。

    举个例子,事务Trx02在要执行 LOCK TABLES db_windeal.t_user WRITE , (这条语句会请求 t_user 表的排它锁)。但是检测发现有另一个事务Trx01正在执行 SELECT * FROM db_windeal.t_user WHERE id=1 LOCK IN SHARE MODE;id=1的记录施加了行级锁,但是对表加了意向共享锁。 因为事务Trx02的表级排它锁与事务Trx01的意向共享锁冲突,事务Trx02只能阻塞。

  3. 对行级共享锁、排它锁设置过程的影响:

    当需要表中的某一行设置行级锁时,需要先请求所在表对应的意向锁;而请求的意向锁时需要检测当前表中是否有与之互斥的表级意向锁或排他锁。比如某个事务Trx02执行 SELECT * FROM db_windeal.t_user WHERE id=1 LOCK IN SHARE MODE; 需要先对 db_windeal.t_user``请求意向共享锁 。如果这时检测到另一个事务Trx01已经对db_windeal.t_user设置了排它锁,因为两者的互斥关系,Trx02只能阻塞。

记录锁

记录锁是对索引记录加的锁。

当我们使用唯一索引唯一行进行检索,并且检索到结果时,会对结果行设置记录锁。

举个例子:假设我们有一个事务需要修改id为3的用户的年龄,那么我们可以对这一行加上记录锁,SQL语句可能如下:

BEGIN-- 开始事务
SELECT * FROM users WHERE id = 3 FOR UPDATE-- 加记录锁
UPDATE users SET age = 24 WHERE id = 3-- 修改数据
COMMIT-- 提交事务,释放锁

在这个例子中,SELECT ... FOR UPDATE语句会对id为3的行加上记录锁。这意味着,其他事务不能修改这一行,直到当前事务提交(COMMIT)并释放锁。但是,其他事务仍然可以访问和修改表中的其他行,例如id为1、6和10的行。

记录锁总是作用于索引记录。

记录锁在SHOW ENGINE INNODB STATUS 中展示如下

SHOW ENGINE INNODB STATUS
TABLE LOCK table `db_windeal`.`t_user` trx id 1875 lock mode IX
RECORD LOCKS space id 26 page no 3 n bits 72 index PRIMARY of table `db_windeal`.`t_user` trx id 1875 lock_mode X locks rec but not gap
Record lockheap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 4; hex 00000003; asc     ;;
 1: len 6; hex 000000000739; asc 9;;
 2: len 7; hex aa0000011e011c; asc        ;;
 3: len 6; hex 757365723033; asc user03;;
 4: len 4; hex 80000017; asc     ;

可以看到添加了一个lock_mode X locks rec but not gap

InnoDB默认不会检测锁的状态,需要通过下列配置才能从SHOW ENGINE INNODB STATUS 中看到锁的状态

SET GLOBAL innodb_status_output=ON;
SET GLOBAL innodb_status_output_locks=ON;

需要注意的是,记录锁只在InnoDB存储引擎中有效,因为只有InnoDB支持事务和行级锁定。如果你使用的是MyISAM等不支持事务的存储引擎,那么MySQL会使用表锁,而不是记录锁。

间隙锁 Gap Lock

间隙锁(Gap Locks)是MySQL中InnoDB存储引擎特有的一种锁定机制。间隙锁不是锁定表中的实际数据行,间隙锁是对索引记录的间隙的锁(包括第一条索引记录前的区间和最后一条索引记录之后的区间)。

间隙锁的主要目的是防止幻读(Phantom Reads)。幻读是指在一个事务内,多次执行相同的查询,但由于其他事务的插入操作,导致每次返回的结果集不同。被施加了间隙搜的间隙gap不允许插入新的记录。

间隙锁锁定的是基于索引记录的全开区间,前面的db_windeal.t_user表可以划分为四个gap:

(-∞, 1), (1,3), (3,6),(6,10), (10, +∞);

关于间隙锁和临键锁在什么时候设置,我这边的实验结果和mysql官方文档的介绍不太一致:

1 . 官方文档没有对比介绍间隙锁和临键锁,按官方文档的介绍,像SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;这样的语句会设置间隙锁。 但实际通过SHOW ENGINE INNODB STATUS 显示的结果是设置了临键锁Next-Key Lock。(不过也不能说他写错了,因为临建锁本就是记录锁加间隙锁;)
2. 官方文档说 Gap locking is not needed for statements that lock rows using a unique index to search for a unique row.  但是实际上如果查询条件没有检索到记录(比如我们的测试数据里使用了WHERE id=7),那么还是会设置间隙锁。

间隙锁示例:

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM t_user WHERE id=7 FOR UPDATE;
Empty set (0.04 sec)
mysql> SHOW ENGINE INNODB STATUS ;
...
SHOW ENGINE INNODB STATUS
TABLE LOCK table `db_windeal`.`t_user` trx id 1879 lock mode IX
RECORD LOCKS space id 26 page no 3 n bits 72 index PRIMARY of table `db_windeal`.`t_user` trx id 1879 lock_mode X locks gap before rec
Record lockheap no 5 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 4; hex 0000000a; asc     ;;
 1: len 6; hex 000000000739; asc      9;;
 2: len 7; hex aa0000011e0134; asc       4;;
 3: len 6; hex 757365723130; asc user10;;
 4: len 4; hex 8000001e; asc     ;;
...

可以看到在记录id=10,name='user10'这一条记录前的一个间隙(6,10)添加了一个间隙锁 lock_mode X locks gap before rec

不需要区分共享间隙锁和排它间隙锁,间隙锁之间也不会产生冲突,甚至当删除某条索引记录时,间隙锁的gap还会发生合并。

间隙锁是对性能与并发进行权衡衍生的折衷的算法,只在REPEATABLE READSERIALIZABLE这两个隔离级别下有效。在READ COMMITTEDREAD UNCOMMITTED这两个隔离级别下,InnoDB不会使用间隙锁。

临键锁 Next-Key Lock

临键锁 Next-Key Lock 是索引记录的记录锁和索引记录之前的间隙锁的组合。

临键锁 = 记录锁 + 间隙锁

Next-Key Lock每次锁定的是一个基于索引记录左开右闭的区间(最后一个区间的右端点是一个supremum伪端点,表示为正无穷), 前面的db_windeal.t_user表可以划分为四个临键锁区间:

(-∞, 1], (1,3], (3,5], (5, +∞);

InnoDB使用临键锁的主要目的也是为了来防止幻读。施加了临键锁的左开右闭区间里,不允许插入新的记录。

间隙锁与临键锁

什么时候产生间隙锁,什么时候产生临键锁

  1. 当查询的范围内不存在记录,就是产生间隙锁。(比如上述数据执行 SELECT * FROM db_windeal.t_user WHERE id>1 and id < 3 FOR UPDATE;
  2. 当查询的范围内存在记录,就会产生记录锁和间隙锁,加起来就是临键锁。((比如上述数据执行 SELECT * FROM db_windeal.t_user WHERE id>1 and id <= 3 FOR UPDAT

临键锁示例:

  • 示例01
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM t_user WHERE id>3 AND id<6 FOR UPDATE;
Empty set (13.22 sec)
mysql> SHOW ENGINE INNODB STATUS ;
...

SHOW ENGINE INNODB STATUS
TABLE LOCK table `db_windeal`.`t_user` trx id 1886 lock mode IX

RECORD LOCKS space id 26 page no 3 n bits 72 index PRIMARY of table `db_windeal`.`t_user` trx id 1886 lock_mode X

Record lockheap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 4; hex 00000006; asc     ;;
 1: len 6; hex 000000000739; asc      9;;
 2: len 7; hex aa0000011e0128; asc (;;
 3: len 6; hex 757365723036; asc user06;;
 4: len 4; hex 8000001a; asc     ;;
...

从输出可得出,(3,6]这个区间被施加了临键锁。

  • 示例02
mysql> BEGIN;
Query OK, 0 rows affected (0.01 sec)
mysql> SELECT * FROM t_user WHERE id>3 AND ID<=6 FOR UPDATE;
+----+--------+-----+
| id | name  | age |
+----+--------+-----+
| 6 | user06 | 26 |
+----+--------+-----+
1 row in set (0.03 sec)
mysql> SHOW ENGINE INNODB STATUS ;
...
SHOW ENGINE INNODB STATUS
TABLE LOCK table db_windeal.t_user trx id 1887 lock mode IX
RECORD LOCKS space id 26 page no 3 n bits 72 index PRIMARY of table db_windeal.t_user trx id 1887 lock_mode X
Record lockheap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
0: len 4; hex 00000006; asc    ;;
1: len 6; hex 000000000739; asc      9;;
2: len 7; hex aa0000011e0128; asc (;;
3: len 6; hex 757365723036; asc user06;;
4: len 4; hex 8000001a; asc    ;;
Record lockheap no 6 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
0: len 4; hex 0000000a; asc    ;;
1: len 6; hex 00000000075a; asc      Z;;
2: len 7; hex 46000001880110; asc F      ;;
3: len 6; hex 757365723130; asc user10;;
4: len 4; hex 8000001e; asc    ;;
...

从输出结果可以得到,SELECT * FROM t_user WHERE id>3 AND ID<=6 FOR UPDATE;对区间(3,6], (6,10]两个区间设置了临键锁。

当检索的结果包含记录时,该记录的下一个临键锁区间也会被设置临键锁。

插入意向(间隙)锁

插入意向锁(Insert Intention Lock)是一种特殊类型的意向锁,主要用于处理数据库中的并发插入操作。这种锁的主要目的是防止两个事务同时插入相同的键值,从而导致数据不一致。

插入意向锁在INSERT操作时,对插入行对应的区间设置的一种间隙锁。插入意向锁是间隙锁,注意和前面提到的意向锁(表级锁)进行区分。

插入意向锁通常应用于索引数据结构中,特别是在B树索引中。在B树索引中,数据是按照键值的顺序存储的,每个键值之间都有一个间隙。插入意向锁就是在这些间隙上设置的。

插入意向锁示例

假设有两个事务T1和T2,它们都想在id为2的位置插入一条新的记录。如果没有插入意向锁,那么可能会出现以下情况:

  1. T1开始执行,检查到id为2的位置没有记录,所以它开始插入新的记录。
  2. 在T1还没有完成插入操作的时候,T2开始执行,也检查到id为2的位置没有记录,所以它也开始插入新的记录。
  3. 结果,T1和T2都在id为2的位置插入了新的记录,导致了数据不一致。

但是,如果我们在id为2的位置设置了一个插入意向锁,那么就可以避免这种情况。当T1开始执行时,它会首先获取插入意向锁,然后开始插入操作。在这个过程中,如果T2也想在id为2的位置插入新的记录,它会发现已经有一个插入意向锁,所以它会等待T1完成插入操作并释放锁,然后再开始插入操作。这样就可以保证数据的一致性。

自增锁 AUTO-INC Locks

InnoDB的自增锁(Auto-Increment Lock)是一种特殊类型的表级锁,用于处理自增字段的并发插入操作。自增字段是数据库中的一种特殊字段,每当插入一条新的记录时,它的值会自动增加。

例如,假设我们有一个表,其中有一个自增字段id。每当我们插入一条新的记录时,id的值就会自动增加1。如果有多个事务同时尝试插入新的记录,那么就需要一个机制来确保每个事务都能获取到一个唯一的id值。这就是自增锁的作用。

8000字长文,MySQL中的锁机制解密

在深入理解自增锁之前,我们先了解下INSERT预计的三种类别(参考InnoDB AUTO_INCREMENT Lock Modes)

  • Simple inserts: 我们可以提前知道插入行数的INSERT语句,不带子查询和 ON DUPLICATE KEY UPDATE;并且由引擎对自增列进行赋值。

  • Bulk inserts: 无法事先计算插入行数的INSERT语句,一般是带有子查询语句;

  • Mixed-mode inserts: 两种场景混合:

    这两种场景我们可以预测所需自增值的最大数量。

    • 1.批量插入时指定部分记录行的自增列值。

    1. ON DUPLICATE KEY UPDATE

自增锁有三种工作模式,通过innodb_autoinc_lock_mode 参数设置。

  • 传统模式 innodb_autoinc_lock_mode=0
    基于带有自增列的表的所有INSERT-statements(非事务)都需要持有自增锁。

  • 连续模式 innodb_autoinc_lock_mode=1
    连续模式是自增锁的默认模式。此模式下,Bulk inserts需要持有自增锁直到语句执行结束;而 Simple inserts只需要在分配自增值时使用轻量级互斥锁(不需要持有到语句结束,也不需要表级的自增锁)。对于可以预测所需自增值的最大数量的Mixed-mode inserts,会分配超出实际数量需求的连续自增值。

  • 交叉模式 innodb_autoinc_lock_mode=2
    任何INSERT语句都不需要持有自增锁。

低级锁对象(互斥锁、读写锁与自旋锁)

前面介绍的各种锁概念都是面向表、记录,是高级的InnoDB中的高级对象。

从操作系统层面而言,或者说对内部内存数据结构的访问控制上,主要是使用互斥锁、读写锁这两种锁对象。

互斥锁-mutexes

互斥锁是排它的,一旦互斥锁被获取,其他进程、线程等就无法再获取相同的锁。

互斥锁是最基本的锁类型,用于保护共享资源的访问。

一个线程在访问某个资源时,首先需要获得相应的互斥锁,然后才能访问该资源。在访问完成后,线程需要释放互斥锁。如果一个资源已经被一个线程获得互斥锁,那么其他试图访问该资源的线程将被阻塞,直到互斥锁被释放。

读写锁-rw-lock

读写锁是一种更复杂的锁类型,它将对共享资源的访问分为读访问和写访问,并允许多个线程同时进行读访问。但是,对于写访问,读写锁只允许一个线程进行。这样,读写锁可以提高在读多写少的情况下的并发性能。

读写锁类型包含三种子对象,访问控制规则如下:

  • 共享锁 s-lock: 允许对相同资源进行读访问。
  • 排它锁 x-lock: 提供了对公共资源的写访问,同时不允许其他线程对公共资源的不一致读、写访问。
  • 共享/排它锁 sx-lock: 提供了对公共资源的写访问,同时允许其他线程对公共资源进行读访问

自旋锁

自旋锁是一种特殊的互斥锁,当一个线程试图获得一个已经被其他线程持有的自旋锁时,它会在一个循环中不断地检查锁是否已经被释放,而不是进入睡眠状态。自旋锁适用于锁持有时间非常短的情况,因为它避免了线程切换的开销。但是,如果锁持有时间较长,自旋锁可能会浪费大量的CPU时间。

  • 对于单核系统而言,获取锁失败后,就只能阻塞,释放CPU等系统资源。等待一段时间后再继续检查。因为涉及到上下文的切换,性能较差。
  • 对于多核系统而言,获取锁失败后,不需要直接进入阻塞,它可以继续拥有系统资源,进行自旋轮询一段时间。

因此,自旋锁其实是在多核系统中,获取互斥锁、读写锁失败的场景下,通过自旋轮询而非直接阻塞(切换出上下文)来持续检查、尝试是否可以获取锁的一种算法。

死锁

死锁是指两个或更多的进程或线程在执行过程中,因争夺资源而造成的一种相互等待的现象,若无外力干涉那它们都将无法推进下去。这种情况在并发操作中经常出现,尤其是在数据库操作中。

MySQL 出现死锁的几个要素:

  1. 互斥条件: 不同事务对某个资源而持有的锁是互斥的,及一个资源只能被一个事务持有锁。
  2. 请求与保持条件: 事务持有锁后,在请求新的锁时,保持持有已持有的旧锁不释放。
  3. 不剥夺条件: 事务已获得的资源(持有锁),在未使用完之前,不能强行剥夺。
  4. 循环等待条件: 事务之间因为持有锁和申请锁导致彼此循环等待

预防死锁的基本方法

预防死锁的基本方法是破坏死锁的四个必要条件:

  1. 互斥条件: 这个条件在我们的任务中无法避免,因为某些资源(例如打印机、文件等)在任何时候只能供一个进程使用。
  2. 请求与保持条件: 一种可能的解决方案是,如果一个进程请求的资源被其他进程占用,那么它必须释放其已经占用的所有资源,然后等待。
  3. 不剥夺条件: 如果一个进程已经占用了一些资源,并且请求的资源被其他进程占用,那么它可以释放其已经占用的所有资源,然后重新请求。
  4. 循环等待条件: 为所有的资源类型定义一个线性的顺序,并要求每个进程按照这个顺序请求资源,这样就不会出现循环等待。

预防死锁最佳实践

  1. 合理的设计索引,缩小扫描范围,缩小加锁范围,减少锁竞争。
  2. 调整事务中SQL的顺序,将update/delete等需要还有锁的语句靠后执行。
  3. 避免大事务,尽量将大事务拆成多个小事务来处理,小事务发生锁冲突的几率也更小。
  4. 以固定的顺序访问表和行。
  5. 在并发比较高的系统中,不要显式加锁,特别是是在事务里显式加锁。如 select … for update 语句,如果是在事务里(运行了 start transaction 或设置了autocommit 等于0),那么就会锁定所查找到的记录。
  6. 优化查询语句,尽量用主键、索引等进行精确查找,减少锁定范围。
  7. 优化 SQL 和表设计,减少同时占用太多资源的情况。比如说,减少连接的表,将复杂 SQL 分解为多个简单的 SQL。

欢迎关注作者公众号: 海天二路搬砖工


原文始发于微信公众号(海天二路搬砖工):8000字长文,MySQL中的锁机制解密

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/233468.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!