P50 面试重灾区:Redis 事务与原子性的大坑
面试题:Redis 事务(MULTI/EXEC)支持原子性吗?和 MySQL 事务有什么区别?
1. Redis 事务是什么
text
MULTI -- 开启事务
SET k1 v1
INCR k2
EXEC -- 一起执行事务期间命令排队,EXEC 时按顺序执行。特点:
- 命令依次执行,不会被其他客户端命令插入(隔离性);
- 一条命令失败不影响其他命令执行(没有回滚!);
- Redis 事务是弱事务,和 MySQL 事务的 ACID 差别很大。
2. 大坑一:不支持回滚
- 运行时错误(如对字符串做 INCR)不会让事务回滚,其他命令照常执行;
- 只有入队时的语法错误会让整个事务失败(Redis 2.6.5+ 提前报错);
- 所以"原子性"指要么全不执行(语法错误/被 DISCARD),要么一条条执行完,不等于"要么全部成功要么全部失败"。
3. 大坑二:WATCH 才是乐观锁
text
WATCH key -- 监视 key
MULTI
...命令...
EXEC- WATCH 监视的 key 在事务执行前被其他客户端修改 → 事务被取消(返回 nil),实现"乐观锁"效果;
- 应用需要重试;
- 不 WATCH 的话,事务里是"先读后写"就会踩到并发覆盖问题。
4. 大坑三:Lua 脚本才是真正的原子操作
实际生产中要"判断 + 修改"原子执行(如扣库存),别用 MULTI/EXEC,用 Lua 脚本:
lua
-- Redis 会原子执行整个脚本(EVAL),期间不处理其他命令
if tonumber(redis.call('get', KEYS[1])) > 0 then
return redis.call('decr', KEYS[1])
end
return -1Redis 单线程执行模型下,Lua 脚本天然原子。
5. 面试对比表
| 特性 | Redis 事务 | MySQL 事务 |
|---|---|---|
| 隔离 | 命令不被插入(单线程排队) | MVCC + 锁 |
| 回滚 | 无 | 有(undo log) |
| 持久性 | 取决于 AOF 策略 | redo log + 刷盘 |
| 并发控制 | WATCH 乐观锁 | 锁/MVCC |
一句话总结
Redis 事务(MULTI/EXEC)只是"命令排队依次执行",不支持回滚、不做原子性保证,真正的原子操作要用 Lua 脚本,防并发覆盖要用 WATCH 或分布式锁;别把 Redis 事务当 MySQL 事务用。