一、Mysql事务回顾
mysql:ACID四个原则,要么同时成功,要么同时失败,原子性(关系型数据库)
mysql事务是必须满足4个条件(ACID):
原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。
- 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
- 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
- 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
二、Redis事务管理
1. 什么是事务?
一组命令操作,要么全部执行成功,要么全部不成功。
在redis中,事务的作用就是在一个队列中一次性、顺序性、排他性的执行一系列的命令。
Redis事务本质:一组命令的集合!一个事务中的所有命令都会序列化,在事务执行过程中,会按照顺序执行而不会被其他命令插入
redis中事务具有:一次性,顺序性,排他性!
2. 事务注意事项
- Redis单条命令是保持原子性的,但是Redis事务是不保持原子性的。
- Redis事务没有隔离级别的概念。
- 所有命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行。Exec
3. 事务执行
事务操作步骤:
1. 开启事务(multi)
2. 命令入队(其他命令...)
3. 执行事务(exec)
3.1 正常执行事务
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 #执行事务
1) OK
2) OK
3) "v2"
4) OK
当使用multi命令时,Redis就开启了事务操作,set、get等命令操作都会依次进入队列当中,注意此时并不会执行,当执行exec命令的时候,队列中的命令才会依次执行。当事务执行完毕后,队列中的命令全部消失(一次性),如果还需要执行其它事务,就需要重新再开启事务写入命令去执行。
3.2 放弃事务
如果在命令入列期间,因为某些原因想放弃本次事务操作,可以使用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 #取消事务 discard
OK
127.0.0.1:6379> get k4 #事务队列中命令都不会执行!
(nil)
当执行了discard命令后,本次事务取消,队列中的命令会被清空不执行。因此get操作获取不到值。
3.3 编译型异常
如代码有问题,命令有错,事务中所有命令都不会被执行
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> set k4 v4
QUEUED
127.0.0.1:6379> exec #执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
#所有命令都不会执行
127.0.0.1:6379> get k4
(nil)
127.0.0.1:6379> get k1
(nil)
上述代码中,由于getset命令错误,导致编译时异常,此次事务中所有命令都不会被执行,因此get命令时,值为空。
3.4 运行时异常(比如1/0)
如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常!
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> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range #虽然第一条命令报错,但依然正常执行成功
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"
4. Redis事务 VS MySql事务
MySQL中的事务要么全部执行成功,要么全部执行失败。
MySQL的事务是保证原子性操作的,但是Redis的事务是不保证原子性操作的。
5. 小结
- Redis事务中的所有命令会被序列化、按顺序执行,在执行的过程中不会被其他客户端发送来的命令打断
- 没有隔离级别的概念,队列中的命令在事务没有被提交之前不会被实际执行
- 不保证事务的原子性,redis中的一个事务中如果存在命令执行失败,那么其他命令依然会被执行,没有回滚机制
三、Redis实现乐观锁
1. 概念
- 悲观锁:其是基于一种悲观的态度类来防止一切数据冲突,它是以一种预防的姿态在修改数据之前把数据锁住,然后再对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作,直到前面一个人把锁释放后下一个人数据加锁才可对数据进行加锁,然后才可以对数据进行操作
- 乐观锁:其是对于数据冲突保持一种乐观态度,操作数据时不会对操作的数据进行加锁(这使得多个任务可以并行的对数据进行操作),只有到数据提交的时候才通过一种机制来验证数据是否存在冲突。可以使用版本号机制version和CAS算法实现。
2.Redis监控测试
2.1 正常执行
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> 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
2.2多线程修改值
多线程修改值,使用watch可以当做redis的乐观锁操作
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> exec #执行之前,另一个线程修改了money的值,这样会导致事务执行失败
(nil)
在执行本次事务中执行之前,另一个线程将money值进行修改,从而导致本次事务执行失败,
如果修改失败,获取最新的即可。
127.0.0.1:6379> watch 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> exec #事务执行失败
(nil)
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> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec #对比监视的值是否发送了变化,如果没有变化,那么可以执行成功,如果变化就执行失败
1) (integer) 60
2) (integer) 40
如果事务执行失败,那么首先unwatch命令进行解锁,再次重新获取最新的money,并且再次监控,最后exec执行后对结果进行对比,如果监视的值发生了变化,那就说明有其它线程正在对money进行修改,那么本次事务执行失败,如果该变量未发生变化,那么事务执行成功。