Redis的单条命令是保证原子性的,但是Redis事务不能保证原子性
Redis事务本质:一组命令的集合。
步骤:
- 开启事务
- 操作一组命令(所有命令放入一个队列)
- 执行事务
事务中每条命令都会被
序列化,执行过程中按顺序执行,不允许其他命令的干扰。
- 一致性
- 顺序性
- 排他性
- Redis事务没有隔离级别的概念
multi)exec)所有事务中的命令在加入时都没有被执行,直到提交时才会开始执行(exec)一次性完成。
案例:
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 k1
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> keys *
QUEUED
127.0.0.1:6379> exec # 执行事务
1) OK
2) OK
3) "v1"
4) OK
5) 1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379>
案例:
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> discard # 放弃事务
OK
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI # 当前未开启事务,无法执行
127.0.0.1:6379> get k1 # 被放弃事务中的命令并未执行
(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> key * # 遇到语法错误异常
(error) ERR unknown command `key`, with args beginning with: `*`,
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec # 提交事务失败
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1 # 所有命令都不会被执行
(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> incr k1 # 逻辑错误(对字符串无法进行增量)
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) (error) ERR value is not an integer or out of range # 运行时报错
4) "v2" # 其他命令正常执行
虽然中间有一条命令报错了,但是后面的指令依旧执行成功了,所以所Redis单条命令保证原子性,但是Redis不能保证原子性。
总结:
在修改数据之前把数据锁住,然后对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作,直到前面一个人把锁释放后,下一个人对数据加锁才能操作数据
一般数据库本身锁的机制都是基于悲观锁的机制来实现的
mysql 悲观锁的方法:
开启事务:
start transaction;
查询信息
select status from table where id = 1 for update # for update加锁
根据信息生成订单
insert into table111(id, goods_id) vlaues(null,1);
修改商品
update table set status = 2 where id =1;
提交信息
commit; # commit释放锁
Java悲观锁的实现
概念
实现
update table set goods='goods_1',version=version+1 where id = 1 and version = 0;
update table set goods='goods_1',version=version+1 where id = 1 and version = 0; # 判断version,是否有人修改过这个数据
如果不使用乐观锁控制,AB同时操作一条数据,会造成一条数据被修改了两次,产生脏数据,例如买票,只剩下一张票,结果AB都拿到了票,数据库发现票数为-1的情况。
使用watch key 监控指定数据,相当于乐观锁加锁
正常执行
127.0.0.1:6379> set money 100 # 设置余额:100
OK
127.0.0.1:6379> set use 0 # 支出使用: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 use 20
QUEUED
127.0.0.1:6379> exec # 监视值没有被被中途修改,事务正常执行
1) (integer) 80
2) (integer) 20
127.0.0.1:6379>
测试多线程修改值,使用watch可以当作redis的乐观锁操作( 相当于getversion )
启动另外一个客户端模拟插队线程
线程1:
# money: 100 use: 0
127.0.0.1:6379> watch money # 上锁
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby use 20
QUEUED
127.0.0.1:6379> # 此时事务还没有结束,还没执行事务
线程2插队:
127.0.0.1:6379> incrby money 500 # 修改了线程1中监视的money
(integer) 600
127.0.0.1:6379>
回到线程1,执行事务:
127.0.0.1:6379> exec # 执行之前,另外一条线程修改了money,这个时候就会导致事务执行失败
(nil) # 没有结果,说明事务执行失败
127.0.0.1:6379> get money # 线程2,修改生效
"600"
127.0.0.1:6379> get use # 线程1事务执行失败,数值没有修改
"0"
解锁(
unwatch)
unwatch进行解锁获取最新值,然后再加锁进行事务。
注意:
每次提交执行exec后都会自动释放锁,不管是否成功