事务概念
一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其他命令插入,不许加塞。
事务的作用
一个队列中,一次性、顺序性、排他性的执行一系列命令。
事务常用命令
multi
标记一个事务块的开始
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) "v1"
discard
取消事务,放弃执行事务块内的所有命令
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> discard
OK
127.0.0.1:6379> get k1
(nil)
exec
执行所有事务块内的命令
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) "v1"
事务的原子性
Redis单条命令是保证原子性的,但是Redis事务并不能保证原子性;所有的命令在事务中并不会立即执行,只会在执行事务的时候才会执行,所以Redis事务没有事务隔离级别的概念
编译时异常
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
# 随便写的,此时会报错误不存在这个命令;但是并没有说事务停止了
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\
QUEUED
127.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> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
# k1值是字符串类型所以无法自增,但是并没有提示错误
127.0.0.1:6379(TX)> incr k1
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) OK
4) (error) ERR value is not an integer or out of range
127.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 100
OK
# 花出去的钱
127.0.0.1:6379> set out 0
OK
# 监视money
127.0.0.1:6379> watch money
OK
# 启动事务
127.0.0.1:6379> multi
OK
# 余额-20
127.0.0.1:6379(TX)> DECRby money 20
QUEUED
# out+20
127.0.0.1:6379(TX)> INCRBY out 20
QUEUED
# 执行事务
127.0.0.1:6379(TX)> EXEC
1) (integer) 80
2) (integer) 20
结果正常
多线程操作
客户端1
# 余额
127.0.0.1:6379> set money 100
OK
# 花出去的钱
127.0.0.1:6379> set out 0
OK
# 监视money
127.0.0.1:6379> watch money
OK
# 启动事务
127.0.0.1:6379> multi
OK
# 余额-20
127.0.0.1:6379(TX)> DECRby money 20
QUEUED
# out+20
127.0.0.1:6379(TX)> INCRBY out 20
QUEUED
注意:这个时候并没有执行事务
客户端2
127.0.0.1:6379> WATCH money
OK
127.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> unwatch
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> INCRBY out 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 180
2) (integer) 20