事务概念
一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其他命令插入,不许加塞。
事务的作用
一个队列中,一次性、顺序性、排他性的执行一系列命令。
事务常用命令
multi
标记一个事务块的开始
127.0.0.1:6379> multiOK127.0.0.1:6379(TX)> set k1 v1QUEUED127.0.0.1:6379(TX)> set k2 v2QUEUED127.0.0.1:6379(TX)> get k1QUEUED127.0.0.1:6379(TX)> exec1) OK2) OK3) "v1"
discard
取消事务,放弃执行事务块内的所有命令
127.0.0.1:6379> multiOK127.0.0.1:6379(TX)> set k1 v1QUEUED127.0.0.1:6379(TX)> set k2 v2QUEUED127.0.0.1:6379(TX)> discardOK127.0.0.1:6379> get k1(nil)
exec
执行所有事务块内的命令
127.0.0.1:6379> multiOK127.0.0.1:6379(TX)> set k1 v1QUEUED127.0.0.1:6379(TX)> set k2 v2QUEUED127.0.0.1:6379(TX)> get k1QUEUED127.0.0.1:6379(TX)> exec1) OK2) OK3) "v1"
事务的原子性
Redis单条命令是保证原子性的,但是Redis事务并不能保证原子性;所有的命令在事务中并不会立即执行,只会在执行事务的时候才会执行,所以Redis事务没有事务隔离级别的概念
编译时异常
127.0.0.1:6379> multiOK127.0.0.1:6379(TX)> set k1 v1QUEUED127.0.0.1:6379(TX)> set k2 v2QUEUED127.0.0.1:6379(TX)> set k3 v3QUEUED# 随便写的,此时会报错误不存在这个命令;但是并没有说事务停止了127.0.0.1:6379(TX)> helloworld(error) ERR unknown command `helloworld`, with args beginning with:127.0.0.1:6379(TX)> set k4 v4\QUEUED127.0.0.1:6379(TX)> exec(error) EXECABORT Transaction discarded because of previous errors.127.0.0.1:6379> get k1(nil)
结果可知:事务中所有命令都不会被执行
运行时异常
127.0.0.1:6379> multiOK127.0.0.1:6379(TX)> set k1 v1QUEUED127.0.0.1:6379(TX)> set k2 v2QUEUED127.0.0.1:6379(TX)> set k3 v3QUEUED# k1值是字符串类型所以无法自增,但是并没有提示错误127.0.0.1:6379(TX)> incr k1QUEUED127.0.0.1:6379(TX)> exec1) OK2) OK3) OK4) (error) ERR value is not an integer or out of range127.0.0.1:6379> get k1"v1"127.0.0.1:6379> get k2"v2"127.0.0.1:6379> get k3"v3"
结果可知:出错的命令不会被执行,正常的命令还是会被执行
基于编译时异常和运行时异常的区别,可以更好的理解为什么说Redis单条命令是保证原子性的,但是Redis事务是不保证原子性的
Watch监控
- 乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量
- 悲观锁:每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会
block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁
单线程操作
# 余额127.0.0.1:6379> set money 100OK# 花出去的钱127.0.0.1:6379> set out 0OK# 监视money127.0.0.1:6379> watch moneyOK# 启动事务127.0.0.1:6379> multiOK# 余额-20127.0.0.1:6379(TX)> DECRby money 20QUEUED# out+20127.0.0.1:6379(TX)> INCRBY out 20QUEUED# 执行事务127.0.0.1:6379(TX)> EXEC1) (integer) 802) (integer) 20
结果正常
多线程操作
客户端1
# 余额127.0.0.1:6379> set money 100OK# 花出去的钱127.0.0.1:6379> set out 0OK# 监视money127.0.0.1:6379> watch moneyOK# 启动事务127.0.0.1:6379> multiOK# 余额-20127.0.0.1:6379(TX)> DECRby money 20QUEUED# out+20127.0.0.1:6379(TX)> INCRBY out 20QUEUED
注意:这个时候并没有执行事务
客户端2
127.0.0.1:6379> WATCH moneyOK127.0.0.1:6379> incrby money 100(integer) 200
回到客户端1
127.0.0.1:6379(TX)> exec(nil)
结果可知:使用
watch可以实现乐观锁的功能
UnWatch
接着上面的watch讲解,先解锁再获取最新的值进行操作
# 解锁127.0.0.1:6379> unwatchOK127.0.0.1:6379> multiOK127.0.0.1:6379(TX)> decrby money 20QUEUED127.0.0.1:6379(TX)> INCRBY out 20QUEUED127.0.0.1:6379(TX)> exec1) (integer) 1802) (integer) 20
