什么是事务?
事务操作是指:在一组操作中,有很多的命令,如果在这组操作时,有一个命令出现的了bug,那么这组这组操作会进行回滚,将环境还原成没有开始这组操作时的状态。在MySQL等关系型数据库中事务操作可能会出现这种结果,但是在redis则也可能出现其他的错误,那就是语法问题,通俗的讲就是在MySQL数据库中实现转账业务,一个人的金额增加,一个人减少,如果失败那么两人就不会有任何变化,但是在redis则不同,它也有可能出现,一个人的金额增加,但是另外的哪一个人金额并没有减少,下面会讲到。
事务操作支持多项命令,命令会被序列化,通俗的来讲就是命令会被放入一个队列中。
在事务操作过程中,执行步骤会按照队列中的顺序进行执行,队列之外的命令并不会插入执行事务操作执行命令的过程中。
单一的命令执行保持原子性,但是事务操作却不会保持原子性,就是要么成功的执行全部,要么则是白用功,回到故事的开端,重新开始。
在redis中执行事务
注意:开启事务后,会出现 TX 标志,此时所有的操作不会马上有结果,而是形成队列(QUEUED),待执行事务后,会将所有命令按顺序执行。
开启事务
127.0.0.1:6379> FLUSHALL 清空redis数据库
OK
127.0.0.1:6379> MULTI 开启事务操作
OK
127.0.0.1:6379(TX)> set key1 v1 命令,但不是直接执行,这项命令会被放到队列中
QUEUED
127.0.0.1:6379(TX)> set key1 v2
QUEUED
127.0.0.1:6379(TX)> set key2 v1
QUEUED
127.0.0.1:6379(TX)> get key2
QUEUED
127.0.0.1:6379(TX)> set key3 v3
QUEUED
127.0.0.1:6379(TX)> exec 执行队列中命令,就是前面的几行命令
1) OK 这是执行命令中的结果2) OK
3) OK
4) "v1"
5) OK
127.0.0.1:6379> keys * 命令执行成功
1) "key3"
2) "key2"
3) "key1"
127.0.0.1:6379>
放弃事务:DISCARD
127.0.0.1:6379> FLUSHALL
OK
127.0.0.1:6379> MULTI 开启事务
OK
127.0.0.1:6379(TX)> set key1 v1
QUEUED
127.0.0.1:6379(TX)> set key2 v2
QUEUED
127.0.0.1:6379(TX)> set key3 v3
QUEUED
127.0.0.1:6379(TX)> DISCARD 取消事务
OK
127.0.0.1:6379> keys * 事务并没有执行,取消成功
(empty array)
127.0.0.1:6379>
事务中的命令存在错误:编译性错误
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set key1 v1 创建一个key,加入到队列当中
QUEUED
127.0.0.1:6379(TX)> getset key1 错误的命令
(error) ERR wrong number of arguments for 'getset' command 报错
127.0.0.1:6379(TX)> set key2 v2 再创建一个key,加入到队列当中
QUEUED
127.0.0.1:6379(TX)> exec 执行事务操作
(error) EXECABORT Transaction discarded because of previous errors. 报错
127.0.0.1:6379> keys * 事务中的所有命令,都没有被执行
(empty array)
127.0.0.1:6379>
运行错误:加入队列中命令没有问题,但是执行命令可能存在一些语法性的问题,这时队列中的问题依旧会被执行,这个问题就是我在上面提到的转账业务,一个人会增加,但是另外一个人却不一定会减少。
127.0.0.1:6379> FLUSHALL
OK
127.0.0.1:6379> set key1 v1 创建一个key
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> INCR key1 让先前创建的key+1,但是因为创键的key1是字符串,所以此代码执行可能会报错,但是这行代码在语法是没有错误,它能正常的添加到队列中。
QUEUED 添加成功
127.0.0.1:6379(TX)> set key2 v2 创建几个其他的可以
QUEUED
127.0.0.1:6379(TX)> get key2
QUEUED
127.0.0.1:6379(TX)> EXEC 执行队列中的方法
1) (error) ERR value is not an integer or out of range 队列的第一条命令报错
2) OK 其他的命令正常执行
3) "v2"
127.0.0.1:6379>
监控:watch
乐观锁:
乐观主义者,认为数据并不会出现问题,并不会进行上锁,在更新数据时,要与监控时数据进行比较,如果有人更改数据,则数据并不会发生改变。
获取version
在执行操作命令时去比较version时候发生改变
悲观锁:
悲观主义者:认为数据无论在什么时候都有可能出现问题,什么数据都上锁
redis测试监控
正常情况下
127.0.0.1:6379> FLUSHALL
OK
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 对money进行监控
OK
127.0.0.1:6379> MULTI 开启事务
OK
127.0.0.1:6379(TX)> DECRBY money 10 减少10
QUEUED
127.0.0.1:6379(TX)> INCRBY out 10 增加10
QUEUED
127.0.0.1:6379(TX)> EXEC 执行队列命令,并不会发生任何问题,正常执行
1) (integer) 90
2) (integer) 10
127.0.0.1:6379>
在多线程环境里出现乐观锁时
线程一:
127.0.0.1:6379> WATCH money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> INCRBY out 50
QUEUED
127.0.0.1:6379(TX)> DECRBY money 50
QUEUED
127.0.0.1:6379(TX)> get money
QUEUED
当线程一正在执行事务时这是线程二介入修改了money的数据值
线程二:
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379>
那么线程一执行事务,会出现怎样的结果呢?
127.0.0.1:6379> WATCH money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> INCRBY out 50
QUEUED
127.0.0.1:6379(TX)> DECRBY money 50
QUEUED
127.0.0.1:6379(TX)> get money
QUEUED
127.0.0.1:6379(TX)> EXEC 队列中命令全都执行失败
(nil)
127.0.0.1:6379>
出现乐观锁问题,之后又如何执行完线程一的操作呢?
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(TX)> DECRBY money 50
QUEUED
127.0.0.1:6379(TX)> INCRBY out 50
QUEUED
127.0.0.1:6379(TX)> get money
QUEUED
127.0.0.1:6379(TX)> EXEC 执行,成功
1) (integer) 50
2) (integer) 60
3) "50"
127.0.0.1:6379>