1. 首页 > 国产数据库教程 > TiDB教程 > 正文

tidb教程FG121-事务管理与并发控制生产实战

fgedu.net.cn

目录

一、基础概念

1.1 事务定义

事务是一组原子性的SQL操作,要么全部执行成功,要么全部失败回滚。TiDB支持ACID特性:

  • 原子性(Atomicity):事务作为一个整体执行,要么全部成功,要么全部失败
  • 一致性(Consistency):事务执行前后数据库状态保持一致
  • 隔离性(Isolation):不同事务之间相互隔离,互不影响
  • 持久性(Durability):事务提交后,数据更改永久保存

1.2 隔离级别

隔离级别 脏读 不可重复读 幻读 描述
READ UNCOMMITTED 允许读取未提交的数据
READ COMMITTED × 只读取已提交的数据
REPEATABLE READ × × 保证同一事务中多次读取结果一致
SERIALIZABLE × × × 完全串行执行事务

1.3 并发控制机制

  • 乐观并发控制(OCC):TiDB默认使用的并发控制机制,通过MVCC和冲突检测实现
  • 悲观并发控制(PCC):通过锁机制实现,适用于高冲突场景
  • MVCC(多版本并发控制):为每个数据版本维护多个快照,实现无锁读

二、规划建议

2.1 事务设计原则

  • 保持事务简短:尽量减少事务持续时间,避免长时间占用锁资源
  • 避免大事务:大事务会增加锁竞争和回滚成本
  • 合理设置隔离级别:根据业务需求选择合适的隔离级别
  • 避免死锁:合理安排操作顺序,避免循环依赖
  • 使用批量操作:对于大量数据操作,考虑使用批量处理

2.2 隔离级别选择

  • READ COMMITTED:适用于对一致性要求不高的场景,如报表查询
  • REPEATABLE READ:TiDB默认隔离级别,适用于大多数业务场景
  • SERIALIZABLE:适用于对一致性要求极高的场景,如金融交易

2.3 并发控制策略

  • 乐观锁:适用于低冲突场景,并发性能高
  • 悲观锁:适用于高冲突场景,保证数据一致性
  • 混合锁:根据业务场景灵活选择

风哥提示:

三、实施方案

3.1 事务操作

开始事务

START TRANSACTION;

提交事务

COMMIT;

回滚事务

ROLLBACK;

3.2 隔离级别设置

设置全局隔离级别

SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;

设置会话隔离级别

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

设置事务隔离级别

START TRANSACTION ISOLATION LEVEL SERIALIZABLE;

3.3 锁机制

悲观锁

-- 使用FOR UPDATE获取行级锁
START TRANSACTION;
SELECT * FROM fgedudb.fgedu_orders WHERE id = 1 FOR UPDATE;
UPDATE fgedudb.fgedu_orders SET status = 2 WHERE id = 1;
COMMIT;

乐观锁

-- 使用版本号实现乐观锁
START TRANSACTION;
SELECT id, status, version FROM fgedudb.fgedu_orders WHERE id = 1;
-- 应用程序检查版本号
UPDATE fgedudb.fgedu_orders SET status = 2, version = version + 1 WHERE id = 1 AND version = 1;
-- 检查影响行数
IF ROW_COUNT() = 0 THEN
    ROLLBACK;
    -- 处理冲突
ELSE
    COMMIT;
END IF;

3.4 死锁处理

查看死锁信息

SHOW ENGINE INNODB STATUS;
=====================================
2024-04-09 10:00:00 0x7f1234567890 INNODB MONITOR OUTPUT
=====================================
...
LATEST DETECTED DEADLOCK
------------------------
2024-04-09 10:00:00 0x7f1234567890
*** (1) TRANSACTION:
TRANSACTION 12345, ACTIVE 1 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 10, OS thread handle 1397134567890, query id 12345 192.168.1.10 root updating
UPDATE fgedudb.fgedu_orders SET status = 2 WHERE id = 1
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 123 page no 4 n bits 72 index PRIMARY of table `fgedudb`.`fgedu_orders` trx id 12345 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
...
*** (2) TRANSACTION:
TRANSACTION 12346, ACTIVE 2 sec starting index read
mysql tables in use 1, locked 1
2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 11, OS thread handle 1397134567900, query id 12346 192.168.1.10 root updating
UPDATE fgedudb.fgedu_orders SET status = 3 WHERE id = 2
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 123 page no 4 n bits 72 index PRIMARY of table `fgedudb`.`fgedu_orders` trx id 12346 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
...
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 123 page no 5 n bits 72 index PRIMARY of table `fgedudb`.`fgedu_orders` trx id 12346 lock_mode X locks rec but not gap waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
...
*** WE ROLL BACK TRANSACTION (1)

学习交流加群风哥QQ113257174

避免死锁的方法

-- 方法1:按相同顺序访问资源
-- 事务1
START TRANSACTION;
UPDATE fgedudb.fgedu_orders SET status = 2 WHERE id = 1;
UPDATE fgedudb.fgedu_orders SET status = 3 WHERE id = 2;
COMMIT;

-- 事务2
START TRANSACTION;
UPDATE fgedudb.fgedu_orders SET status = 2 WHERE id = 1;
UPDATE fgedudb.fgedu_orders SET status = 3 WHERE id = 2;
COMMIT;

-- 方法2:使用超时机制
SET SESSION innodb_lock_wait_timeout = 30;

-- 方法3:使用悲观锁
START TRANSACTION;
SELECT * FROM fgedudb.fgedu_orders WHERE id IN (1, 2) FOR UPDATE;
UPDATE fgedudb.fgedu_orders SET status = 2 WHERE id = 1;
UPDATE fgedudb.fgedu_orders SET status = 3 WHERE id = 2;
COMMIT;

四、实战案例

4.1 电商订单处理

场景:电商平台处理订单,需要保证订单状态的一致性和并发安全。

步骤1:创建订单表

CREATE TABLE fgedudb.fgedu_orders (
    id BIGINT PRIMARY KEY,
    order_no VARCHAR(32) NOT NULL,
    user_id BIGINT NOT NULL,
    total_amount DECIMAL(12,2) NOT NULL,
    status TINYINT NOT NULL DEFAULT 0,
    created_at DATETIME NOT NULL,
    updated_at DATETIME NOT NULL
);

步骤2:处理订单状态更新

-- 使用悲观锁处理订单状态更新
START TRANSACTION;
-- 锁定订单记录
SELECT * FROM fgedudb.fgedu_orders WHERE id = ? FOR UPDATE;
-- 检查订单状态
IF (SELECT status FROM fgedudb.fgedu_orders WHERE id = ?) = 0 THEN
    -- 更新订单状态为已支付
    UPDATE fgedudb.fgedu_orders SET status = 1, updated_at = NOW() WHERE id = ?;
    -- 记录支付日志
    INSERT INTO fgedudb.fgedu_payment_logs (order_id, amount, payment_method, created_at) VALUES (?, ?, ?, NOW());
    COMMIT;
    SELECT 'success' AS result;
ELSE
    ROLLBACK;
    SELECT 'failed' AS result, '订单状态错误' AS message;
END IF;

步骤3:并发测试

-- 会话1
START TRANSACTION;
SELECT * FROM fgedudb.fgedu_orders WHERE id = 1 FOR UPDATE;
-- 模拟处理时间
DO SLEEP(5);
UPDATE fgedudb.fgedu_orders SET status = 1 WHERE id = 1;
COMMIT;

-- 会话2(在会话1执行后立即执行)
START TRANSACTION;
SELECT * FROM fgedudb.fgedu_orders WHERE id = 1 FOR UPDATE;
-- 这里会等待会话1释放锁
UPDATE fgedudb.fgedu_orders SET status = 1 WHERE id = 1;
COMMIT;

4.2 库存管理

场景:电商平台库存管理,需要保证库存数据的一致性。

步骤1:创建库存表

CREATE TABLE fgedudb.fgedu_inventory (
    id BIGINT PRIMARY KEY,
    product_id BIGINT NOT NULL,
    quantity INT NOT NULL,
    version INT NOT NULL DEFAULT 1,
    updated_at DATETIME NOT NULL
);

步骤2:使用乐观锁更新库存

-- 乐观锁更新库存
START TRANSACTION;
-- 获取当前库存和版本号
SELECT quantity, version FROM fgedudb.fgedu_inventory WHERE product_id = ?;
-- 检查库存是否足够
IF quantity >= ? THEN
    -- 更新库存,使用版本号作为乐观锁
    UPDATE fgedudb.fgedu_inventory SET 
        quantity = quantity - ?, 
        version = version + 1, 
        updated_at = NOW() 
    WHERE product_id = ? AND version = ?;
    -- 检查更新是否成功
    IF ROW_COUNT() = 1 THEN
        -- 记录库存变动
        INSERT INTO fgedudb.fgedu_inventory_logs (product_id, quantity_change, type, created_at) VALUES (?, -?, 'out', NOW());
        COMMIT;
        SELECT 'success' AS result;
    ELSE
        ROLLBACK;
        SELECT 'failed' AS result, '库存更新冲突' AS message;
    END IF;
ELSE
    ROLLBACK;
    SELECT 'failed' AS result, '库存不足' AS message;
END IF;

步骤3:高并发测试

-- 模拟高并发场景
-- 会话1
START TRANSACTION;
SELECT quantity, version FROM fgedudb.fgedu_inventory WHERE product_id = 1;
-- 模拟处理时间
DO SLEEP(2);
UPDATE fgedudb.fgedu_inventory SET quantity = quantity - 1, version = version + 1 WHERE product_id = 1 AND version = 1;
COMMIT;

-- 会话2(在会话1执行后立即执行)
START TRANSACTION;
SELECT quantity, version FROM fgedudb.fgedu_inventory WHERE product_id = 1;
-- 此时获取到的版本号仍然是1
UPDATE fgedudb.fgedu_inventory SET quantity = quantity - 1, version = version + 1 WHERE product_id = 1 AND version = 1;
-- 由于版本号不匹配,更新失败
COMMIT;

五、经验总结

5.1 事务管理最佳实践

  • 保持事务简短:尽量减少事务持续时间,避免长时间占用锁资源
  • 避免大事务:将大事务拆分为多个小事务
  • 合理设置隔离级别:根据业务需求选择合适的隔离级别
  • 使用批量操作:对于大量数据操作,使用批量处理减少事务次数
  • 定期提交:避免长时间未提交的事务

5.2 并发控制最佳实践

  • 选择合适的锁机制:低冲突场景使用乐观锁,高冲突场景使用悲观锁
  • 避免死锁:按相同顺序访问资源,设置合理的锁超时
  • 使用索引:确保锁操作能够快速定位到目标记录
  • 监控锁等待:定期检查锁等待情况,优化热点数据访问
  • 使用分布式锁:对于跨节点的并发控制,使用分布式锁

5.3 常见问题与解决方案

问题 原因 解决方案
死锁 事务间循环等待锁资源 按相同顺序访问资源,设置锁超时
锁等待超时 锁竞争激烈或事务执行时间过长 优化事务逻辑,减少事务持续时间
乐观锁冲突 并发更新同一记录 实现重试机制,或使用悲观锁
大事务性能问题 事务操作数据量过大 拆分为小事务,使用批量处理
隔离级别导致的问题 隔离级别设置不当 根据业务需求选择合适的隔离级别

5.4 性能优化建议

  • 使用索引:确保WHERE子句中的列有索引,减少锁范围
  • 避免全表扫描:全表扫描会导致锁表,影响并发性能
  • 使用分区表:将数据分散到多个分区,减少锁竞争
  • 优化SQL语句:减少事务中的复杂查询
  • 监控事务性能:定期分析事务执行时间和锁等待情况
  • 合理使用连接池:控制并发连接数,避免连接耗尽

更多视频教程www.fgedu.net.cn

© 2024 TiDB数据库培训文档

本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html

联系我们

在线咨询:点击这里给我发消息

微信号:itpux-com

工作日:9:30-18:30,节假日休息