事务transition
Redis事务的本质是一组命令的集合,一次执行多个指令,事务中所有命令都被序列化,其他客户端提交的命令请求不会插入到事务执行命令序列中
顺序、排他、一次性
单条命令是原子性执行的,但事务不保证原子性,且没有回滚,事务中任意命令执行失败,其余命令仍会被执行
- 开启事务(multi)
- 命令入队(。。。)
- 执行事务(exec)
- 取消事务(discard)
- 监视、加锁(watch)
- 取消监视、解锁(unwatch)
锁
乐观锁应该适用于读多写少的情况,悲观锁应该适用于写多读少的情况
悲观锁
- 为了避免其他人同时修改,直接对数据进行加锁以防止并发,修改数据前锁定,修改的方式被称为悲观并发控制 Pessimistic Concurrency Control 悲观、并发、控制
- 独占性、排他性
- 在整个数据处理过程中,数据处于锁定状态
- 线程操作数据,对数据添加排他锁
- 假设最坏的情况,每次都默认其他线程更改数据
- 传统数据库使用的几种加锁机制,都是在操作之前上锁
- 行锁
- 表锁
- 读锁
- 写锁
- 在Java中同步用synchronized关键字实现
- 悲观锁住要分共享锁和排他锁
- 共享锁 shared locks
- 读锁、S锁,多个事务对同一个数据可以共享一把锁,都能访问数据,只能读不能修改
- 排他锁 exclusive locks
- 写锁、X锁、不能与其他锁并存,一个事务获取了数据行的排他锁,其他事物就不能再获得该行的其他锁,获取排他锁的事物可以对数据行读取和修改
- 共享锁 shared locks
- 悲观并发控制 先取锁再访问 的保守策略,开销大,还增加死锁的概率,降低并发性,其他事务必须等待
乐观锁
- 假设数据一般情况不会造成冲突,在数据提交更新时正式对数据的冲突与否进行检测,发现了冲突,发出错误信息,让用户决定如何处理,适用于读操作多的场景,可以提高程序的吞吐量
- 为了避免数据库的幻读,业务处理时间过长,乐观锁不会刻意使用数据库本身的锁机制,而是一句数据本身来保证数据的正确性
- 实现方法
- CAS实现:java.util.concurrent.atomic 包下的原子变量
- 版本号控制,在数据表添加爱version字段,数据被修改时,version值+1,线程更新数据,同时读取version值,提交更新时,读取到的version值与当前数据库中的version值相等才更新,否则重试,直到更新成功
multi实现
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1 k2 v2
QUEUED
127.0.0.1:6379> getget k1
(error) ERR unknown command `getget`, with args beginning with: `k1`,
127.0.0.1:6379> 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> flushdb
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> mset k1 test k2 v2
QUEUED
127.0.0.1:6379> incr k1
QUEUED
127.0.0.1:6379> get k2
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) OK
2) (error) ERR value is not an integer or out of range
3) "v2"
4) OK
5) "v3"
127.0.0.1:6379> #出现执行中产生的错误,能正确入队,不会影响到其他命令的执行
watch监视实现
-
当有多个线程在操控redis的时候
-
被watch监视的key值如果发生改变,正在进行的事务将会失败
-
每次加锁后都要进行解锁,再加锁去重新获取最新的值
-
线程1
-
127.0.0.1:6379> watch key OK 127.0.0.1:6379> set key1 10 OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> incrby key 10 QUEUED 127.0.0.1:6379> incrby key1 20 QUEUED 127.0.0.1:6379> exec (nil) 127.0.0.1:6379> mget key key1 1) "30" 2) "10" 127.0.0.1:6379> #对key进行监控,key被阻止事务更新,key1在事务中也无法更新
-
-
线程2
-
127.0.0.1:6379> set key 30 OK 127.0.0.1:6379> #在线程1进行事务时,watch之后 exec之前,执行了set操作,会导致线程1的事务执行失败
-