Redis 事务(transaction)提供了一种将多个命令打包为一个单一的原子操作执行的机制。在 Redis 中,事务确保一组命令按顺序执行,并且在事务执行期间不会被其他客户端的命令干扰。以下是对 Redis 事务的详细理解:
Redis 事务的基本概念
MULTI 和 EXEC:
- MULTI 命令用于开启一个事务。之后的所有命令会被放入一个事务队列中,而不是立即执行。
- EXEC 命令用于执行所有被放入事务队列中的命令。所有命令会按顺序执行,并且在执行过程中不会被其他命令插入或打断。
命令的原子性:
- 在 Redis事务中,每个命令自身是原子的,但事务内的多个命令并不保证整体的原子性。也就是说,如果在执行事务的过程中发生错误,已经执行的命令不会被回滚。
- 如果在事务队列中的某个命令执行失败,后续的命令仍会继续执行。
WATCH 和 UNWATCH:
- WATCH 命令用于监视一个或多个键。如果在事务执行之前这些键被其他客户端修改,则事务将被中止,EXEC 命令返回 nil,表示事务失败。
- UNWATCH 命令用于取消对所有键的监视。
以下是使用 Redis 事务的示例代码:
import redis
r = redis.StrictRedis(host='localhost', port=6379, db=0)
def run_transaction():
# 监视键 'account'
r.watch('account')
//watch 命令用于监视一个或多个键。如果在 exec 命令执行前,这些键的值被其他客户端修改,则事务会被中止。这里监视了 account 键,确保在事务执行前如果有其他客户端修改了 account,事务将不会执行。
try:
# 开启事务
r.multi()
//multi 命令用于开启一个事务。在调用 exec 命令前,所有命令都将被放入事务队列中,而不会立即执行。
# 操作命令
r.incr('account', 10)
r.decr('account', 5)
//incr 命令用于将 account 键的值增加 10。decr 命令用于将 account 键的值减少 5。这些命令在事务队列中排队等待执行。
# 执行事务
r.exec()
print("Transaction executed successfully")
//exec 命令用于执行所有在 multi 之后加入事务队列的命令。如果在 watch 之后到 exec 之前,任何被监视的键被修改,事务将失败并返回 nil。如果事务成功执行,打印 "Transaction executed successfully"。
except redis.WatchError:
print("Transaction aborted due to concurrent modification")
//WatchError 异常会在 exec 执行时检测到被监视的键被修改而抛出。捕获这个异常并打印 "Transaction aborted due to concurrent modification" 表示事务由于并发修改而中止。
# 设置初始值
r.set('account', 100)
# 运行事务
run_transaction()
//r.set('account', 100):将 account 键的初始值设置为 100。run_transaction():调用定义的事务函数 run_transaction 来执行事务操作。
# 查看结果
print(r.get('account').decode('utf-8'))
Redis 事务的特性
串行化执行:
- 事务中的命令按顺序执行,确保执行的顺序与命令被发送的顺序一致。
隔离性:
- 在事务执行期间,其他客户端的命令不会插入到事务中,保证了事务的隔离性。
乐观锁:
- 通过 WATCH 实现的乐观锁机制,防止事务在执行过程中被其他客户端干扰。
无回滚:
- Redis 事务不支持回滚机制。如果事务中的某个命令执行失败,已经执行的命令不会被回滚。
事务的应用场景
- 原子操作:需要确保一组操作作为一个原子操作执行,例如银行转账。
- 复杂操作:涉及多个键的复杂操作需要确保一致性。
- 并发控制:通过 WATCH 实现乐观锁机制,避免并发修改导致的数据不一致。
Redis 事务为什么不支持回滚?
这样做的原因是因为回滚需要增加很多工作,而不支持回滚则可以保持简单、快速的特性。
Redis 事务的注意点有哪些?
- Redis 事务是不支持回滚的,不像 MySQL 的事务一样,要么都执行要么都不执行;
- Redis 服务端在执行事务的过程中,不会被其他客户端发送来的命令请求打断。直到事务命令 全部执行完毕才会执行其他客户端的命令。