5.1 事务基础概念
事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。事务必须满足ACID特性,确保数据库的一致性和可靠性。
5.1.1 ACID特性详解
class TransactionACID:
def __init__(self):
self.acid_properties = {
"Atomicity": {
"中文名": "原子性",
"定义": "事务是一个不可分割的工作单位,要么全部完成,要么全部不做",
"实现机制": "undo log(回滚日志)",
"示例": "银行转账:扣款和入账必须同时成功或同时失败"
},
"Consistency": {
"中文名": "一致性",
"定义": "事务执行前后,数据库从一个一致性状态转换到另一个一致性状态",
"实现机制": "约束检查、触发器、应用程序逻辑",
"示例": "转账后总金额保持不变,满足业务规则"
},
"Isolation": {
"中文名": "隔离性",
"定义": "并发执行的事务之间不能相互干扰",
"实现机制": "锁机制、MVCC(多版本并发控制)",
"示例": "两个用户同时查询账户余额,不会看到对方未提交的修改"
},
"Durability": {
"中文名": "持久性",
"定义": "事务一旦提交,其结果就是永久性的,即使系统崩溃也不会丢失",
"实现机制": "redo log(重做日志)、binlog(二进制日志)",
"示例": "提交的转账记录在系统重启后仍然存在"
}
}
self.transaction_states = {
"Active": "事务正在执行中",
"Partially Committed": "事务执行完毕,但还未提交",
"Committed": "事务已成功提交",
"Failed": "事务执行失败",
"Aborted": "事务已回滚,数据库恢复到事务开始前的状态"
}
def explain_acid(self):
"""解释ACID特性"""
print("ACID特性详解:")
print("=" * 60)
for property_name, details in self.acid_properties.items():
print(f"\n{property_name} ({details['中文名']}):")
print(f" 定义: {details['定义']}")
print(f" 实现机制: {details['实现机制']}")
print(f" 示例: {details['示例']}")
def show_transaction_states(self):
"""显示事务状态"""
print("\n事务状态转换:")
print("=" * 30)
for state, description in self.transaction_states.items():
print(f"{state}: {description}")
state_diagram = """
事务状态转换图:
[开始] → [Active] → [Partially Committed] → [Committed]
↓ ↓
[Failed] → [Aborted]
状态转换说明:
1. 事务开始后进入Active状态
2. 执行完所有操作后进入Partially Committed状态
3. 如果提交成功,进入Committed状态
4. 如果执行过程中出错,进入Failed状态
5. Failed状态的事务会被回滚到Aborted状态
"""
print(state_diagram)
def transaction_examples(self):
"""事务示例"""
examples = """
-- MySQL事务操作示例
-- 1. 基本事务操作
START TRANSACTION; -- 或者使用 BEGIN;
-- 执行一系列操作
UPDATE accounts SET balance = balance - 1000 WHERE account_id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE account_id = 2;
-- 提交事务
COMMIT;
-- 2. 事务回滚示例
START TRANSACTION;
UPDATE accounts SET balance = balance - 1000 WHERE account_id = 1;
-- 检查余额是否足够
SELECT balance FROM accounts WHERE account_id = 1;
-- 如果余额不足,回滚事务
ROLLBACK;
-- 3. 保存点(Savepoint)使用
START TRANSACTION;
UPDATE accounts SET balance = balance - 500 WHERE account_id = 1;
SAVEPOINT sp1;
UPDATE accounts SET balance = balance + 500 WHERE account_id = 2;
SAVEPOINT sp2;
UPDATE accounts SET balance = balance - 200 WHERE account_id = 1;
-- 如果这步操作有问题,可以回滚到sp2
ROLLBACK TO sp2;
-- 最终提交
COMMIT;
-- 4. 自动提交控制
-- 查看当前自动提交状态
SHOW VARIABLES LIKE 'autocommit';
-- 关闭自动提交
SET autocommit = 0;
-- 执行操作(需要手动提交)
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
COMMIT;
-- 重新开启自动提交
SET autocommit = 1;
-- 5. 事务隔离级别设置
-- 查看当前隔离级别
SELECT @@transaction_isolation;
-- 设置会话级别的隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 设置下一个事务的隔离级别
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 6. 只读事务
START TRANSACTION READ ONLY;
SELECT * FROM accounts WHERE account_id = 1;
COMMIT;
"""
print("\n事务操作示例:")
print("=" * 40)
print(examples)
# ACID特性演示
acid_demo = TransactionACID()
acid_demo.explain_acid()
acid_demo.show_transaction_states()
acid_demo.transaction_examples()
5.1.2 事务隔离级别
class TransactionIsolation:
def __init__(self):
self.isolation_levels = {
"READ UNCOMMITTED": {
"中文名": "读未提交",
"描述": "最低的隔离级别,允许读取未提交的数据",
"可能问题": ["脏读", "不可重复读", "幻读"],
"性能": "最高",
"使用场景": "对数据一致性要求极低的场景"
},
"READ COMMITTED": {
"中文名": "读已提交",
"描述": "只能读取已提交的数据",
"可能问题": ["不可重复读", "幻读"],
"性能": "较高",
"使用场景": "大多数数据库的默认级别"
},
"REPEATABLE READ": {
"中文名": "可重复读",
"描述": "在同一事务中多次读取同一数据结果一致",
"可能问题": ["幻读(MySQL InnoDB已解决)"],
"性能": "中等",
"使用场景": "MySQL InnoDB的默认级别"
},
"SERIALIZABLE": {
"中文名": "串行化",
"描述": "最高的隔离级别,事务串行执行",
"可能问题": [],
"性能": "最低",
"使用场景": "对数据一致性要求极高的场景"
}
}
self.concurrency_problems = {
"脏读": {
"定义": "读取到其他事务未提交的数据",
"示例": "事务A修改数据但未提交,事务B读取到了修改后的数据",
"解决方案": "使用READ COMMITTED或更高隔离级别"
},
"不可重复读": {
"定义": "同一事务中多次读取同一数据,结果不一致",
"示例": "事务A两次读取同一行,期间事务B修改并提交了该行",
"解决方案": "使用REPEATABLE READ或更高隔离级别"
},
"幻读": {
"定义": "同一事务中多次查询,返回的行数不一致",
"示例": "事务A两次查询满足条件的行,期间事务B插入了新行",
"解决方案": "使用SERIALIZABLE隔离级别或间隙锁"
}
}
def explain_isolation_levels(self):
"""解释隔离级别"""
print("事务隔离级别详解:")
print("=" * 60)
for level, details in self.isolation_levels.items():
print(f"\n{level} ({details['中文名']}):")
print(f" 描述: {details['描述']}")
print(f" 可能问题: {', '.join(details['可能问题']) if details['可能问题'] else '无'}")
print(f" 性能: {details['性能']}")
print(f" 使用场景: {details['使用场景']}")
def explain_concurrency_problems(self):
"""解释并发问题"""
print("\n并发访问问题:")
print("=" * 40)
for problem, details in self.concurrency_problems.items():
print(f"\n{problem}:")
print(f" 定义: {details['定义']}")
print(f" 示例: {details['示例']}")
print(f" 解决方案: {details['解决方案']}")
def isolation_level_examples(self):
"""隔离级别示例"""
examples = """
-- 隔离级别演示示例
-- 1. 脏读演示(READ uncommitted)
-- 会话1
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
SELECT balance FROM accounts WHERE account_id = 1; -- 假设结果是1000
-- 会话2(在会话1事务未结束时)
START TRANSACTION;
UPDATE accounts SET balance = 2000 WHERE account_id = 1;
-- 注意:不要提交
-- 回到会话1
SELECT balance FROM accounts WHERE account_id = 1; -- 结果是2000(脏读)
COMMIT;
-- 会话2回滚
ROLLBACK;
-- 2. 不可重复读演示(read committed)
-- 会话1
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT balance FROM accounts WHERE account_id = 1; -- 第一次读取
-- 会话2
START TRANSACTION;
UPDATE accounts SET balance = balance + 500 WHERE account_id = 1;
COMMIT;
-- 回到会话1
SELECT balance FROM accounts WHERE account_id = 1; -- 第二次读取,结果不同
COMMIT;
-- 3. 幻读演示(repeatable read)
-- 会话1
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT COUNT(*) FROM accounts WHERE balance > 1000; -- 第一次查询
-- 会话2
START TRANSACTION;
INSERT INTO accounts (account_id, balance) VALUES (999, 1500);
COMMIT;
-- 回到会话1
SELECT COUNT(*) FROM accounts WHERE balance > 1000; -- 第二次查询
-- 在MySQL InnoDB中,由于间隙锁的存在,不会出现幻读
COMMIT;
-- 4. 串行化演示(serializable)
-- 会话1
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
SELECT * FROM accounts WHERE account_id = 1;
-- 会话2(会被阻塞)
START TRANSACTION;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
-- 这个操作会等待会话1提交
-- 会话1提交后,会话2才能继续
COMMIT; -- 在会话1中执行
-- 5. 隔离级别对比测试
-- 创建测试表
CREATE TABLE isolation_test (
id INT PRIMARY KEY,
value VARCHAR(50)
);
INSERT INTO isolation_test VALUES (1, 'initial');
-- 测试不同隔离级别的行为
-- 可以开启多个会话,设置不同的隔离级别进行对比测试
"""
print("\n隔离级别示例:")
print("=" * 40)
print(examples)
def isolation_level_selection_guide(self):
"""隔离级别选择指南"""
guide = """
隔离级别选择指南:
1. READ UNCOMMITTED:
- 适用场景:数据分析、报表生成等对一致性要求不高的场景
- 优点:性能最好,无锁等待
- 缺点:可能读到脏数据
- 建议:一般不推荐使用
2. READ COMMITTED:
- 适用场景:大多数OLTP应用
- 优点:避免脏读,性能较好
- 缺点:可能出现不可重复读
- 建议:Oracle、SQL Server的默认级别
3. REPEATABLE READ:
- 适用场景:需要保证读一致性的应用
- 优点:避免脏读和不可重复读
- 缺点:可能出现幻读(MySQL InnoDB已解决)
- 建议:MySQL InnoDB的默认级别,推荐使用
4. SERIALIZABLE:
- 适用场景:对数据一致性要求极高的场景
- 优点:完全避免并发问题
- 缺点:性能最差,容易产生锁等待
- 建议:谨慎使用,仅在必要时使用
选择建议:
- 一般应用:使用REPEATABLE READ
- 高并发应用:考虑READ COMMITTED
- 数据分析:可以使用READ UNCOMMITTED
- 金融系统:根据具体需求选择REPEATABLE READ或SERIALIZABLE
"""
print("\n隔离级别选择指南:")
print("=" * 40)
print(guide)
# 事务隔离演示
isolation_demo = TransactionIsolation()
isolation_demo.explain_isolation_levels()
isolation_demo.explain_concurrency_problems()
isolation_demo.isolation_level_examples()
isolation_demo.isolation_level_selection_guide()
5.2 锁机制详解
5.2.1 锁的分类与特点
class LockMechanism:
def __init__(self):
self.lock_classifications = {
"按锁的粒度分类": {
"表级锁": {
"描述": "锁定整个表",
"优点": "开销小,加锁快,无死锁",
"缺点": "并发度低",
"存储引擎": "MyISAM、Memory"
},
"行级锁": {
"描述": "锁定具体的行",
"优点": "并发度高,锁冲突概率低",
"缺点": "开销大,加锁慢,可能死锁",
"存储引擎": "InnoDB"
},
"页级锁": {
"描述": "锁定数据页",
"优点": "介于表锁和行锁之间",
"缺点": "可能锁定不需要的行",
"存储引擎": "BDB"
}
},
"按锁的类型分类": {
"共享锁(S锁)": {
"描述": "读锁,多个事务可以同时持有",
"兼容性": "与共享锁兼容,与排他锁不兼容",
"SQL语句": "SELECT ... LOCK IN SHARE MODE"
},
"排他锁(X锁)": {
"描述": "写锁,只有一个事务可以持有",
"兼容性": "与任何锁都不兼容",
"SQL语句": "SELECT ... FOR UPDATE, UPDATE, DELETE, INSERT"
},
"意向共享锁(IS锁)": {
"描述": "表示事务准备在某些行上加共享锁",
"作用": "提高表级锁的效率",
"自动加锁": "InnoDB自动管理"
},
"意向排他锁(IX锁)": {
"描述": "表示事务准备在某些行上加排他锁",
"作用": "提高表级锁的效率",
"自动加锁": "InnoDB自动管理"
}
},
"按锁的算法分类": {
"记录锁(Record Lock)": {
"描述": "锁定索引记录",
"作用范围": "具体的索引记录",
"使用场景": "精确匹配的查询"
},
"间隙锁(Gap Lock)": {
"描述": "锁定索引记录之间的间隙",
"作用范围": "索引记录之间的空隙",
"使用场景": "防止幻读"
},
"临键锁(Next-Key Lock)": {
"描述": "记录锁+间隙锁的组合",
"作用范围": "索引记录及其前面的间隙",
"使用场景": "范围查询,防止幻读"
}
}
}
def explain_lock_classifications(self):
"""解释锁的分类"""
print("MySQL锁机制分类:")
print("=" * 60)
for category, locks in self.lock_classifications.items():
print(f"\n{category}:")
for lock_name, details in locks.items():
print(f" {lock_name}:")
for key, value in details.items():
print(f" {key}: {value}")
def lock_compatibility_matrix(self):
"""锁兼容性矩阵"""
compatibility_info = """
锁兼容性矩阵:
| IS | IX | S | X |
--------|------|------|------|------|
IS | ✓ | ✓ | ✓ | ✗ |
IX | ✓ | ✓ | ✗ | ✗ |
S | ✓ | ✗ | ✓ | ✗ |
X | ✗ | ✗ | ✗ | ✗ |
说明:
- ✓ 表示兼容,可以同时持有
- ✗ 表示不兼容,需要等待
- IS:意向共享锁
- IX:意向排他锁
- S:共享锁
- X:排他锁
行级锁兼容性:
| S | X |
--------|------|------|
S | ✓ | ✗ |
X | ✗ | ✗ |
锁升级规则:
1. 意向锁是表级锁,行级锁是具体的数据锁
2. 加行级共享锁前,必须先获得表的IS锁或更强的锁
3. 加行级排他锁前,必须先获得表的IX锁或更强的锁
4. 意向锁之间相互兼容,不会阻塞
"""
print("\n锁兼容性矩阵:")
print("=" * 40)
print(compatibility_info)
def lock_examples(self):
"""锁使用示例"""
examples = """
-- MySQL锁使用示例
-- 1. 共享锁示例
-- 会话1:加共享锁
START TRANSACTION;
SELECT * FROM accounts WHERE account_id = 1 LOCK IN SHARE MODE;
-- 此时其他会话可以读取,但不能修改
-- 会话2:尝试修改(会被阻塞)
START TRANSACTION;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
-- 这个操作会等待会话1释放锁
-- 会话1提交释放锁
COMMIT;
-- 2. 排他锁示例
-- 会话1:加排他锁
START TRANSACTION;
SELECT * FROM accounts WHERE account_id = 1 FOR UPDATE;
-- 此时其他会话既不能读取也不能修改
-- 会话2:尝试读取(会被阻塞)
START TRANSACTION;
SELECT * FROM accounts WHERE account_id = 1 LOCK IN SHARE MODE;
-- 这个操作会等待
-- 会话1提交释放锁
COMMIT;
-- 3. 表级锁示例
-- 读锁
LOCK TABLES accounts READ;
SELECT * FROM accounts; -- 可以读取
-- UPDATE accounts SET balance = 1000; -- 不能修改,会报错
UNLOCK TABLES;
-- 写锁
LOCK TABLES accounts WRITE;
SELECT * FROM accounts; -- 可以读取
UPDATE accounts SET balance = balance + 100; -- 可以修改
UNLOCK TABLES;
-- 4. 死锁示例
-- 会话1
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- 等待一段时间,然后执行下面的语句
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
-- 会话2(在会话1执行第二个UPDATE前执行)
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 2;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
-- 这里会产生死锁,MySQL会自动检测并回滚其中一个事务
-- 5. 间隙锁示例
-- 假设accounts表中有id为1, 5, 10的记录
START TRANSACTION;
-- 这会在(1,5)和(5,10)之间加间隙锁
SELECT * FROM accounts WHERE account_id BETWEEN 2 AND 8 FOR UPDATE;
-- 其他会话尝试插入会被阻塞
-- INSERT INTO accounts (account_id, balance) VALUES (3, 1000); -- 被阻塞
-- INSERT INTO accounts (account_id, balance) VALUES (7, 1000); -- 被阻塞
COMMIT;
-- 6. 查看锁信息
-- 查看当前锁等待情况
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
SELECT * FROM information_schema.INNODB_TRX;
-- MySQL 8.0中的新表
SELECT * FROM performance_schema.data_locks;
SELECT * FROM performance_schema.data_lock_waits;
"""
print("\n锁使用示例:")
print("=" * 40)
print(examples)
def lock_optimization_tips(self):
"""锁优化技巧"""
tips = """
锁优化技巧:
1. 减少锁持有时间:
- 尽量缩短事务时间
- 避免在事务中进行耗时操作
- 及时提交或回滚事务
2. 降低锁粒度:
- 使用行级锁而不是表级锁
- 只锁定必要的行
- 避免全表扫描
3. 统一加锁顺序:
- 多个事务按相同顺序访问资源
- 避免循环等待导致的死锁
- 使用固定的表访问顺序
4. 使用合适的隔离级别:
- 根据业务需求选择最低的隔离级别
- 避免不必要的锁等待
- 考虑使用READ COMMITTED
5. 索引优化:
- 确保WHERE条件使用索引
- 避免锁定不必要的行
- 使用覆盖索引减少回表
6. 批量操作优化:
- 将大事务拆分为小事务
- 使用批量操作减少锁的次数
- 考虑使用乐观锁
7. 监控和诊断:
- 定期检查锁等待情况
- 分析死锁日志
- 监控事务执行时间
8. 应用层优化:
- 使用连接池减少连接开销
- 实现重试机制处理死锁
- 考虑使用分布式锁
"""
print("\n锁优化技巧:")
print("=" * 40)
print(tips)
# 锁机制演示
lock_demo = LockMechanism()
lock_demo.explain_lock_classifications()
lock_demo.lock_compatibility_matrix()
lock_demo.lock_examples()
lock_demo.lock_optimization_tips()
5.2.2 死锁检测与处理
class DeadlockManagement:
def __init__(self):
self.deadlock_types = {
"简单死锁": {
"描述": "两个事务相互等待对方释放锁",
"示例": "事务A锁定资源1等待资源2,事务B锁定资源2等待资源1",
"检测难度": "容易"
},
"复杂死锁": {
"描述": "多个事务形成循环等待",
"示例": "事务A→事务B→事务C→事务A形成环路",
"检测难度": "困难"
},
"间隙锁死锁": {
"描述": "由间隙锁引起的死锁",
"示例": "两个事务在同一间隙插入不同的记录",
"检测难度": "中等"
}
}
self.deadlock_detection_methods = {
"等待图法": {
"原理": "构建事务等待图,检测是否存在环路",
"优点": "准确性高",
"缺点": "开销较大"
},
"超时法": {
"原理": "设置锁等待超时时间,超时则认为发生死锁",
"优点": "实现简单",
"缺点": "可能误判"
},
"银行家算法": {
"原理": "预先检测资源分配是否会导致死锁",
"优点": "预防性强",
"缺点": "实现复杂,开销大"
}
}
def explain_deadlock_types(self):
"""解释死锁类型"""
print("死锁类型分析:")
print("=" * 50)
for deadlock_type, details in self.deadlock_types.items():
print(f"\n{deadlock_type}:")
for key, value in details.items():
print(f" {key}: {value}")
def explain_detection_methods(self):
"""解释死锁检测方法"""
print("\n死锁检测方法:")
print("=" * 40)
for method, details in self.deadlock_detection_methods.items():
print(f"\n{method}:")
for key, value in details.items():
print(f" {key}: {value}")
def deadlock_examples(self):
"""死锁示例"""
examples = """
-- 死锁示例和分析
-- 1. 经典死锁示例
-- 创建测试表
CREATE TABLE deadlock_test (
id INT PRIMARY KEY,
value VARCHAR(50)
);
INSERT INTO deadlock_test VALUES (1, 'A'), (2, 'B');
-- 会话1
START TRANSACTION;
UPDATE deadlock_test SET value = 'A1' WHERE id = 1; -- 锁定id=1
-- 等待10秒
SELECT SLEEP(10);
UPDATE deadlock_test SET value = 'B1' WHERE id = 2; -- 尝试锁定id=2
COMMIT;
-- 会话2(在会话1的SLEEP期间执行)
START TRANSACTION;
UPDATE deadlock_test SET value = 'B2' WHERE id = 2; -- 锁定id=2
UPDATE deadlock_test SET value = 'A2' WHERE id = 1; -- 尝试锁定id=1,死锁!
COMMIT;
-- 2. 间隙锁死锁示例
CREATE TABLE gap_lock_test (
id INT PRIMARY KEY,
name VARCHAR(50)
);
INSERT INTO gap_lock_test VALUES (1, 'A'), (10, 'B');
-- 会话1
START TRANSACTION;
SELECT * FROM gap_lock_test WHERE id = 5 FOR UPDATE; -- 在(1,10)间隙加锁
-- 会话2
START TRANSACTION;
SELECT * FROM gap_lock_test WHERE id = 6 FOR UPDATE; -- 在(1,10)间隙加锁
-- 会话1尝试插入
INSERT INTO gap_lock_test VALUES (5, 'C'); -- 被会话2阻塞
-- 会话2尝试插入
INSERT INTO gap_lock_test VALUES (6, 'D'); -- 被会话1阻塞,死锁!
-- 3. 查看死锁信息
-- 启用死锁日志记录
SET GLOBAL innodb_print_all_deadlocks = 1;
-- 查看最近的死锁信息
SHOW ENGINE INNODB STATUS\G
-- 在错误日志中查看详细的死锁信息
-- 死锁信息包括:
-- - 涉及的事务
-- - 持有的锁
-- - 等待的锁
-- - 被回滚的事务
-- 4. 死锁监控查询
-- 查看当前锁等待
SELECT
r.trx_id waiting_trx_id,
r.trx_mysql_thread_id waiting_thread,
r.trx_query waiting_query,
b.trx_id blocking_trx_id,
b.trx_mysql_thread_id blocking_thread,
b.trx_query blocking_query
FROM information_schema.innodb_lock_waits w
INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id
INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id;
-- 查看死锁统计
SHOW STATUS LIKE 'Innodb_deadlocks';
-- 5. 死锁预防示例
-- 统一访问顺序
-- 不好的做法(可能死锁)
START TRANSACTION;
UPDATE table1 SET col1 = 'value1' WHERE id = 1;
UPDATE table2 SET col2 = 'value2' WHERE id = 1;
COMMIT;
-- 另一个事务
START TRANSACTION;
UPDATE table2 SET col2 = 'value3' WHERE id = 1;
UPDATE table1 SET col1 = 'value4' WHERE id = 1;
COMMIT;
-- 好的做法(统一顺序)
-- 所有事务都按table1 -> table2的顺序访问
START TRANSACTION;
UPDATE table1 SET col1 = 'value1' WHERE id = 1;
UPDATE table2 SET col2 = 'value2' WHERE id = 1;
COMMIT;
START TRANSACTION;
UPDATE table1 SET col1 = 'value4' WHERE id = 1;
UPDATE table2 SET col2 = 'value3' WHERE id = 1;
COMMIT;
"""
print("\n死锁示例:")
print("=" * 30)
print(examples)
def deadlock_prevention_strategies(self):
"""死锁预防策略"""
strategies = """
死锁预防和处理策略:
1. 预防策略:
a) 统一加锁顺序:
- 所有事务按相同顺序访问资源
- 按主键顺序访问表
- 按表名字母顺序访问多个表
b) 减少事务大小:
- 将大事务拆分为小事务
- 减少锁持有时间
- 避免长时间的用户交互
c) 降低隔离级别:
- 使用READ COMMITTED代替REPEATABLE READ
- 减少间隙锁的使用
- 权衡一致性和并发性
d) 使用索引:
- 确保WHERE条件使用索引
- 减少锁定的行数
- 避免全表扫描
2. 检测和处理:
a) 自动检测:
- MySQL自动检测死锁
- 选择代价最小的事务回滚
- 通常回滚插入、更新、删除行数最少的事务
b) 超时处理:
- 设置合理的锁等待超时时间
- innodb_lock_wait_timeout参数
- 应用程序处理超时异常
c) 重试机制:
- 应用程序检测死锁异常
- 实现指数退避重试
- 限制重试次数
3. 监控和分析:
a) 死锁日志:
- 启用innodb_print_all_deadlocks
- 分析死锁模式
- 识别热点资源
b) 性能监控:
- 监控死锁发生频率
- 分析锁等待时间
- 识别问题SQL
c) 应用优化:
- 优化业务逻辑
- 减少并发冲突
- 使用乐观锁
4. 最佳实践:
- 保持事务简短
- 在同一事务中按相同顺序访问表和行
- 使用较低的隔离级别
- 为WHERE子句添加合适的索引
- 避免在事务中进行用户交互
- 实现死锁重试逻辑
- 定期监控死锁情况
"""
print("\n死锁预防策略:")
print("=" * 40)
print(strategies)
def deadlock_troubleshooting(self):
"""死锁故障排除"""
troubleshooting = """
死锁故障排除步骤:
1. 收集信息:
-- 查看死锁统计
SHOW STATUS LIKE 'Innodb_deadlocks';
-- 查看最近的死锁信息
SHOW ENGINE INNODB STATUS\G
-- 查看当前锁等待
SELECT * FROM information_schema.INNODB_TRX;
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
2. 分析死锁日志:
- 识别涉及的表和索引
- 分析事务的SQL语句
- 确定锁的类型和范围
- 找出循环等待的模式
3. 定位问题代码:
- 查找相关的应用程序代码
- 分析事务的执行逻辑
- 检查资源访问顺序
- 识别并发热点
4. 制定解决方案:
- 统一资源访问顺序
- 优化事务逻辑
- 调整隔离级别
- 添加或优化索引
5. 验证和监控:
- 部署修复方案
- 监控死锁发生频率
- 验证性能改善
- 持续优化
常见死锁场景及解决方案:
场景1:不同顺序访问多个表
解决:统一表访问顺序
场景2:间隙锁冲突
解决:使用READ COMMITTED隔离级别
场景3:大事务锁定过多资源
解决:拆分事务,减少锁持有时间
场景4:热点行竞争
解决:业务逻辑优化,分散热点
场景5:外键约束冲突
解决:优化外键设计,统一操作顺序
"""
print("\n死锁故障排除:")
print("=" * 40)
print(troubleshooting)
# 死锁管理演示
deadlock_demo = DeadlockManagement()
deadlock_demo.explain_deadlock_types()
deadlock_demo.explain_detection_methods()
deadlock_demo.deadlock_examples()
deadlock_demo.deadlock_prevention_strategies()
deadlock_demo.deadlock_troubleshooting()
5.3 MVCC多版本并发控制
5.3.1 MVCC原理与实现
class MVCCMechanism:
def __init__(self):
self.mvcc_concepts = {
"基本概念": {
"定义": "多版本并发控制,通过保存数据的多个版本来实现并发控制",
"目标": "在不加锁的情况下解决读写冲突",
"优势": "读不阻塞写,写不阻塞读,提高并发性能"
},
"版本链": {
"定义": "同一行数据的多个版本通过指针连接形成的链表",
"结构": "最新版本在前,历史版本在后",
"维护": "通过undo log实现"
},
"ReadView": {
"定义": "事务开始时创建的一致性视图",
"作用": "确定当前事务可以看到哪些版本的数据",
"内容": "活跃事务列表、最小事务ID、最大事务ID"
}
}
self.mvcc_fields = {
"DB_TRX_ID": {
"描述": "最后修改该行的事务ID",
"大小": "6字节",
"作用": "标识数据版本"
},
"DB_ROLL_PTR": {
"描述": "回滚指针,指向undo log中的记录",
"大小": "7字节",
"作用": "构建版本链"
},
"DB_ROW_ID": {
"描述": "行ID,当表没有主键时使用",
"大小": "6字节",
"作用": "唯一标识行"
}
}
def explain_mvcc_concepts(self):
"""解释MVCC概念"""
print("MVCC多版本并发控制:")
print("=" * 60)
for concept, details in self.mvcc_concepts.items():
print(f"\n{concept}:")
for key, value in details.items():
print(f" {key}: {value}")
def explain_mvcc_fields(self):
"""解释MVCC相关字段"""
print("\nMVCC隐藏字段:")
print("=" * 40)
for field, details in self.mvcc_fields.items():
print(f"\n{field}:")
for key, value in details.items():
print(f" {key}: {value}")
def mvcc_workflow(self):
"""MVCC工作流程"""
workflow = """
MVCC工作流程:
1. 事务开始:
- 分配唯一的事务ID(递增)
- 创建ReadView(一致性视图)
- 记录当前活跃事务列表
2. 读取数据:
- 根据ReadView判断数据版本可见性
- 如果当前版本不可见,通过undo log查找历史版本
- 返回第一个可见的版本
3. 修改数据:
- 在undo log中记录修改前的数据
- 更新行的DB_TRX_ID为当前事务ID
- 更新DB_ROLL_PTR指向undo log记录
4. 版本可见性判断规则:
a) 如果DB_TRX_ID < ReadView.min_trx_id:
版本可见(在ReadView创建前已提交)
b) 如果DB_TRX_ID >= ReadView.max_trx_id:
版本不可见(在ReadView创建后开始的事务)
c) 如果min_trx_id <= DB_TRX_ID < max_trx_id:
- 如果DB_TRX_ID在活跃事务列表中:不可见
- 如果DB_TRX_ID不在活跃事务列表中:可见
d) 如果DB_TRX_ID等于当前事务ID:
版本可见(自己的修改)
5. 不同隔离级别的ReadView创建时机:
- READ COMMITTED:每次查询都创建新的ReadView
- REPEATABLE READ:事务开始时创建一次ReadView
"""
print("\nMVCC工作流程:")
print("=" * 40)
print(workflow)
def mvcc_examples(self):
"""MVCC示例"""
examples = """
-- MVCC示例演示
-- 创建测试表
CREATE TABLE mvcc_test (
id INT PRIMARY KEY,
name VARCHAR(50),
balance DECIMAL(10,2)
);
INSERT INTO mvcc_test VALUES (1, 'Alice', 1000.00);
-- 示例1:READ COMMITTED隔离级别
-- 会话1
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT * FROM mvcc_test WHERE id = 1; -- 读取初始值:balance = 1000
-- 会话2
START TRANSACTION;
UPDATE mvcc_test SET balance = 1500 WHERE id = 1;
COMMIT; -- 提交修改
-- 回到会话1
SELECT * FROM mvcc_test WHERE id = 1; -- 读取到新值:balance = 1500
-- 因为READ COMMITTED每次查询都创建新的ReadView
COMMIT;
-- 示例2:REPEATABLE READ隔离级别
-- 会话1
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT * FROM mvcc_test WHERE id = 1; -- 读取:balance = 1500
-- 会话2
START TRANSACTION;
UPDATE mvcc_test SET balance = 2000 WHERE id = 1;
COMMIT;
-- 回到会话1
SELECT * FROM mvcc_test WHERE id = 1; -- 仍然读取:balance = 1500
-- 因为REPEATABLE READ使用事务开始时的ReadView
COMMIT;
-- 示例3:查看undo log信息(仅供理解,实际无法直接查看)
/*
假设有以下操作序列:
1. 事务100:INSERT INTO mvcc_test VALUES (1, 'Alice', 1000);
2. 事务101:UPDATE mvcc_test SET balance = 1500 WHERE id = 1;
3. 事务102:UPDATE mvcc_test SET balance = 2000 WHERE id = 1;
版本链结构(从新到旧):
当前版本:{id:1, name:'Alice', balance:2000, DB_TRX_ID:102, DB_ROLL_PTR:ptr1}
↓
undo log1:{id:1, name:'Alice', balance:1500, DB_TRX_ID:101, DB_ROLL_PTR:ptr2}
↓
undo log2:{id:1, name:'Alice', balance:1000, DB_TRX_ID:100, DB_ROLL_PTR:null}
不同事务的可见性:
- 事务103(在102提交后开始):看到balance=2000
- 事务104(在101提交后、102提交前开始):看到balance=1500
- 事务105(在100提交后、101开始前开始):看到balance=1000
*/
-- 示例4:MVCC与锁的配合
-- 当前读(加锁读)不使用MVCC
START TRANSACTION;
-- 快照读(使用MVCC)
SELECT * FROM mvcc_test WHERE id = 1;
-- 当前读(不使用MVCC,会加锁)
SELECT * FROM mvcc_test WHERE id = 1 FOR UPDATE;
SELECT * FROM mvcc_test WHERE id = 1 LOCK IN SHARE MODE;
-- DML语句都是当前读
UPDATE mvcc_test SET balance = balance + 100 WHERE id = 1;
DELETE FROM mvcc_test WHERE id = 1;
INSERT INTO mvcc_test VALUES (2, 'Bob', 500);
COMMIT;
-- 示例5:幻读的解决
-- REPEATABLE READ + MVCC可以解决快照读的幻读
-- 但当前读仍可能出现幻读,需要间隙锁解决
START TRANSACTION;
-- 快照读,不会出现幻读
SELECT COUNT(*) FROM mvcc_test WHERE balance > 1000;
-- 当前读,可能出现幻读(如果没有间隙锁)
SELECT COUNT(*) FROM mvcc_test WHERE balance > 1000 FOR UPDATE;
COMMIT;
"""
print("\nMVCC示例:")
print("=" * 30)
print(examples)
def mvcc_advantages_limitations(self):
"""MVCC优势与限制"""
analysis = """
MVCC优势与限制分析:
优势:
1. 高并发性能:
- 读操作不需要加锁
- 写操作不会阻塞读操作
- 大大提高了系统的并发能力
2. 一致性保证:
- 提供事务级别的一致性视图
- 避免脏读、不可重复读
- 在REPEATABLE READ级别解决幻读
3. 无锁读取:
- 快照读不需要等待锁
- 减少锁竞争和死锁
- 提高查询响应速度
4. 历史版本访问:
- 支持时间点查询
- 便于数据恢复和审计
- 支持闪回查询
限制:
1. 存储开销:
- 需要额外存储历史版本
- undo log占用空间
- 版本链过长影响性能
2. 清理复杂性:
- 需要定期清理过期版本
- purge线程的开销
- 长事务影响清理效率
3. 内存消耗:
- ReadView占用内存
- 版本链遍历消耗CPU
- 大量并发事务的内存开销
4. 当前读限制:
- 当前读仍需要加锁
- 无法完全避免锁等待
- 某些场景下性能不如预期
最佳实践:
1. 避免长事务:
- 及时提交事务
- 避免在事务中进行耗时操作
- 监控事务执行时间
2. 合理使用隔离级别:
- 根据业务需求选择合适的隔离级别
- 权衡一致性和性能
- 避免不必要的高隔离级别
3. 监控版本链长度:
- 定期检查undo log大小
- 监控purge线程效率
- 优化长事务问题
4. 优化查询模式:
- 尽量使用快照读
- 减少不必要的当前读
- 合理设计索引
"""
print("\nMVCC优势与限制:")
print("=" * 40)
print(analysis)
# MVCC机制演示
mvcc_demo = MVCCMechanism()
mvcc_demo.explain_mvcc_concepts()
mvcc_demo.explain_mvcc_fields()
mvcc_demo.mvcc_workflow()
mvcc_demo.mvcc_examples()
mvcc_demo.mvcc_advantages_limitations()
5.4 总结
本章详细介绍了MySQL的事务处理与锁机制,包括:
核心知识点
事务ACID特性:
- 原子性:事务要么全部成功,要么全部失败
- 一致性:数据库从一个一致性状态转换到另一个一致性状态
- 隔离性:并发事务之间相互隔离
- 持久性:已提交的事务永久保存
事务隔离级别:
- READ UNCOMMITTED:可能出现脏读、不可重复读、幻读
- READ COMMITTED:避免脏读,可能出现不可重复读、幻读
- REPEATABLE READ:避免脏读和不可重复读,MySQL InnoDB解决了幻读
- SERIALIZABLE:完全串行化,避免所有并发问题
锁机制:
- 表级锁:MyISAM使用,并发度低但开销小
- 行级锁:InnoDB使用,并发度高但开销大
- 共享锁和排他锁:控制读写操作的并发
- 意向锁:提高表级锁的效率
死锁处理:
- 死锁检测:MySQL自动检测死锁并回滚代价最小的事务
- 死锁预防:统一加锁顺序、减少事务大小、使用合适的隔离级别
- 死锁监控:启用死锁日志,分析死锁模式
MVCC机制:
- 多版本并发控制:通过保存数据的多个版本实现并发控制
- ReadView:事务的一致性视图,决定可见的数据版本
- 版本链:通过undo log构建的历史版本链表
- 快照读vs当前读:快照读使用MVCC,当前读需要加锁
最佳实践
事务设计:
- 保持事务简短,及时提交或回滚
- 避免在事务中进行用户交互
- 合理选择事务隔离级别
锁优化:
- 统一资源访问顺序避免死锁
- 使用索引减少锁定范围
- 监控锁等待和死锁情况
并发控制:
- 充分利用MVCC的优势
- 区分快照读和当前读的使用场景
- 避免长事务影响系统性能
性能调优:
- 合理配置InnoDB参数
- 监控事务执行时间
- 优化热点数据的访问模式
下一章我们将学习MySQL的复制与高可用,这是构建可靠数据库系统的重要技术。