redis事务
Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
一个事务从开始到执行会经历以下三个阶段:
1.开始事务(multi)。
2.命令入队。
3.执行事务(exec)。
Redis 事务本质:一组命令的集合!
一个事务中的所有命令都会被序列化,在事务执行过程的中,会按
照顺序执行!
单个Redis命令的执行是原子性的,但Redis没有在事务上增加任何维持原子性的机制,所以Redis事务的执行并不是原子性。
事务可以理解为一个打包的批量执行脚本,但批量执行并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续指令不做。
Redis单条命令式保存原子性的,但是事务不保证原子性!
正常执行事务
MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> setbit week 0 1
QUEUED
127.0.0.1:6379> setbit week 1 0
QUEUED
127.0.0.1:6379> exec #执行事务
1) (integer) 0
2) (integer) 0
127.0.0.1:6379> getbit week 1 #验证
(integer) 0
127.0.0.1:6379>
取消事务 (discard)
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set name fanlan
QUEUED
127.0.0.1:6379> set name woshifanlan
QUEUED
127.0.0.1:6379> discard #取消事务
OK
127.0.0.1:6379> get name #队列中的事务不会被执行
(nil)
127.0.0.1:6379>
事务中可能出现的两种事务
1.编译时异常 (比如:java程序没运行时,报的错)
2.运行时异常 (比如:Java程序运行之前不报错,但是一但运行就报错,这就是运行时异常。例如:程序中3/0运行时就会报异常)
我们来看下redis事务中报这两个中异常时会发生什么?
编译时异常
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set name fanlan
QUEUED
127.0.0.1:6379> set name woshifanlan
QUEUED
127.0.0.1:6379> set name1 fanlan
QUEUED
127.0.0.1:6379> getset name fx
QUEUED
127.0.0.1:6379> set name #编译报错
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379> set name2 fanlan
QUEUED
127.0.0.1:6379> exec #执行事务包错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get name #获取事务包错
(nil)
127.0.0.1:6379>
由此可见:事务在编译时包错,事务所有命令都不会被执行
运行时异常
127.0.0.1:6379> set key fanlan #提前设置一个value
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set name fanlan
QUEUED
127.0.0.1:6379> set name1 woshifanlan
QUEUED
127.0.0.1:6379> incr key #(incr自增需要key是数值)编译时没有报错但是看结果
QUEUED
127.0.0.1:6379> set name2 fx
QUEUED
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> exec #执行事务(其中过包错)
1) OK
2) OK
3) (error) ERR value is not an integer or out of range
4) OK
5) "fanlan"
127.0.0.1:6379>
由此可见:事务队列中运行时有报错,但是不会影响其他命令的执行。
监控! Watch
乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改该数据,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这条数据,一般使用版本号机制进行判断。乐观锁适用于多读的应用类型,这样可以提高吞吐量。
悲观锁:顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改该数据,所以每次在拿数据的时候都会先上锁,这样别人想拿这个数据就会block阻塞直到它拿到锁传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁,让别人无法操作该数据
正常监控事务
127.0.0.1:6379> set fanlan 100 #设置key-vlune
OK
127.0.0.1:6379> set woshifanlan 0
OK
127.0.0.1:6379> watch fanlan #watch监控不能在开启事务之后执行
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> decrby fanlan 20 #泛滥花费20
QUEUED
127.0.0.1:6379> incrby woshifanlan 20 #我是泛滥商家收入20
QUEUED
127.0.0.1:6379> exec #执行事务
1) (integer) 80
2) (integer) 20
127.0.0.1:6379>
实现乐观锁
127.0.0.1:6379> set fanlan 100 #设置泛滥身上有100元
OK
127.0.0.1:6379> set woshifanlan 0 #商家
OK
127.0.0.1:6379> watch fanlan #监控泛滥
OK
另开一台redis-cli执行
127.0.0.1:6379> set fanlan 80 #出现个意外,泛滥拿的时候少拿了20元
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> decrby fanlan 20 #当支付的时候要给商家20元,但是泛滥记得是拿的是100元,于是发生了不愉快
QUEUED
127.0.0.1:6379> incrby woshifanlan 20 #商家有监控器发现泛滥并没有支付
QUEUED
127.0.0.1:6379> exec #执行事务失败(最后交易取消)
(nil)
如果修改失败,获取最新的值就好
unwatch 取消监管
泛滥回家后,发现原来自己少拿了20元,于是重返商店购买
另开一台redis-cli执行
127.0.0.1:6379> set fanlan 80
127.0.0.1:6379> unwatch #取消监管重新赋值
之后
购买成功!!!