Version: Next

Redis事务

Redis单挑命令保证原子性,但是redis的事务不保证原子性

Redis事务的本质:

  • 一次性:一组命令的集合
  • 顺序性:会形成队列顺序执行
  • 排他性:在事务执行过程中,不受其他影响
-----
set
set
set
-----
caution

redis事务没有隔离级别的概念,所有的命令在事务中并没有被直接执行,只有发起执行命令的时候才会执行

正常执行事务

Redis事务的三个阶段:

  • 开启事务——multi
  • 命令入队
  • 执行事务——exec

开启事务

一旦执行了multi指令开启事务,接下来的指令就会被放到队列里

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379>

执行exec指令将队列中的指令一次性执行

127.0.0.1:6379> exec
1) OK
2) OK
3) "v2"
4) OK

入队列时指令并没有执行,只有执行exec指令时队列中的指令才被真正执行了

放弃事务discard

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get k4
(nil)

编译型异常

  • 在一组事务中,有一步操作存在语法错误,是编译期就可以发现的错误,则整个事务都不会执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> exec
(error) EXECABORT Transaction descarded because of parevious errors
127.0.0.1:6379> get k1
(nil)

运行型异常

  • 对字符串执行自动加1
  • 只有错误语句失败了,事务中的其他命令依然可以执行成功,因此说Redis的事务不保证原子性
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range
2) OK
3) "v2"
127.0.0.1:6379>

Redis实现乐观锁

悲观锁

无论做什么都会加锁

乐观锁

认为不会出什么问题,不会上锁

  • 在更新数据时,判断在此期间是否有人修改过数据

Redis监视测试——watch

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> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20

事务正常结束,没有异常

模拟多线程情况,多开Redis客户端

  • 线程1

    写一些操作,但并未提交事务

127.0.0.1:6379> watch money # 监视Money资源
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY out 10
QUEUED
127.0.0.1:6379>
  • 线程2

    第二条线程修改了共享资源的值

127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 1000
OK
127.0.0.1:6379>

此时线程1提交,watch指令会发现共享数据被修改过了,则事务提交失败

127.0.0.1:6379> exec
(nil)

此时,要unwatch释放监视,然后重新用watch对共享资源进行监视

127.0.0.1:6379> unwatch # 1.如果事务失败,就先unwatch放弃锁
OK
127.0.0.1:6379> watch money # 2.再次监视,获取新锁
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 1
QUEUED
127.0.0.1:6379> INCRBY money 1
QUEUED
127.0.0.1:6379> exec # 3.比对监控值是否发生变化,没变化就执行成功
1) (integer) 990
2) (integer) 1000