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的事务处理与锁机制,包括:

核心知识点

  1. 事务ACID特性

    • 原子性:事务要么全部成功,要么全部失败
    • 一致性:数据库从一个一致性状态转换到另一个一致性状态
    • 隔离性:并发事务之间相互隔离
    • 持久性:已提交的事务永久保存
  2. 事务隔离级别

    • READ UNCOMMITTED:可能出现脏读、不可重复读、幻读
    • READ COMMITTED:避免脏读,可能出现不可重复读、幻读
    • REPEATABLE READ:避免脏读和不可重复读,MySQL InnoDB解决了幻读
    • SERIALIZABLE:完全串行化,避免所有并发问题
  3. 锁机制

    • 表级锁:MyISAM使用,并发度低但开销小
    • 行级锁:InnoDB使用,并发度高但开销大
    • 共享锁和排他锁:控制读写操作的并发
    • 意向锁:提高表级锁的效率
  4. 死锁处理

    • 死锁检测:MySQL自动检测死锁并回滚代价最小的事务
    • 死锁预防:统一加锁顺序、减少事务大小、使用合适的隔离级别
    • 死锁监控:启用死锁日志,分析死锁模式
  5. MVCC机制

    • 多版本并发控制:通过保存数据的多个版本实现并发控制
    • ReadView:事务的一致性视图,决定可见的数据版本
    • 版本链:通过undo log构建的历史版本链表
    • 快照读vs当前读:快照读使用MVCC,当前读需要加锁

最佳实践

  1. 事务设计

    • 保持事务简短,及时提交或回滚
    • 避免在事务中进行用户交互
    • 合理选择事务隔离级别
  2. 锁优化

    • 统一资源访问顺序避免死锁
    • 使用索引减少锁定范围
    • 监控锁等待和死锁情况
  3. 并发控制

    • 充分利用MVCC的优势
    • 区分快照读和当前读的使用场景
    • 避免长事务影响系统性能
  4. 性能调优

    • 合理配置InnoDB参数
    • 监控事务执行时间
    • 优化热点数据的访问模式

下一章我们将学习MySQL的复制与高可用,这是构建可靠数据库系统的重要技术。