【MySQL】锁机制

目录

前情提要

什么是事务?

定义:事务是一组必须全部成功或全部失败的数据库操作(例如:转账需要同时完成扣款和收款)

ACID 特性

  • 原子性(Atomicity):事务要么全部执行,要么全部回滚
  • 一致性(Consistency):事务执行后数据库必须处于合法状态(如账户余额不能为负数)
  • 隔离性(Isolation):事务之间互相隔离,通过锁机制实现
  • 持久性(Durability):事务提交后数据永久保存

为什么需要锁?

典型问题:两个用户同时购买最后一个商品,无锁会导致超卖

锁的作用:像"单线程"操作关键数据,防止并发冲突

锁的分类

按锁的粒度分类

全局锁(Global Lock)

定义:锁定整个 MySQL 实例,所有数据库的写操作会被阻塞。

使用场景 :全库逻辑备份(如 mysqldump)。

语法

sql 复制代码
FLUSH TABLES WITH READ LOCK;  -- 加全局读锁
UNLOCK TABLES;               -- 释放锁

注意事项

  • 在 MySQL 里并没有像全局读锁 FLUSH TABLES WITH READ LOCK 那样专门用于全局写操作的全局写锁语句

  • 全局锁会导致业务停摆,备份推荐用 mysqldump --single-transaction(仅限 InnoDB)

表级锁(Table-Level Lock)

定义:锁定整张表,分为读锁(共享锁)和写锁(排他锁)

使用场景

  • MyISAM 引擎默认锁机制
  • 修改表结构(如 ALTER TABLE

语法

sql 复制代码
LOCK TABLES table_name READ;   -- 加读锁
LOCK TABLES table_name WRITE;  -- 加写锁
UNLOCK TABLES;                 -- 释放锁

代码示例

sql 复制代码
-- 会话A
LOCK TABLES orders READ; -- 会话A给orders表加了读锁
-- 会话B
INSERT INTO orders VALUES (...); -- 会话B给orders表添加数据被阻塞

行级锁(Row-Level Lock)

定义:锁定单行或多行数据(InnoDB 核心特性)

使用场景:高并发下精确控制数据访问

语法

sql 复制代码
SELECT * FROM users WHERE id=1 FOR UPDATE; -- 排他锁
SELECT * FROM users WHERE id=1 FOR SHARE;  -- 共享锁

注意事项

  • 行锁必须通过索引实现,否则退化为表锁
  • 示例:UPDATE users SET age=20 WHERE name='张三'(若 name 无索引,锁整个表)

页级锁(Page-Level Lock)

定义:锁定数据页(16KB),仅 BDB 引擎支持

现状:已被淘汰,了解即可

按锁的类型分类

共享锁(S Lock)

定义:允许多事务读同一数据,阻止写操作

类比:多人同时阅读同一本书,但不能修改

兼容性:与共享锁兼容,与排他锁冲突

排他锁(X Lock)

定义:独占锁,阻止其他事务读写

类比:你独占了书桌写作业,其他人不能看也不能改

兼容性:与所有锁冲突

意向锁(Intention Lock)

定义:表级锁,快速判断表中是否有行锁

  • IS 锁:事务计划加行级共享锁
  • IX 锁:事务计划加行级排他锁

作用:避免逐行检查锁状态,提升效率

按锁的实现方式分类

悲观锁

定义:假设并发冲突会发生,先加锁再操作

实现SELECT ... FOR UPDATE

适用场景:写操作频繁(如秒杀)

乐观锁

定义:假设无冲突,提交时检查版本号

实现

sql 复制代码
UPDATE products 
SET stock=stock-1, version=version+1 
WHERE id=100 AND version=current_version;

适用场景:读多写少(如文章阅读量统计)

InnoDB 行级锁的四种实现

记录锁(Record Lock)

定义:锁定索引中的一条记录

触发条件:精确匹配唯一索引(如 WHERE id=1)

代码示例

sql 复制代码
-- 锁定 id=1 的行
SELECT * FROM users WHERE id=1 FOR UPDATE;

间隙锁(Gap Lock)

定义 :锁定索引记录之间的间隙(如 id 在 (5,10) 之间)

触发条件:范围查询或查询不存在的值

作用:防止其他事务插入数据(解决幻读)

仅生效于REPEATABLE READ 隔离级别

临键锁(Next-Key Lock)

定义:记录锁 + 间隙锁,锁定左开右闭区间

触发条件:非唯一索引的范围查询

代码示例

sql 复制代码
-- 锁定 age 在 (20,25] 区间
SELECT * FROM users WHERE age > 20 AND age <=25 FOR UPDATE;

插入意向锁(Insert Intention Lock)

定义:插入前标记目标位置的间隙锁

特点:不阻塞其他插入意向锁,但阻塞间隙锁

事务隔离级别与锁的关系

隔离级别 脏读 不可重复读 幻读 锁机制特点
Read Uncommitted 可能 可能 可能 不加锁,直接读最新数据(可能脏读)
Read Committed 避免 可能 可能 行锁,无间隙锁
Repeatable Read 避免 避免 可能 默认使用临键锁,防止幻读
Serializable 避免 避免 避免 所有 SELECT 自动转为 FOR SHARE

建议 :默认使用 REPEATABLE READ,高并发写入场景可尝试 READ COMMITTED

锁的监控与死锁处理

查看锁状态

代码示例

sql 复制代码
-- 当前持有的锁
SELECT * FROM information_schema.INNODB_LOCKS;

-- 锁等待关系
SELECT * FROM information_schema.INNODB_LOCK_WAITS;

-- 事务状态(包含锁信息)
SELECT * FROM information_schema.INNODB_TRX;

死锁自动处理

InnoDB 自动检测死锁,回滚代价较小的事务

手动处理

sql 复制代码
SHOW ENGINE INNODB STATUS;  -- 查看死锁日志
KILL [transaction_id];      -- 终止事务

避免死锁的黄金法则

按固定顺序访问资源(如按主键排序更新)

尽量使用短事务,快速提交

为高频查询字段添加索引

常见问题

为什么我的 SQL 执行卡住?

可能原因:锁等待超时

解决步骤

  1. 查询阻塞事务:

    sql 复制代码
    SELECT * FROM information_schema.INNODB_TRX;
  2. 终止阻塞者:

    sql 复制代码
    KILL 12345; -- 替换为实际事务ID

操作案例

索引缺失导致锁升级

  1. 创建无索引表

    sql 复制代码
    CREATE TABLE test (name VARCHAR(20));
    INSERT INTO test VALUES ('Alice'), ('Bob');
  2. 会话A

    sql 复制代码
    BEGIN;
    UPDATE test SET name='Charlie' WHERE name='Alice'; -- 无索引,触发表锁
  3. 会话B

    sql 复制代码
    UPDATE test SET name='Dave' WHERE name='Bob'; -- 被阻塞!

结论:无索引的更新操作会锁住整张表

总结

  1. 索引优化

    • 所有 WHERE 条件字段尽量加索引
    • 避免全表扫描(如 LIKE '%xxx%'
  2. 事务设计

    • 事务尽量简短,避免在事务中执行网络请求
    • 批量操作分批次提交
  3. 锁超时设置

    sql 复制代码
    SET innodb_lock_wait_timeout = 5; -- 等待5秒自动放弃
  4. 锁替代方案

    • 乐观锁:通过版本号控制
    • 队列机制:异步处理高并发请求
相关推荐
love729234ming12 分钟前
MySQL Explain 分析 SQL 执行计划
数据库·sql·mysql
wxhxmj22 分钟前
数据库----单表、多表
数据库·oracle
echola_mendes39 分钟前
LangChain 结构化输出:用 Pydantic + PydanticOutputParser 驯服 LLM 的“自由发挥”
服务器·前端·数据库·ai·langchain
nlog3n1 小时前
MySQL 常见面试问题总结
java·数据库·mysql·面试
字节跳动开源1 小时前
MySQL遇到AI:字节跳动开源 MySQL 虚拟索引 VIDEX
人工智能·mysql·开源·虚拟索引技术·解耦架构
碧海饮冰1 小时前
MongoDB 与 Elasticsearch 使用场景区别及示例
数据库·mongodb·elasticsearch
搬码红绿灯1 小时前
数据库——MySQL字符、日期函数
数据库·mysql
Tyler先森1 小时前
Oracle数据库数据编程SQL<2.2 DDL 视图、序列>
数据库·sql·oracle
xcLeigh2 小时前
安装教程:windows上安装oracle详细教程
数据库·ide·oracle·可视化工具
TPCloud3 小时前
如何快速解决django存储session变量时出现的django.db.utils.DatabaseError错误
数据库·python·django