Redis中的事务
与关系型数据库事务的区别
Redis事务是指将多条命令加入队列,一次批量执行多条命令,每条命令会按顺序执行,事务执行过程中不会被其他客户端发来的命令所打断。也就是说,Redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
Redis事务和关系型数据库的事务不太一样,它不保证原子性,也没有隔离级别的概念。
事务不保证原子性,但是Redis命令本身是原子性的
- Redis事务没有隔离级别的概念
批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务里的查询要看到本事务的更新或其它事务的修改更新操作的问题。(Mysql里的事务的语句不是放入队列,而是直接执行) - Redis不保证原子性
Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。
Redis事务的运行流程
Redis事务相关命令
- Multi :开始事务
- Exec :执行事务中的所有命令,即提交;
- discard :放弃事务;和回滚不一样,Redis事务不支持回滚。
- WATCH:监视Key改变,用于实现乐观锁。如果监视的Key的值改变,事务最终会执行失败。
- UNWATCH:放弃监视。
没有隔离级别
当事务开启时,事务期间的命令并没有执行,而是加入队列,只有执行EXEC命令时,事务中的命令才会按照顺序执行,也就是说事务间就不会存在数据脏读、不可重复读、幻读的问题,因此就没有隔离级别。
事务不保证原子性
如上图所示,在通过EXEC执行事务时,其中命令执行失败不会影响到其他命令的执行,因此并没有保证同时成功和同时失败的原子操作,尽管这样,Redis事务中也没有提供回滚的支持
官方理由为:保证Redis的性能
- 事实上如果使用Redis命令语法错误,或是将命令运用在错误的数据类型键上(如对字符串进行加减乘除等),从而导致业务数据有问题,这种情况认为是编程导致的错误,应该在开发过程中解决,避免在生产环境中发生;
- 由于不用支持回滚功能,Redis内部简单化,而且还比较快;
多数事务失败是由语法错误或者数据结构类型错误导致的,语法错误说明在命令入队前就进行检测的,而类型错误是在执行时检测的,Redis为提升性能而采用这种简单的事务,这是不同于关系型数据库的,特别要注意区分。Redis之所以保持这样简易的事务,完全是为了保证高并发下的核心问题——性能。
语法错误(编译器错误)
在开启事务后,A的转出操作命令打成了DECRBYa
,最终会导致事务提交失败,所有命令都不会执行,A、B保留原值。
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> DECRBYa A 500
(error) ERR unknown command 'DECRBYa', with args beginning with: 'A' '500'
127.0.0.1:6379(TX)> INCRBY B 500
QUEUED
127.0.0.1:6379(TX)> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> mget A B
1) "1000"
2) "100"
127.0.0.1:6379>
类型错误(运行时错误)
在运行时检测类型错误,此时事务并没有回滚,而是跳过错误命令继续执行, 结果B值改变、A保留原值。
小结
- 当事务中命令语法使用错误时,最终会导致事务执行不成功,即事务内所有命令都不执行;
- 当事务中命令知识逻辑错误,就比如给字符串做加减乘除操作时,只能在执行过程中发现错误,这种事务执行中失败的命令不影响其他命令的执行。
使用WATCH实现乐观锁
WATCH通过监视指定Redis Key,如果没有改变,就执行成功,如果发现对应值发生改变,事务就会执行失败,如下图:
三种方式可以取消监视:
- 事务执行之后,不管是否执行成功还好是失败,都会取消对应的监视;
- 当监视的客户端断开连接时,也会取消监视;
- 可以手动UNWATCH取消所有Key的监视;