事务的基础概念
- 关于事务最常见的例子就是银行转账,A 账户给 B 账户转账一个亿 (T1),买一块地盖房子;在这种交易的过程中,有几个问题值得思考:
- 如何同时保证上述交易中,A 账户 总金额减少一个亿,B 账户 总金额增加一个亿?
A
- A 账户 如果同时在和 C 账户 交易(T2),如何让这两笔交易互不影响?
I
- 如果交易完成时数据库突然崩溃,如何保证交易数据成功保存在数据库中?
D
- 如何在支持大量交易的同时,保证数据的合法性(没有钱凭空产生或消失)?
C
- 如何同时保证上述交易中,A 账户 总金额减少一个亿,B 账户 总金额增加一个亿?
- 要保证交易正常可靠地进行,数据库就得解决上面的四个问题,这也就是事务诞生的背景,它能解决上面的四个问题,并拥有四大特性:
原子性
(Atomicity):事务要么全部完成,要么全部取消,如果事务崩溃,状态回到事务之前(事务回滚);确保不管交易过程中发生了什么意外状况(服务器崩溃、网络中断等),不能出现 A 账户 少了一个亿,但 B 账户 没到帐,A 和 B 账户 的金额变动要么同时成功,要么同时失败;隔离性
(Isolation):如果 2 个事务 T1 和 T2 同时运行,事务 T1 和 T2 最终的结果是相同的,不管 T1 和 T2 谁先结束;如果 A 在转账 1 亿 给 B(T1),同时 C 又在转账 3 亿 给 A(T2),不管 T1 和 T2 谁先执行完毕,最终结果必须是 A 账户增加 2 亿 而不是 3 亿 ,B 增加 1 亿 ,C 减少 3 亿;持久性
(Durability):一旦事务提交,不管发生什么(崩溃或者出错),数据要保存在数据库中;确保如果 T1 刚刚提交,数据库就发生崩溃,T1 执行的结果依然会保持在数据库中;一致性
(Consistency):只有合法的数据才能写入数据库;确保钱不会在系统内凭空产生或消失,依赖原子性和隔离性;
- 如果,几个互不知晓的事务在同时修改同一份数据,那么很容易出现后完成的事务覆盖了前面的事务的结果,导致不一致;事务在最终提交之前都有可能会回滚,撤销所有修改;
Redis 中的事务
Redis 保证了一个事务中的所有命令要么都执行,要么都不执行;
- 如果在发送 EXEC 命令前客户端掉线了,则 Redis 会清空事务队列,事务中的所有命令都不会执行;
- 而一旦客户端发送了 EXEC 命令,所有的命令就都会被执行,即使此后客户端断线也没关系,因为 Redis 中已经记录了所有要执行的命令;
除此之外 Redis 的事务还能保证一个事务内的命令依次执行而不被其它命令插入:试想客户端 A 需要执行几条命令,同时客户端 B 发送了一条命令,如果不使用事务,则客户端 B 的命令可能会插入到客户端 A 的几条命令中执行,如果不希望发送这种情况,也可以使用事务;
-
Redis 中提供了以下三个命令来处理事务
# 标记一个事务块的开始 # 事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性(atomic)地执行 MULTI # 执行所有事务块内的命令 EXEC # 取消事务,放弃执行事务块内的所有命令 DISCARD
-
示例代码:
# 初始化 Jack 有 10块钱,Rose 有 20 块钱 SET Jack 10 SET Rose 20 # 开启事务 MULTI # Jack 给 Rose 转账 5 块钱 # 事务块内的多条命令会按照先后顺序被放进一个队列当中 # 返回 QUEUED 表示这两条命令已经进入等待执行的事务队列中了 DECRBY Jack 5 INCRBY Rose 5 # 执行所有事务块内的命令 EXEC # 将等待执行的事务队列中的所有命令按照发送的顺序依次执行 # 返回值就是这些命令的返回值组成的列表,返回值顺序和命令的顺序相同 # 1) (integer) 5 # 2) (integer) 25
事务中的错误处理
语法错误
:命令不存在或命令参数的个数不对;
运行错误
:
- 命令执行时出现的错误,比如使用散列类型的命令操作集合类型的键,这种错误在实际执行之前 Redis 是无法发现的,所以在事务里这样的命令是会被 Redis 接受并执行的;
- 如果事务里的一条命令出现了运行错误,事务里其它的命令依然会继续执行;
- Redis 事务没有关系数据库事务提供的回滚(rollback)功能,为此开发者必须在事务执行出错后自己收拾剩下的摊子(将数据库复原回事务执行前的状态等);
- 不过由于 Redis 不支持回滚功能,也使得 Redis 在事务上可以保持简洁和快速,此外回顾刚才提到的会导致事务执行失败的两种错误,其中语法错误完全可以在开发时找出并解决,另外如果能够很好的规划数据库的使用,是不会出现如命令与数据类型不匹配这样的运行时错误的;
MULTI
# key 为字符串类型
SET key 1
# 对字符串进行了集合操作,可见虽然 SADD key 2 出现了错误,但是 SET key 3 依然执行了
SADD key 2
SET key 3
EXEC
事务中的 WATCH 命令
-
WATCH 定义:监视一个或多个 key ,如果在事务执行之前 key 被其他命令所改动,那么事务将被打断:
WATCH key [key ...] # 手动取消 WATCH 命令对所有 key 的监视,需要再执行 UNWATCH # 自动取消 WATCH 命令对所有 key 的监视,在执行 WATCH 之后 执行 EXEC 命令或 DISCARD 命令 UNWATCH
-
来一个生活中的例子比较好理解(未使用 watch):
BATCHBATCH# 假设我的银行卡有 100 元 SET balance 100 # 第一个终端:我自己在消费 # 开启事务 MULTI # 拿了瓶水 DECRBY balance 3 # 拿了包烟 DECRBY balance 20 # 当女朋友结完账之后,我恰好也挑选完去结账,账户为负数,这是不对的 EXEC
# 第二个终端:女朋友消费 # 如果我在消费的时候,女朋友也使用我的银行卡消费了 # 开启事务 MULTI # 买了 10 斤苹果,直接刷了 100 DECRBY balance 100 # 最后结账,最后还剩 0 元 EXEC
-
针对上面的场景,使用 watch 之后解决:
BATCHBATCH# 假设我的银行卡有 100 元 SET balance 100 # 第一个终端:我自己在消费 # 监听 balance,如果在事务执行之前 balance 被其他命令所改动,那么事务将被打断 WATCH balance # 开启事务 MULTI # 拿了瓶水 DECRBY balance 3 # 拿了包烟 DECRBY balance 20 # 当女朋友结完账之后,我恰好也挑选完去结账 # (nil),事务执行之前 balancebalance 被改动,事务被打断,执行失败 EXEC
# 第二个终端:女朋友消费 # 如果我在消费的时候,女朋友也使用我的银行卡消费了 # 开启事务 MULTI # 买了 10 斤苹果,直接刷了 100 DECRBY balance 100 # 最后结账,最后还剩 0 元 EXEC
Redis👉 过期时间
上一篇