MySQL锁机制

系列文章目录

一、MySQL数据结构选择
二、MySQL性能优化explain关键字详解
三、MySQL索引优化
四、MySQL事务
五、MySQL锁机制
六、MySQL多版本并发(MVCC)机制


文章目录

  • 系列文章目录
  • 一、MySQL锁机制概述
  • 二、悲观锁
  • 三、乐观锁
  • 四、表锁、行锁、页锁
    • 4.1、表锁
    • 4.2、行锁
    • 4.3、页锁
  • 五、读锁、写锁、意向锁
    • 5.1、读锁
    • 5.2、写锁
    • 5.3、意向锁
  • 六、间隙锁、临键锁


一、MySQL锁机制概述

  MySQL的锁机制是数据库并发控制的重要组成部分,目的是防止多个事务对数据的并发访问导致数据不一致或其他问题,区别于JUC并发编程中的锁,从不同的角度,MySQL的锁通常分为:

  • 乐观锁、悲观锁。
  • 表锁、页锁、行锁。
  • 读锁、写锁、意向锁。
  • 间隙锁、临键锁。

  锁的两个重要概念是粒度冲突,锁的粒度指的是锁作用的范围,粒度越小,锁的冲突概率越低,系统的并发能力越强;但管理锁的开销也会增加。锁冲突是指两个或多个事务试图以不兼容的方式访问相同的数据。例如,一个事务试图加排他锁,而另一个事务已经持有该数据的共享锁。
  锁的相关命令:

  • LOCK TABLES:手动锁定一个或多个表,可以加共享锁或排他锁。
  • UNLOCK TABLES:释放通过LOCK TABLES命令获得的锁。
  • SELECT … FOR UPDATE:用于显式地为查询结果集中的行加排他锁,通常用于事务中的数据更新操作。
  • SELECT … LOCK IN SHARE MODE:用于为查询结果集中的行加共享锁。

二、悲观锁

  和JUC的悲观锁思想一样,“假设会发生冲突”,因此在数据操作前,它会主动获取锁,防止其他事务并发地修改数据。悲观锁适用于冲突概率较高的场景,特别是在对数据一致性要求较高的情况下。
  通常会在对数据进行操作之前显式地加锁,并且操作完成之后才释放锁。锁定的时间长,贯穿整个操作的过程。
  MySQL的InnoDB存储引擎通过行级锁来实现悲观锁,使用SELECT FOR UPDATELOCK IN SHARE MODE语句来加锁数据。

  • SELECT FOR UPDATE:当查询结果集中的行被锁定时,其他事务无法修改这些行,直到当前事务提交或回滚。
  • LOCK IN SHARE MODE:读取数据并加共享锁,允许其他事务读取数据,但不能修改数据。
BEGIN;
SELECT * FROM account WHERE account_id = 1 FOR UPDATE;  -- 获取排他锁
UPDATE account SET balance = balance - 100 WHERE account_id = 1;
COMMIT;

  上述的sql语句,在执行更新操作时,account_id = 1的行被锁定,其他事务无法同时对该行数据进行修改。

三、乐观锁

  乐观锁的思想在于,“假设不会发生冲突”,也就是在对于数据的操作时不会显式地加锁,而是在操作完成后,对原始数据进行比对,确定本事务在操作的过程中,数据没有被其他数据修改。如果原始数据已经被修改,则放弃本次更新,再次重试。(CAS机制)。
  乐观锁通常通过版本号或时间戳来实现:

  • 版本号机制:在表中增加一个版本号字段,每次更新数据时,版本号都会增加。事务在修改数据时,需要检查数据的版本号,确保数据没有被其他事务修改。
  • 时间戳机制:在表中增加一个时间戳字段,记录每条记录的最后修改时间。事务提交时检查数据的时间戳是否发生变化,若变化则回滚事务。
-- 事务A读取数据
SELECT id, name, price, version FROM products WHERE id = 1;

-- 事务A尝试更新数据,检查版本号是否一致
UPDATE products 
SET price = price + 10, version = version + 1
WHERE id = 1 AND version = 1;  -- version = 1 是事务A读取时的版本号

  如果事务A读取时的版本号和更新时的版本号不一致,表示数据已被其他事务修改,事务A将回滚或重试。
  乐观锁和悲观锁相比,减少了显式加锁的开销,但是乐观锁重试的过程对于cpu性能也有一定的影响。适用于冲突概率不高的场景。

特性 悲观锁 (Pessimistic Lock) 乐观锁 (Optimistic Lock)
锁的策略 事务操作时会加锁,假设有冲突 事务操作时不加锁,假设没有冲突,提交时检查冲突
锁的粒度 锁的粒度较大,通常为行级锁或表级锁 锁的粒度较小,通常基于版本号或时间戳字段来实现
并发性能 可能会导致较高的锁竞争,性能下降 性能较好,适用于读多写少的场景,但存在冲突时需要重试
使用场景 适用于冲突概率较高,数据一致性要求较高的场景 适用于冲突概率较低,数据读取较多,写入较少的场景
实现复杂度 相对简单,直接通过数据库加锁实现 实现较为复杂,需要额外的字段(如版本号或时间戳)来支持冲突检测
锁的持续时间 锁持有时间较长,直到事务提交或回滚 锁持有时间较短,事务提交时才检查冲突

四、表锁、行锁、页锁

4.1、表锁

  表锁指的是,对于表中某一行数据的修改,会将整张表锁定。MyISAM存储引擎默认使用表锁。其开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低,表锁可以通过 LOCK TABLES 命令显式地锁定:

LOCK TABLES 表名 WRITE; 

  通常是在对于数据库表迁移时使用。

4.2、行锁

  和表锁相对应,行锁则是需要修改表中的哪一行数据,就对于该行数据上锁。InnoDB存储引擎默认使用行锁。相较于表锁,力度小,并发性能高,上文中悲观锁的SELECT FOR UPDATELOCK IN SHARE MODE语句便可以显式加行锁。

4.3、页锁

  页锁是介于表锁和行锁之间的锁,锁定的是数据页。数据页是数据库中存储数据的最小单位,MySQL的InnoDB存储引擎将数据组织在大小为16KB的页面中。相当于行锁和表锁的折中方案。锁粒度适中,适用于中等并发的场景。

特性 表锁 (Table-level Lock) 行锁 (Row-level Lock) 页锁 (Page-level Lock)
锁定粒度 锁定整个表 锁定某一行 锁定某一数据页(通常为16KB)
并发性 最差,多个事务对同一表的操作会阻塞其他事务 最好,多个事务可以并发操作不同的行 中等,多个事务可以并发操作不同的页,但同一页内的操作会发生阻塞
锁管理开销 最小,锁定粒度大,管理开销较小 最大,锁管理较为复杂 中等,管理开销大于表锁但小于行锁
适用场景 适用于低并发、操作简单的场景 适用于高并发、高频繁更新的场景 适用于中等并发和批量操作的场景
死锁概率 最低 较高,尤其是多个事务竞争同一行数据时 中等,死锁发生的概率低于行锁,但高于表锁

五、读锁、写锁、意向锁

5.1、读锁

  读锁是控制共享访问的锁,即多个事务可以对同样的数据进行读操作,但是任何事务都不可进行写操作

-- 事务A读取数据,获取共享读锁
BEGIN;
SELECT * FROM account WHERE account_id = 1;
-- 事务A在此时可以读取数据,但无法进行更新

5.2、写锁

  写锁是控制单独访问的锁,即只有持有写锁的事务,可以对数据进行写操作,同时阻止其他事务对该数据进行读写操作

-- 事务B更新数据,获取排他写锁
BEGIN;
UPDATE account SET balance = balance - 100 WHERE account_id = 1;
-- 事务B执行期间,其他事务无法读取或修改该行数据

5.3、意向锁

  意向锁本身并不实际锁定数据,而是用来表明事务的意图。当有事务给表的数据行加了共享锁或排他锁,同时会给表设置一个标识,代表已经有行锁了,其他事务要想对表加表锁时,就不必逐行判断有没有行锁可能跟表锁冲突了,直接读这个标识就可以确定自己该不
该加表锁。
  意向锁有助于协调不同粒度的锁(如表锁和行锁),保证事务在锁的管理中不会发生冲突,可以分为

  • 意向共享锁(IS,Intention Share):表示事务打算对某一行加共享锁。
  • 意向排他锁(IX,Intention Exclusive):表示事务打算对某一行加排他锁。

  假设我们有一个 account 表,事务A想要对某行数据加行级锁,但首先会加意向锁。

-- 事务A对表加意向排他锁,表示打算对某行加排他锁
LOCK TABLES account WRITE;
-- 事务A对具体的行加排他锁
SELECT * FROM account WHERE account_id = 1 FOR UPDATE;

锁类型 描述 并发情况 应用场景
读锁(共享锁) 允许多个事务共享读取数据,但不能修改数据。 多个事务可以同时加读锁,互不影响。 读多写少的场景,如报告查询、数据统计等。
写锁(排他锁) 只允许一个事务对数据进行修改,其他事务无法读取或修改该数据。 会阻塞其他事务,直到持锁事务完成。 数据更新、插入、删除等操作。
意向锁 用于表明事务打算对某些行加锁的意图,不实际锁定数据。 不会阻塞其他事务,但会影响对数据的锁定操作。 多粒度锁的场景,主要用于协调表锁和行锁。

六、间隙锁、临键锁

  间隙锁锁定的是范围数据中的间隙,并且是在可重复读隔离级别下才会生效,假设目前表中的数据如下,有(4,10),(10,15),(15,正无穷)的三个区间
MySQL锁机制_第1张图片  执行如下的sql:

select * from account WHERE id = 11 for UPDATE

MySQL锁机制_第2张图片
  则其他连接无法在(10,15)区间内插入任何数据,待事务提交后方可插入。
MySQL锁机制_第3张图片

你可能感兴趣的:(MySQL,mysql,数据库,java)