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
