Redis 的基本事务操作
Redis 事务
Redis 单条命令是保持原子性的,但是Redis 的事务没有原子性!!!
Redis 事务的本质: 一组命令的集合
一个事务中的所有命令都会被序列化,在事务执行的过程中,会按照顺序执行
----- 队列 set set set 执行 ---
事务的特点:
- 一次性
- 顺序性
- 排他性
- 执行一些列的命令
Redis 的事务没有隔离级别的概念
Redis 的事务:
- 开启事务(
multi
) - 命令入队(
......
) - 执行事务(
exec
) - 放弃事务(
discard
)
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> multi # 开启事务
OK
# 命令入队
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec # 执行事务
1) OK
2) OK
3) "v2"
4) OK
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> discard # 取消事务
OK
127.0.0.1:6379> get k4 # 事务队列中的命令都不会执行
(nil)
常见异常
- 编译型异常:
代码有问题,命令有错,事务中的所有命令都不会被执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> getset k3 # 错误命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> exec # 执行错误出错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5 # 所有命令都没有被执行
(nil)
- 运行时异常:
如果事务队列中存在语法性问题(e.g.1/0
),那么执行命令的时候,其他命令是可以正常执行的
127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1 # 会执行失败
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range # 虽然第一条命令报错了,但是依旧执行成功了
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"
Redis 事务与 MySQL 事务对比
MySQL 中事务的四大特性
MySQL 中事务有四大特性:ACID
- 原子性(Atomicity)
原子性是最基本的特性,一个事务内所有操作就是最小的一个操作单元,要么全部成功,要么全部失败 - 一致性(Consistency)
一个事务可以封装状态改变(除非是只读的);事务必须始终保持系统处于一致的状态,不管在任何给定的时间,并发事务有多少
如果一个操作触发辅助操作(级联、触发器),这些辅助操作也必须成功,否则该操作失败
如果系统是由多个节点组成的,一致性规定所有的变化必须传播到所有节点(多主复制);如果从站节点是异步更新的,那么就打破了一致性规则,系统成为 “最终一致性”
一个事务是数据状态的切换,当事务是并发多个时,系统也必须如同串行事务一样操作
大多数数据库管理系统选择(默认情况)是放宽一致性,以达到更好的并发性 - 隔离性(Isolation)
事务的隔离性,基于原子性和一致性,事务可以有多个原子包的形式并发执行,但是每个事务之间互不干扰 - 持久性(Durability)
当一个事务提交后,数据库的状态就永远地发生了改变;即使事务提交后,数据库发生异常,也不会因为异常导致事务提交失败
不同的事务命令
- MySQL
Begin
: 显式地开启一个事务Commit
: 提交事务,将对数据库进行的所有修改变成永久性Rollback
: 结束用户的事务,并撤销现在正在进行的未提交的修改 - redis
Multi
: 标记事务的开始Exec
: 执行事务的 commands 队列Discard
: 结束事务,并清除 commands 队列
不同的默认状态
- MySQL
MySQL 会默认开启事务,且缺省设置是自动提交,即每成功执行 sql,一个事务就会马上 commit,所以不能 rollback - redis
redis 默认不会开启事务,即 command 会立刻执行,而不会排队,并不支持 rollback
不同的使用方式
- MySQL
用 Begin、Rollback、Commit 显式开启并控制一个 新的 事务 Transaction
执行命令 set autocommit=0 ,用来禁止当前会话自动 commit,并控制默认开启事务 - redis
用 Multi、Exec、Discard,显式开启控制一个 Transaction
不同的实现原理
- MySQL
MySQL 实现事务是基于 undo/redo 日志
undo 记录修改前状态,rollback 基于 undo 日志实现
redo 记录修改后状态,commit 基于 redo 日志实现 - redis
redis 实现事务是基于 commands 队列
如果没有开启事务,command 将会被立即执行并返回执行结果,并直接写入磁盘
如果事务开启,command 不会被立即执行,而是排入队列,并返回排队状态;调用 exec 才会执行 commands 队列
redo 日志是 innodb 专有的,所以 innodb 支持事务
在 MySQL 中,无论是否开启事务,sql 都会被立即执行并返回执行结果,只是事务开启后执行后的状态记录在 redo 日志中,执行 commit 之后,数据才会被写入磁盘
redis 事务的错误
redis 的事务期间,可能会遇到两种命令错误:
一、在调用 exec 命令之前出现错误(command 排队失败)
例如,命令可能存在语法错误(参数数量错误、命令名称错误、、、),或者内存不足(如果服务器使用 maxmemory 指令做了内存限制)
二、在调用 exec 命令之后出现错误
例如,使用错误的值对某个 key 执行操作(如,对 String 值调用 List 操作)
exec 命令执行后发生的错误并不会被特殊对待:即使事务中的某些命令执行失败,其他命令仍会被正常执行
因为 redis 命令在事务执行时可能会失败,但是仍继续执行剩余命令而不是 rollback(事务回滚),所以 redis 自然也不会支持事务回滚
由于不必支持事务回滚,redis 内部简洁且更加高效
悲观锁和乐观锁
悲观锁
- 很悲观,认为什么时候都会出现问题,无论对数据做什么操作都会加锁
乐观锁
- 很乐观,认为什么时候都不会出现问题,所以不会对数据上锁
- 但是更新数据的时候会去判断一下,在此期间是否有人修改过这个数据
- 获取
version
- 更新的时候比较
version
正常执行
# 正常执行成功
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
测试多线程修改值,使用 watch 可以当做 redis 的乐观锁操作
127.0.0.1:6379> watch money # 监视 money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec # 执行前,另一个线程,修改了我们的值,这个时候,就会导致事务执行失败
(nil)
# 如果事务执行失败,就先解锁
127.0.0.1:6379> unwatch # 解锁之前的监视
OK
127.0.0.1:6379> watch money # 获取最新的值,再次监视
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec # 比对监视的值是否发生变化,如果没有发生变化 ,那么可以执行成功,否则执行失败
1) (integer) 990
2) (integer) 30
Jedis
Jedis 是 Redis 官方推荐的 Java 链接开发工具!使用 Java 操作 Redis 中间件!如果要使用 Java 操作 Redis,那么一定要对 Jedis 十分熟悉