一、锁概述

锁是保证数据一致性和并发控制的重要机制。

锁的作用:

  • 保证数据完整性
  • 实现事务隔离
  • 控制并发访问

二、锁的类型

全局锁

锁定整个数据库实例。

使用场景:

  • 全库逻辑备份
  • 数据迁移
1
2
3
4
5
-- 加全局读锁
FLUSH TABLES WITH READ LOCK;

-- 释放锁
UNLOCK TABLES;

特点:

  • 整个库只读
  • 阻塞所有写操作

表级锁

锁定整张表。

表锁

1
2
3
4
5
6
7
8
-- 加读锁
LOCK TABLES table_name READ;

-- 加写锁
LOCK TABLES table_name WRITE;

-- 释放锁
UNLOCK TABLES;

读锁:可以读,不能写
写锁:独占表,其他会话不能读写

元数据锁

DDL操作自动加MDL锁。

  • MDL读锁:DML操作
  • MDL写锁:DDL操作

意向锁

InnoDB支持,表示事务意图在表中的行上加锁。

  • 意向共享锁(IS)
  • 意向排他锁(IX)

作用:表锁和行锁兼容性判断。

行级锁

InnoDB支持的细粒度锁。

Record Lock

锁定单条记录。

1
SELECT * FROM user WHERE id = 1 FOR UPDATE;

Gap Lock

锁定记录之间的间隙,防止幻读。

1
2
3
-- 假设id为1,5,10
SELECT * FROM user WHERE id > 1 AND id < 5 FOR UPDATE;
-- 锁定(1,5)间隙

Next-Key Lock

Record Lock + Gap Lock,锁定记录和前面的间隙。

1
2
SELECT * FROM user WHERE id >= 5 FOR UPDATE;
-- 锁定(1,5]和(5,10)

三、共享锁和排他锁

共享锁(S锁)

读锁,多个事务可以同时持有。

1
2
3
4
-- 加共享锁
SELECT * FROM table WHERE condition LOCK IN SHARE MODE;
-- 或 MySQL 8.0
SELECT * FROM table WHERE condition FOR SHARE;

排他锁(X锁)

写锁,独占资源。

1
2
3
4
5
6
7
-- 加排他锁
SELECT * FROM table WHERE condition FOR UPDATE;

-- DML操作自动加排他锁
UPDATE table SET column = value WHERE condition;
DELETE FROM table WHERE condition;
INSERT INTO table VALUES (...);

兼容性

S锁 X锁
S锁 兼容 冲突
X锁 冲突 冲突

四、行锁的实现

索引条件

行锁必须通过索引实现。

1
2
3
4
5
-- 命中索引,行锁
UPDATE user SET name = '张三' WHERE id = 1;

-- 未命中索引,退化为表锁
UPDATE user SET name = '张三' WHERE age = 20; -- age无索引

锁的加锁范围

唯一索引等值查询:

  • 存在记录:加Record Lock
  • 不存在记录:加Gap Lock

唯一索引范围查询:

  • 加Next-Key Lock

非唯一索引等值查询:

  • 加Next-Key Lock
  • 加Gap Lock

锁查看

1
2
3
4
5
6
-- 查看当前锁
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;

-- MySQL 8.0
SELECT * FROM performance_schema.data_locks;

五、锁算法

当前读

读取最新数据,加锁。

1
2
3
SELECT * FROM table WHERE condition FOR UPDATE;
SELECT * FROM table WHERE condition LOCK IN SHARE MODE;
UPDATEDELETEINSERT

快照读

读取历史版本,不加锁,通过MVCC实现。

1
SELECT * FROM table WHERE condition;

在READ COMMITTED和REPEATABLE READ下,快照读实现不同。

六、死锁

死锁产生

两个事务相互等待对方释放锁。

1
2
3
4
5
6
7
-- 事务1
UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;

-- 事务2
UPDATE account SET balance = balance - 100 WHERE id = 2;
UPDATE account SET balance = balance + 100 WHERE id = 1;

死锁检测

1
2
3
4
5
-- 查看死锁检测配置
SHOW VARIABLES LIKE 'innodb_deadlock_detect';

-- 查看死锁日志
SHOW ENGINE INNODB STATUS;

死锁避免

  • 按相同顺序访问资源
  • 减少长事务
  • 合理设计索引
  • 降低隔离级别

七、锁优化

减少锁持有时间

  • 事务尽量简短
  • 避免在事务中做耗时操作

减少锁范围

  • 使用索引,避免表锁
  • 避免全表扫描

合理使用锁

  • 只读查询不加锁
  • 必要时使用FOR UPDATE

监控锁状态

1
2
3
4
5
-- 锁等待超时
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';

-- 查看锁等待
SELECT * FROM sys.innodb_lock_waits;

八、总结

MySQL锁要点:

  • 全局锁、表锁、行锁
  • 共享锁和排他锁
  • Record Lock、Gap Lock、Next-Key Lock
  • 行锁依赖索引实现
  • 死锁检测和避免

合理使用锁机制可以保证数据一致性和并发性能。