各位数据库爱好者们好!今天我们要深入探讨MySQL中确保数据一致性和完整性的两大核心机制——事务和锁 ️。事务就像数据库操作的"原子弹",要么全部成功,要么全部失败;而锁机制则是数据库的"交通警察",协调并发访问避免混乱。在当今高并发的应用环境中,理解这些机制至关重要!本教程将带你从理论到实践,全面掌握MySQL事务和锁的精髓,让你的数据库既能高效并发又能保持数据一致!
原子性就像数字支付,要么转账成功,要么完全回退 :
特点:
示例:
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;
-- 如果第二条语句失败,第一条也会自动回滚
COMMIT;
一致性就像会计的借贷平衡,数据必须永远满足预定义的规则 ⚖️:
特点:
示例:
-- 转账事务保证了总金额不变
-- 事务前:A(500) + B(500) = 1000
-- 事务后:A(400) + B(600) = 1000
隔离性就像独立办公室,每个事务感觉不到其他事务的存在 :
特点:
持久性就像刻在石头上的字,一旦提交就永久保存 :
特点:
隔离级别就像社交距离,决定事务间的"亲密程度" :
隔离级别 | 脏读 | 不可重复读 | 幻读 | 性能 |
---|---|---|---|---|
READ UNCOMMITTED | ❌ | ❌ | ❌ | ⚡⚡⚡⚡ |
READ COMMITTED | ✅ | ❌ | ❌ | ⚡⚡⚡ |
REPEATABLE READ (默认) | ✅ | ✅ | ❌ | ⚡⚡ |
SERIALIZABLE | ✅ | ✅ | ✅ | ⚡ |
脏读:读到其他事务未提交的数据 :
-- 事务A
START TRANSACTION;
UPDATE users SET age = 30 WHERE id = 1; -- 未提交
-- 事务B(READ UNCOMMITTED能读到A未提交的修改)
SELECT age FROM users WHERE id = 1; -- 结果为30
不可重复读:同一事务内两次读取结果不同 :
-- 事务A
START TRANSACTION;
SELECT age FROM users WHERE id = 1; -- 结果为25
-- 事务B
UPDATE users SET age = 30 WHERE id = 1;
COMMIT;
-- 事务A再次读取
SELECT age FROM users WHERE id = 1; -- 结果变为30
幻读:同一事务内两次查询返回的行数不同 :
-- 事务A
START TRANSACTION;
SELECT COUNT(*) FROM users WHERE age > 20; -- 返回10
-- 事务B
INSERT INTO users(name, age) VALUES('新用户', 25);
COMMIT;
-- 事务A再次查询
SELECT COUNT(*) FROM users WHERE age > 20; -- 返回11
查看当前隔离级别:
SELECT @@transaction_isolation;
-- 或(旧版本)
SELECT @@tx_isolation;
设置会话级隔离级别:
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
设置全局隔离级别(需重启生效):
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
事务控制就像开关,精确控制操作范围 :
开始事务:
START TRANSACTION;
-- 或
BEGIN;
提交事务:
COMMIT;
回滚事务:
ROLLBACK;
保存点(Savepoint):
START TRANSACTION;
INSERT INTO orders VALUES(...);
SAVEPOINT sp1;
UPDATE inventory SET ...;
-- 可以回滚到保存点
ROLLBACK TO sp1;
COMMIT;
银行转账经典案例 :
START TRANSACTION;
-- 检查账户余额是否充足
SELECT balance INTO @balance FROM accounts WHERE user_id = 1 FOR UPDATE;
IF @balance < 100 THEN
ROLLBACK;
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '余额不足';
END IF;
-- 执行转账
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;
-- 记录交易日志
INSERT INTO transaction_log(from_user, to_user, amount) VALUES(1, 2, 100);
COMMIT;
MySQL锁就像多层次的安保系统 :
锁类型 | 描述 | 兼容性 | 使用场景 |
---|---|---|---|
共享锁(S锁) | 允许其他事务读但不能写 | 与S锁兼容,与X锁不兼容 | SELECT … LOCK IN SHARE MODE |
排他锁(X锁) | 禁止其他事务读和写 | 与其他任何锁都不兼容 | SELECT … FOR UPDATE |
意向共享锁(IS) | 表示事务打算在表某些行加S锁 | 表级锁,提高冲突检测效率 | 自动添加 |
意向排他锁(IX) | 表示事务打算在表某些行加X锁 | 表级锁,提高冲突检测效率 | 自动添加 |
行级锁:
表级锁:
锁升级:
当行锁太多或涉及无索引列更新时,InnoDB可能升级为表锁
显式加锁:
-- 加共享锁(其他事务可读不可写)
SELECT * FROM accounts WHERE user_id = 1 LOCK IN SHARE MODE;
-- 加排他锁(其他事务不可读不可写)
SELECT * FROM accounts WHERE user_id = 1 FOR UPDATE;
隐式加锁:
死锁就像交通堵塞,多个事务互相等待 :
必要条件:
-- 事务A
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1; -- 锁住id=1
UPDATE accounts SET balance = balance + 100 WHERE user_id = 2; -- 等待B释放id=2
-- 事务B
START TRANSACTION;
UPDATE accounts SET balance = balance - 50 WHERE user_id = 2; -- 锁住id=2
UPDATE accounts SET balance = balance + 50 WHERE user_id = 1; -- 等待A释放id=1
MySQL自动检测:
避免死锁的最佳实践:
innodb_lock_wait_timeout=50
手动处理死锁:
-- 查看当前锁情况
SHOW ENGINE INNODB STATUS;
-- 查看锁等待
SELECT * FROM performance_schema.events_waits_current;
-- 终止造成死锁的事务
KILL [transaction_id];
间隙锁锁定索引记录间的"间隙" :
特点:
示例:
-- 锁定age在20-30之间的间隙,防止插入age=25的新记录
SELECT * FROM users WHERE age BETWEEN 20 AND 30 FOR UPDATE;
临键锁是InnoDB默认的行锁算法 :
特点:
悲观锁(默认):
SELECT ... FOR UPDATE
乐观锁:
乐观锁实现示例:
-- 添加version字段
ALTER TABLE products ADD version INT DEFAULT 0;
-- 更新时检查版本
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE product_id = 1001 AND version = 5;
-- 检查影响行数是否为1
查看当前运行事务:
SELECT * FROM information_schema.INNODB_TRX;
查看锁等待情况:
SELECT
r.trx_id waiting_trx_id,
r.trx_mysql_thread_id waiting_thread,
b.trx_id blocking_trx_id,
b.trx_mysql_thread_id blocking_thread
FROM information_schema.INNODB_LOCK_WAITS w
JOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_id
JOIN information_schema.INNODB_TRX r ON r.trx_id = w.requesting_trx_id;
InnoDB事务相关参数:
# 事务超时时间(秒)
innodb_lock_wait_timeout = 50
# 死锁检测(开启会消耗资源,高并发可考虑关闭)
innodb_deadlock_detect = ON
# 事务日志缓冲区大小
innodb_log_buffer_size = 16M
# 事务日志文件大小
innodb_log_file_size = 256M
事务设计:
锁优化:
监控与调优:
通过本教程,我们系统掌握了MySQL事务和锁机制的核心知识 :
关键收获:
下一步学习建议:
PS:如果你在学习过程中遇到问题,别慌!欢迎在评论区留言,我会尽力帮你解决!