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

tidb教程FG138-TiDB事务管理

fgedu.net.cn

目录

一、基础概念

1.1 事务定义

事务是指一组SQL语句的集合,这些语句要么全部执行成功,要么全部执行失败。事务具有ACID特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

1.2 事务隔离级别

  • 读未提交(Read Uncommitted):允许读取未提交的数据,可能导致脏读、不可重复读和幻读。
  • 读已提交(Read Committed):只允许读取已提交的数据,避免脏读,但可能导致不可重复读和幻读。
  • 可重复读(Repeatable Read):确保同一事务中多次读取同一数据得到相同结果,避免脏读和不可重复读,但可能导致幻读。
  • 串行化(Serializable):最高隔离级别,完全避免脏读、不可重复读和幻读,但性能较差。

1.3 TiDB事务模型

TiDB采用乐观并发控制(Optimistic Concurrency Control,OCC)和两阶段提交(Two-Phase Commit,2PC)协议来实现事务。在TiDB中,事务的执行过程包括:

  • 开始事务:记录事务的开始时间戳。
  • 执行SQL:执行事务中的SQL语句,记录修改的行。
  • 预提交:检查是否有冲突。
  • 提交:如果没有冲突,提交事务;否则,回滚事务。

1.4 事务冲突

事务冲突是指两个或多个事务同时修改同一数据,导致数据不一致的情况。TiDB通过乐观并发控制来处理事务冲突,当冲突发生时,会回滚其中一个事务。

二、规划建议

2.1 事务设计

  • 保持事务短小:事务应该尽可能短小,减少锁定时间。
  • 避免长时间事务:长时间事务会占用资源,增加冲突概率。
  • 合理设置隔离级别:根据业务需求选择合适的隔离级别。
  • 避免在事务中执行昂贵操作:如大量计算、网络请求等。

2.2 并发控制

  • 乐观并发控制:适合读多写少的场景。
  • 悲观并发控制:适合写多读少的场景。
  • 合理使用锁:根据业务需求使用行锁、表锁等。

2.3 错误处理

  • 处理事务冲突:当事务冲突时,需要重试事务。
  • 处理死锁:当发生死锁时,需要回滚事务。
  • 处理超时:当事务超时时,需要回滚事务。

三、实施方案

3.1 事务操作

开始事务

-- 开始事务
BEGIN;
-- 或
START TRANSACTION;

提交事务

-- 提交事务
COMMIT;

回滚事务

-- 回滚事务
ROLLBACK;

3.2 设置隔离级别

查看当前隔离级别

-- 查看当前隔离级别
SELECT @@tx_isolation;
-- 或
SELECT @@transaction_isolation;
+-----------------------+风哥提示:
| @@transaction_isolation |
+-----------------------+
| REPEATABLE-READ       |
+-----------------------+

设置隔离级别

-- 设置会话级隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 设置全局隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;

3.3 事务示例

基本事务操作

-- 开始事务
BEGIN;

-- 执行SQL语句
INSERT INTO fgedu_users VALUES (3, 'user3', 'user3@example.com', 35, NOW());
UPDATE fgedu_users SET age = 31 WHERE id = 1;

-- 提交事务
COMMIT;

-- 查看结果
SELECT * FROM fgedu_users;
+------+----------+------------------+-----+---------------------+
| id   | username | email            | age | created_at          |
+------+----------+------------------+-----+---------------------+
|    1 | admin    | admin@example.com |  31 | 2024-04-09 10:00:00 |
|    2 | user1    | user1@example.com |  25 | 2024-04-09 10:00:00 |
|    3 | user3    | user3@example.com |  35 | 2024-04-09 11:00:00 |
+------+----------+------------------+-----+---------------------+

事务回滚

-- 开始事务
BEGIN;

-- 执行SQL语句
INSERT INTO fgedu_users VALUES (4, 'user4', 'user4@example.com', 40, NOW());
UPDATE fgedu_users SET age = 32 WHERE id = 1;

-- 回滚事务
ROLLBACK;

-- 查看结果
SELECT * FROM fgedu_users;
+------+----------+------------------+-----+---------------------+
| id   | username | email            | age | created_at          |
+------+----------+------------------+-----+---------------------+
|    1 | admin    | admin@example.com |  31 | 2024-04-09 10:00:00 |
|    2 | user1    | user1@example.com |  25 | 2024-04-09 10:00:00 |
|    3 | user3    | user3@example.com |  35 | 2024-04-09 11:00:00 |
+------+----------+------------------+-----+---------------------+

3.4 事务冲突处理

模拟事务冲突

-- 会话1:开始事务并修改数据
BEGIN;
UPDATE fgedu_users SET age = 33 WHERE id = 1;

-- 会话2:开始事务并修改同一数据
BEGIN;
UPDATE fgedu_users SET age = 34 WHERE id = 1;

-- 会话1:提交事务
COMMIT;

-- 会话2:提交事务(会失败)
COMMIT;
ERROR 9007 (HY000): Write conflict, txnStartTS=43651234567890, conflictStartTS=43651234567891, conflictCommitTS=43651234567892, key={tableID=123, rowID=1}, primary={tableID=123, rowID=1}

处理事务冲突

-- 重试事务
BEGIN;
-- 重新读取数据
SELECT * FROM fgedu_users WHERE id = 1;
-- 基于最新数据进行修改
UPDATE fgedu_users SET age = 34 WHERE id = 1;
COMMIT;

-- 查看结果
SELECT * FROM fgedu_users WHERE id = 1;
+------+----------+------------------+-----+---------------------+
| id   | username | email            | age | created_at          |
+------+----------+------------------+-----+---------------------+
|    1 | admin    | admin@example.com |  34 | 2024-04-09 10:00:00 |学习交流加群风哥QQ113257174
+------+----------+------------------+-----+---------------------+

3.5 死锁处理

模拟死锁

-- 会话1:开始事务
BEGIN;
-- 锁定行1
UPDATE fgedu_users SET age = 35 WHERE id = 1;

-- 会话2:开始事务
BEGIN;
-- 锁定行2
UPDATE fgedu_users SET age = 26 WHERE id = 2;

-- 会话1:尝试锁定行2
UPDATE fgedu_users SET age = 27 WHERE id = 2;

-- 会话2:尝试锁定行1
UPDATE fgedu_users SET age = 36 WHERE id = 1;
-- 会话1或会话2会收到死锁错误
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

处理死锁

-- 回滚事务
ROLLBACK;

-- 重新执行事务,按相同顺序访问资源
BEGIN;
-- 先锁定行1
UPDATE fgedu_users SET age = 35 WHERE id = 1;
-- 再锁定行2
UPDATE fgedu_users SET age = 27 WHERE id = 2;
COMMIT;

3.6 事务监控

查看事务状态

-- 查看当前活跃事务
SELECT * FROM information_schema.innodb_trx;

-- 查看锁信息
SELECT * FROM information_schema.innodb_locks;

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

使用TiDB Dashboard

# 访问TiDB Dashboard
# 地址:http://tidb-server:10080/dashboard

# 查看事务
# 在TiDB Dashboard > 事务

# 查看锁
# 在TiDB Dashboard > 锁

四、实战案例

4.1 高并发事务处理

场景:电商系统中的订单处理,需要处理大量并发事务。

步骤1:创建订单表

-- 创建订单表
CREATE TABLE fgedu_orders (
    id INT PRIMARY KEY,
    user_id INT NOT NULL,
    amount DECIMAL(10,2) NOT NULL,
    status VARCHAR(20) NOT NULL,
    created_at DATETIME NOT NULL
);

-- 创建库存表
CREATE TABLE fgedu_inventory (
    id INT PRIMARY KEY,
    product_id INT NOT NULL,
    quantity INT NOT NULL,
    updated_at DATETIME NOT NULL
);

步骤2:处理订单事务

-- 开始事务
BEGIN;

-- 检查库存
SELECT quantity FROM fgedu_inventory WHERE product_id = 1 FOR UPDATE;

-- 库存不足,回滚事务
-- IF quantity < order_quantity THEN ROLLBACK;

-- 减少库存
UPDATE fgedu_inventory SET quantity = quantity - 1, updated_at = NOW() WHERE product_id = 1;

-- 创建订单
INSERT INTO fgedu_orders VALUES (1, 1, 100.00, 'pending', NOW());

-- 提交事务
COMMIT;

-- 查看结果
SELECT * FROM fgedu_inventory WHERE product_id = 1;
SELECT * FROM fgedu_orders WHERE id = 1;
+------+------------+----------+---------------------+
| id   | product_id | quantity | updated_at          |
+------+------------+----------+---------------------+
|    1 |          1 |       99 | 2024-04-09 12:00:00 |
+------+------------+----------+---------------------+

+------+---------+--------+---------+---------------------+
| id   | user_id | amount | status  | created_at          |
+------+---------+--------+---------+---------------------+
|    1 |       1 | 100.00 | pending | 2024-04-09 12:00:00 |
+------+---------+--------+---------+---------------------+

步骤3:处理并发事务

-- 会话1:开始事务
BEGIN;
SELECT quantity FROM fgedu_inventory WHERE product_id = 1 FOR UPDATE;
-- 模拟处理时间
DO SLEEP(5);
UPDATE fgedu_inventory SET quantity = quantity - 1, updated_at = NOW() WHERE product_id = 1;
INSERT INTO fgedu_orders VALUES (2, 2, 100.00, 'pending', NOW());
COMMIT;

-- 会话2:开始事务(在会话1执行期间)
BEGIN;
SELECT quantity FROM fgedu_inventory WHERE product_id = 1 FOR UPDATE;
-- 会等待会话1提交
UPDATE fgedu_inventory SET quantity = quantity - 1, updated_at = NOW() WHERE product_id = 1;
INSERT INTO fgedu_orders VALUES (3, 3, 100.00, 'pending', NOW());
COMMIT;

-- 查看结果
SELECT * FROM fgedu_inventory WHERE product_id = 1;
SELECT * FROM fgedu_orders;
+------+------------+----------+---------------------+
| id   | product_id | quantity | updated_at          |
+------+------------+----------+---------------------+
|    1 |          1 |       97 | 2024-04-09 12:00:10 |
+------+------------+----------+---------------------+

+------+---------+--------+---------+---------------------+
| id   | user_id | amount | status  | created_at          |
+------+---------+--------+---------+---------------------+
|    1 |       1 | 100.00 | pending | 2024-04-09 12:00:00 |
|    2 |       2 | 100.00 | pending | 2024-04-09 12:00:05 |
|    3 |       3 | 100.00 | pending | 2024-04-09 12:00:10 |
+------+---------+--------+---------+---------------------+

4.2 事务优化

场景:优化长事务,减少锁定时间。

步骤1:识别长事务

-- 查看长事务
SELECT * FROM information_schema.innodb_trx WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 60;

-- 查看慢事务
SHOW GLOBAL VARIABLES LIKE 'slow_query_log';
SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 10;

步骤2:优化事务

-- 不好的事务:包含非数据库操作
BEGIN;
-- 执行数据库操作
UPDATE fgedu_users SET age = 30 WHERE id = 1;
-- 执行网络请求(耗时操作)
-- 执行文件操作(耗时操作)
COMMIT;

-- 好的事务:只包含数据库操作
-- 先执行非数据库操作
-- 执行网络请求
-- 执行文件操作
-- 再执行数据库操作
BEGIN;
UPDATE fgedu_users SET age = 30 WHERE id = 1;
COMMIT;

步骤3:使用批量操作

-- 不好的做法:多次执行单个事务
BEGIN;
INSERT INTO fgedu_users VALUES (4, 'user4', 'user4@example.com', 40, NOW());
COMMIT;
BEGIN;
INSERT INTO fgedu_users VALUES (5, 'user5', 'user5@example.com', 45, NOW());
COMMIT;

-- 好的做法:批量执行事务
BEGIN;
INSERT INTO fgedu_users VALUES (4, 'user4', 'user4@example.com', 40, NOW());
INSERT INTO fgedu_users VALUES (5, 'user5', 'user5@example.com', 45, NOW());
COMMIT;

五、经验总结

5.1 事务管理最佳实践

  • 保持事务短小:事务应该尽可能短小,减少锁定时间。
  • 避免长时间事务:长时间事务会占用资源,增加冲突概率。
  • 合理设置隔离级别:根据业务需求选择合适的隔离级别。
  • 避免在事务中执行昂贵操作:如大量计算、网络请求等。
  • 处理事务冲突:当事务冲突时,需要重试事务。
  • 处理死锁:当发生死锁时,需要回滚事务并重新执行。
  • 使用批量操作:批量执行SQL语句,减少事务数量。
  • 监控事务:定期监控事务状态,及时发现和处理问题。

5.2 隔离级别选择

隔离级别 脏读 不可重复读 幻读 适用场景
读未提交 可能 可能 可能 对数据一致性要求不高的场景
读已提交 不可能 可能 可能 大多数业务场景
可重复读 不可能 不可能 可能 对数据一致性要求较高的场景
串行化 不可能 不可能 不可能 对数据一致性要求极高的场景

5.3 常见问题与解决方案

问题 原因 解决方案
事务冲突 多个事务同时修改同一数据 重试事务,使用乐观并发控制
死锁 事务循环等待锁 回滚事务,按相同顺序访问资源
长事务 事务执行时间过长 拆分事务,减少事务执行时间
事务超时 事务执行时间超过阈值 优化事务,增加超时时间
性能下降 事务过多,锁定资源 优化事务,使用批量操作

5.4 事务管理检查清单

检查项 配置要求 状态
事务设计 保持事务短小,避免长时间事务
隔离级别 根据业务需求选择合适的隔离级别
并发控制 合理使用乐观或悲观并发控制
错误处理 处理事务冲突、死锁和超时
批量操作 使用批量操作减少事务数量
监控 定期监控事务状态
优化 定期优化事务性能
测试 测试事务在高并发场景下的性能

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

© 2024 TiDB数据库培训文档

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

联系我们

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

微信号:itpux-com

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