Redis中的事务和乐观锁

简单的说一下事务,所谓事务有四个特性:原子性(atomicity),一致性(consistency),隔离性(isolation),持久性(durability)。

①原子性:一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。

②一致性: 事务必须是使数据从一个一致性状态变到另一个一致性状态,一致性与原子性是密切相关的。

③隔离性:一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

④持久性:指的是一个事务一旦提交,它对数据库中数据的改变就应该是永久性的,接下来的其他操作或故障不应该对其右任何影响。

Redis事务可以一次执行多个命令,并且带有三个重要的保证

        1)批量操作在发送EXEC命令前被放入队列缓存。

        2)收到EXEC命令后进入事务执行,事务中任意命令执行失败,其余命令依然被执行

        3)在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

一个事务从开始到执行会经历三个阶段:开始事务,命令入队,执行事务。

Redis事务的相关命令如下:

  • MULTI:标识一个事务的开启,即开启事务;
  • EXEC:执行事务中的所有命令,即提交;
  • DISCARD:放弃事务;和回滚不一样,Redis事务不支持回滚。
  • WATCH:监视Key改变,用于实现乐观锁。如果监视的Key的值改变,事务最终会执行失败。
  • UNWATCH:放弃监视。

Redis事务和关系型数据库的事务不太一样,它不保证原子性,也没有隔离级别的概念。Redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。Redis单条命令式保存原子性的,但是事务不能保证原子性。

①正常执行事务

127.0.0.1:6379> multi    #开启事务
OK
127.0.0.1:6379> set name notebook    #添加数据
QUEUED
127.0.0.1:6379> set age 23    #添加数据
QUEUED
127.0.0.1:6379> set high 181    #添加数据
QUEUED
127.0.0.1:6379> exec    #执行事务
1) OK
2) OK
3) OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
3) "high"
127.0.0.1:6379> get name    #获取数据成功,证明事务执行成功
"notebook"
127.0.0.1:6379> get age
"23"
127.0.0.1:6379> get high
"181"
127.0.0.1:6379>

②放弃事务

127.0.0.1:6379> multi    #开启事务
OK
127.0.0.1:6379> set name notebook    #添加数据
QUEUED
127.0.0.1:6379> set age 23    #添加数据
QUEUED
127.0.0.1:6379> discard    #放弃事务
OK
127.0.0.1:6379> keys *    #查询数据,发现放弃事务以后不会执行事务里面的添加操作
(empty list or set)
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379>

③编译时异常,代码有问题,或者命令有问题,所有的命令都不会被执行

127.0.0.1:6379> multi    #开启事务
OK
127.0.0.1:6379> set name local    #添加数据
QUEUED
127.0.0.1:6379> set age 23    #添加数据
QUEUED
127.0.0.1:6379> getset name    #输入一个错误的命令,这时候已经报错了,但是这个还是进入了事务的队列当中
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set high 181    #添加数据
QUEUED
127.0.0.1:6379> exec    #执行事务,报错,并且所有的命令都不会执行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> keys *    #获取数据为空,证明没有执行
(empty list or set)
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379>

④运行时异常,除了语法错误不会被执行且抛出异常后,其他的正确命令可以正常执行

127.0.0.1:6379> multi    #开启事务
OK
127.0.0.1:6379> set name local    #添加数据
QUEUED
127.0.0.1:6379> incr name    #对添加的字符串数据就行自增操作
QUEUED
127.0.0.1:6379> set age 23    #添加数据
QUEUED
127.0.0.1:6379> get age    #获取数据
QUEUED
127.0.0.1:6379> exec    #执行事务。虽然对字符串数据进行自增操作报错了,但是其他的命令还是可以正常执行的
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) "23"
127.0.0.1:6379> get name
"local"
127.0.0.1:6379> get age
"23"
127.0.0.1:6379>

悲观锁: 什么时候都会出问题,所以一直监视着,没有执行当前步骤完成前,不让任何线程执行,十分浪费性能!一般不使用!
乐观锁: 只有更新数据的时候去判断一下,在此期间是否有人修改过被监视的这个数据,没有的话正常执行事务,反之执行失败!

下面说一下Redis实现乐观锁,大多数是基于数据版本(version)的记录机制实现的。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个”version”字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加1。此时,将提交数据的版本号与数据库表对应记录的当前版本号进行比对,如果提交的数据版本号大于数据库当前版本号,则予以更新,否则认为是过期数据。redis中可以使用watch命令会监视给定的key,当exec时候如果监视的key从调用watch后发生过变化,则整个事务会失败。也可以调用watch多次监视多个key。这样就可以对指定的key加乐观锁了。注意watch的key是对整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自动清除。当然了exec,discard,unwatch命令都会清除连接中的所有监视。

①watch(监视)

127.0.0.1:6379> set money 100    #添加金钱100
OK
127.0.0.1:6379> set cost 0    #添加花费0
OK
127.0.0.1:6379> watch money    #监控金钱
OK
127.0.0.1:6379> multi    #开启事务
OK
127.0.0.1:6379> decrby money 30    #金钱-30
QUEUED
127.0.0.1:6379> incrby cost 30    #花费+30
QUEUED
127.0.0.1:6379> exec    #执行事务,成功!这时候数据没有发生变动才可以成功
1) (integer) 70
2) (integer) 30
127.0.0.1:6379>

②多线程测试watch

#线程1
127.0.0.1:6379> set money 100    #添加金钱100
OK
127.0.0.1:6379> set cost 0    #添加花费0
OK
127.0.0.1:6379> watch money    #开启监视(乐观锁)
OK
127.0.0.1:6379> multi     #开启事务
OK
127.0.0.1:6379> decrby money 20    #金钱-20
QUEUED
127.0.0.1:6379> incrby cost 20    #花费+20
QUEUED
#这里先不要执行,先执行线程2来修改被监视的值
127.0.0.1:6379> exec    #执行报错,因为我们监视了money这个值,如果事务要对这个值进行操作前
#监视器会判断这个值是否正常,如果发生改变,事务执行失败!
(nil)
127.0.0.1:6379>


#线程2,在线程1事务执行前操作执行
127.0.0.1:6379> incrby money 20    #金钱+20
(integer) 120
127.0.0.1:6379>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值