Skip to content

MariaDB 事务与锁

事务的基本概念

事务是数据库中一组不可分割的操作序列,要么全部执行成功,要么全部执行失败。事务用于保证数据的完整性和一致性,是数据库并发控制的基础。

事务的 ACID 特性

  • 原子性(Atomicity):事务中的所有操作要么全部成功,要么全部失败回滚
  • 一致性(Consistency):事务执行前后,数据库从一个一致性状态转换到另一个一致性状态
  • 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务
  • 持久性(Durability):事务一旦提交,其结果应该永久保存在数据库中

事务的生命周期

  1. 开始事务:使用 START TRANSACTIONBEGIN 命令
  2. 执行操作:执行一系列 SQL 语句
  3. 提交事务:使用 COMMIT 命令,将事务的修改永久保存到数据库
  4. 回滚事务:使用 ROLLBACK 命令,撤销事务的所有修改

示例

sql
-- 开始事务
START TRANSACTION;

-- 执行操作
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

-- 提交事务
COMMIT;

-- 或者回滚事务
-- ROLLBACK;

事务隔离级别

事务隔离级别定义了多个并发事务之间的隔离程度,不同的隔离级别提供不同的一致性保证和并发性能。

隔离级别的类型

MariaDB 支持以下四种隔离级别:

隔离级别脏读不可重复读幻读并发性能
读未提交(READ UNCOMMITTED)可能可能可能最高
读已提交(READ COMMITTED)不可能可能可能较高
可重复读(REPEATABLE READ)不可能不可能可能中等
串行化(SERIALIZABLE)不可能不可能不可能最低

隔离级别的设置

查看当前隔离级别

sql
-- 全局隔离级别
SELECT @@global.tx_isolation;

-- 会话隔离级别
SELECT @@session.tx_isolation;

设置隔离级别

sql
-- 设置全局隔离级别
SET GLOBAL tx_isolation = 'REPEATABLE-READ';

-- 设置会话隔离级别
SET SESSION tx_isolation = 'READ-COMMITTED';

在 my.cnf 中配置默认隔离级别

ini
[mysqld]
transaction-isolation = REPEATABLE-READ

各隔离级别的具体表现

1. 读未提交(READ UNCOMMITTED)

  • 允许一个事务读取另一个未提交事务的数据
  • 可能导致脏读、不可重复读和幻读
  • 适用于对数据一致性要求不高的场景

2. 读已提交(READ COMMITTED)

  • 只能读取已提交事务的数据
  • 避免了脏读,但可能导致不可重复读和幻读
  • 适用于大多数 Web 应用场景

3. 可重复读(REPEATABLE READ)

  • 保证同一事务中多次读取同一数据的结果一致
  • 避免了脏读和不可重复读,但可能导致幻读
  • MariaDB 默认隔离级别
  • 通过 MVCC(多版本并发控制)实现

4. 串行化(SERIALIZABLE)

  • 完全隔离并发事务,相当于单线程执行
  • 避免了脏读、不可重复读和幻读
  • 并发性能最低,适用于对数据一致性要求极高的场景

锁的基本概念

锁是数据库用于控制并发访问的机制,通过锁可以防止多个事务同时修改同一资源,从而保证数据的一致性。

锁的作用

  • 保证数据一致性:防止多个事务同时修改同一数据导致数据不一致
  • 实现事务隔离:通过锁机制实现不同的事务隔离级别
  • 协调并发访问:合理分配资源,提高系统并发性能

锁的类型

1. 按粒度划分

表级锁
  • 对整个表加锁
  • 开销小,加锁快
  • 锁定粒度大,并发度低
  • 适用于 MyISAM 存储引擎

表级锁类型

  • 表共享读锁(Table Read Lock):允许其他事务读,不允许写
  • 表独占写锁(Table Write Lock):不允许其他事务读和写

示例

sql
-- 加表共享读锁
LOCK TABLES users READ;

-- 加表独占写锁
LOCK TABLES users WRITE;

-- 释放锁
UNLOCK TABLES;
页级锁
  • 对数据页加锁(通常为 16KB)
  • 开销和加锁速度介于表级锁和行级锁之间
  • 锁定粒度中等,并发度中等
  • 适用于 BDB 存储引擎
行级锁
  • 对单行数据加锁
  • 开销大,加锁慢
  • 锁定粒度小,并发度高
  • 适用于 InnoDB 存储引擎

行级锁类型

  • 共享锁(S Lock):允许读,不允许写
  • 排他锁(X Lock):不允许读和写

示例

sql
-- 加共享锁(读锁)
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;

-- 加排他锁(写锁)
SELECT * FROM users WHERE id = 1 FOR UPDATE;

2. 按功能划分

意向锁
  • 表级锁,用于表示事务将要对表中的行加锁
  • 意向共享锁(IS Lock):事务准备对表中的行加共享锁
  • 意向排他锁(IX Lock):事务准备对表中的行加排他锁
间隙锁
  • 锁定索引记录之间的间隙,防止幻读
  • 只在可重复读(REPEATABLE READ)和串行化(SERIALIZABLE)隔离级别下生效
  • 锁定范围包括:
    • 索引记录之间的间隙
    • 索引记录之前的间隙
    • 索引记录之后的间隙

示例

sql
-- 锁定 id 在 100 到 200 之间的间隙
SELECT * FROM users WHERE id BETWEEN 100 AND 200 FOR UPDATE;
临键锁
  • 间隙锁和行锁的组合
  • 锁定索引记录及其之前的间隙
  • 是 InnoDB 行级锁的默认算法
记录锁
  • 直接锁定索引记录
  • 只锁定索引记录本身,不包括间隙

锁的状态查看

查看表锁状态

sql
SHOW OPEN TABLES WHERE In_use > 0;

查看行锁状态

sql
-- 查看 InnoDB 锁状态
SHOW ENGINE INNODB STATUS;

-- 查看锁等待情况
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
SELECT * FROM information_schema.INNODB_TRX;

锁的等待和死锁

锁等待

当一个事务请求的锁被另一个事务占用时,该事务会进入等待状态,直到锁被释放。锁等待可能导致事务执行时间延长,影响系统性能。

查看锁等待超时时间

sql
SELECT @@innodb_lock_wait_timeout;

设置锁等待超时时间

sql
-- 全局设置
SET GLOBAL innodb_lock_wait_timeout = 60;

-- 会话设置
SET SESSION innodb_lock_wait_timeout = 60;

死锁

死锁是指两个或多个事务相互等待对方释放锁,导致所有事务都无法继续执行的状态。

死锁示例

sql
-- 事务 A
START TRANSACTION;
UPDATE users SET name = 'A' WHERE id = 1;
-- 等待事务 B 释放 id=2 的锁
UPDATE users SET name = 'A' WHERE id = 2;

-- 事务 B
START TRANSACTION;
UPDATE users SET name = 'B' WHERE id = 2;
-- 等待事务 A 释放 id=1 的锁
UPDATE users SET name = 'B' WHERE id = 1;

死锁的检测和处理

死锁检测

MariaDB 默认开启死锁检测,通过 innodb_deadlock_detect 参数控制:

sql
SELECT @@innodb_deadlock_detect;

死锁处理

  1. 自动检测和回滚:InnoDB 会自动检测死锁,并回滚代价较小的事务
  2. 手动处理:当死锁检测关闭时,需要手动终止事务

查看死锁日志

sql
SHOW ENGINE INNODB STATUS;

手动终止事务

sql
-- 查看当前事务
SELECT * FROM information_schema.INNODB_TRX;

-- 终止指定事务
KILL TRANSACTION_ID;

事务和锁的最佳实践

1. 合理设计事务

  • 保持事务短小:事务执行时间越短,锁持有时间越短,并发性能越高
  • 避免在事务中执行非数据库操作:如调用外部 API、等待用户输入等
  • 避免在事务中执行大量查询:减少事务持有时间

2. 选择合适的隔离级别

  • 根据业务需求选择合适的隔离级别
  • 大多数场景下,使用默认的 REPEATABLE READ 隔离级别即可
  • 对于读多写少的场景,可以考虑使用 READ COMMITTED 隔离级别提高并发性能

3. 优化锁的使用

  • 避免锁定不必要的数据:只锁定必要的行和列
  • 使用索引减少锁定范围:确保查询使用索引,避免全表扫描导致的表级锁
  • 避免长时间持有锁:尽快提交或回滚事务
  • 使用乐观锁:对于并发冲突较少的场景,可以使用版本号或时间戳实现乐观锁

4. 避免死锁

  • 统一访问顺序:在多个事务中,按照相同的顺序访问表和行
  • 减少事务并发度:通过合理设计业务逻辑,减少事务并发执行
  • 使用更细粒度的锁:尽量使用行级锁,避免表级锁
  • 设置合理的锁等待超时时间:避免事务无限期等待

5. 监控和调优

  • 定期监控锁等待情况:及时发现和解决锁等待问题
  • 分析死锁日志:找出死锁原因,优化业务逻辑
  • 调整事务隔离级别:根据实际情况调整隔离级别
  • 优化查询语句:减少锁持有时间

版本差异

MariaDB 10.0+

  • 引入了乐观锁机制
  • 增强了 MVCC 实现

MariaDB 10.1+

  • 引入了 Galera Cluster,支持分布式事务
  • 增强了死锁检测机制

MariaDB 10.2+

  • 支持事务读写分离
  • 增强了 InnoDB 锁机制

MariaDB 10.3+

  • 引入了动态列,支持事务操作动态列
  • 增强了事务监控功能

MariaDB 10.4+

  • 默认事务隔离级别仍然是 REPEATABLE READ
  • 增强了 InnoDB 并行复制,提高了事务处理性能

常见问题(FAQ)

1. 如何选择合适的事务隔离级别?

建议

  • 对于大多数 Web 应用,使用 READ COMMITTED 或 REPEATABLE READ 隔离级别
  • 对于对数据一致性要求极高的场景,使用 SERIALIZABLE 隔离级别
  • 对于对并发性能要求极高且数据一致性要求不高的场景,使用 READ UNCOMMITTED 隔离级别

2. 如何避免死锁?

建议

  • 统一事务中表的访问顺序
  • 保持事务短小,尽快提交或回滚
  • 避免在事务中执行复杂的查询和非数据库操作
  • 使用索引减少锁定范围
  • 设置合理的锁等待超时时间

3. 如何处理锁等待问题?

建议

  • 优化查询语句,减少锁持有时间
  • 增加系统资源,提高并发处理能力
  • 调整事务隔离级别
  • 分析锁等待原因,优化业务逻辑

4. 什么情况下会导致表级锁?

可能原因

  • 使用 MyISAM 存储引擎
  • 查询没有使用索引,导致全表扫描
  • 执行 ALTER TABLE、DROP TABLE 等 DDL 操作
  • 手动使用 LOCK TABLES 命令加表锁

5. 如何查看当前正在执行的事务?

方法

sql
SELECT * FROM information_schema.INNODB_TRX;

6. 如何查看死锁日志?

方法

sql
SHOW ENGINE INNODB STATUS;

7. 如何优化事务性能?

建议

  • 保持事务短小
  • 避免在事务中执行非数据库操作
  • 使用合适的隔离级别
  • 优化查询语句,减少锁持有时间
  • 增加系统资源,提高并发处理能力

8. 什么是 MVCC?

MVCC(多版本并发控制)是 InnoDB 存储引擎实现事务隔离的机制,通过保存数据的多个版本,实现并发读写操作的隔离。MVCC 可以提高系统的并发性能,避免读写操作之间的阻塞。

9. 如何手动终止一个长时间运行的事务?

方法

sql
-- 查看当前事务
SELECT * FROM information_schema.INNODB_TRX;

-- 终止指定事务
KILL TRANSACTION_ID;

10. 如何监控事务和锁的使用情况?

建议

  • 使用 MariaDB 自带的状态监控工具:SHOW ENGINE INNODB STATUS
  • 查询 information_schema 中的事务和锁相关表
  • 使用第三方监控工具,如 Prometheus、Grafana 等
  • 定期分析慢查询日志和错误日志

总结

事务和锁是 MariaDB 数据库中的核心概念,对于保证数据一致性和提高并发性能至关重要。DBA 需要深入理解事务的 ACID 特性、隔离级别、锁的类型和机制,以及如何处理锁等待和死锁问题。

在实际生产环境中,DBA 应该根据业务需求选择合适的事务隔离级别,优化事务设计,合理使用锁机制,避免死锁和锁等待问题,并定期监控和调优事务和锁的使用情况。

通过本文的介绍,相信您对 MariaDB 事务和锁有了更深入的了解,能够在实际生产环境中更好地管理和优化事务和锁,提高数据库的性能和可靠性。