在The Little Redis Book一书中,提到了redis中transaction的概念,也就是事务,transaction能够保证以下特性:
The commands will be executed in order
The commands will be executed as a single atomic operation (without another client’s command being executed halfway through)
That either all or none of the commands in the transaction will be executed
然后书中举了一个例子,使用get和set操作来实现一个incr操作,因为incr操作具有原子性,直接使用get和set操作不能保证原子性,因此需要使用transaction。书中首先举了一个transaction错误的使用方法:
redis.multi()
current = redis.get('powerlevel')
redis.set('powerlevel', current + 1)
redis.exec()
一开始我并不明白这段代码为什么是错误的,我觉得它给出的错误原因颇具误导性(当然也可能是我个人理解能力的问题了):
With the code above, we wouldnt be able to implement our own incr command since they are all executed together once exec is called.
一开始我觉得这里提到的“executed together”和上文的“executed in order”有矛盾,因此有些糊涂,直到我亲手执行了这段代码:
irb(main):001:0> require 'redis'
=> true
irb(main):002:0> redis=Redis.new(:host => "localhost", :port => 6379)
=> #<Redis client v3.3.0 for redis://localhost:6379/0>
irb(main):003:0> redis.multi()
=> "OK"
irb(main):004:0> current = redis.get('powerlevel')
=> "QUEUED"
irb(main):005:0> redis.set('powerlevel', current + 1)
TypeError: no implicit conversion of Fixnum into String
from (irb):5:in `+'
from (irb):5
from /usr/bin/irb:12:in `<main>'
irb(main):006:0> current
=> "QUEUED"
irb(main):007:0>
对于get指令,在ruby中进行调用时,由于并没有直接执行该指令,因此无法得到确切的值,因此只是将提示字符串”QUEUED”赋给了current,在后面的set指令中的current + 1操作自然就会出错了。一定要认识到的是:调用了exec()后,ruby不可能回过头来给current赋值,因为命令队列是在redis里实现的,而不是在ruby中实现的。所以我觉得如果作者给出的解释是“the get command is not executed the moment it is called,so the variable current won’t get the correct value”的话就更容易理解了。
至于后面提供的watch指令,就解决了current无法得到正确的值的问题了,因为get指令并不在transaction中,所以会被立即执行,将正确的结果赋给current。其实watch并没有保证下面的两个操作不会被中途打断,只是保证在被中途打断后停止操作(我可能把数据库事务中的原子性和编程中的原子操作混淆了,transaction中的原子性指的是All or Nothing,并不保证指编程中原子操作的不可中断性):
redis.watch('powerlevel')
current = redis.get('powerlevel')
redis.multi()
redis.set('powerlevel', current + 1)
redis.exec()