• 【译】StackExchange.Redis 中文文档(五)事务


    事务

    redis 的事务与SQL数据库中的事务不同。 完整的文档在这里,但换句话说:

    redis 的事务由位于 MULTIEXEC(或 DISCARD,用于回滚)之间的命令块组成。一旦遇到 MULTI,该连接上的命令将不会执行:它们会排队(并且调用者将获得对每个命令的回复 QUEUED)。 当遇到 EXEC 时,一次性提交。如果看到的是 DISCARD 而不是 EXEC,则一切都将被丢弃。由于事务内的命令已排队,因此你无法在事务内做决定。例如,在 SQL 数据库中,你可以执行以下操作(伪代码-仅用于说明):

    // assign a new unique id only if they don't already
    // have one, in a transaction to ensure no thread-races
    var newId = CreateNewUniqueID(); // optimistic
    using(var tran = conn.BeginTran())
    {
    	var cust = GetCustomer(conn, custId, tran);
    	var uniqueId = cust.UniqueID;
    	if(uniqueId == null)
    	{
    		cust.UniqueId = newId;
    		SaveCustomer(conn, cust, tran);
    	}
    	tran.Complete();
    }
    

    在 redis 如何做?

    这在 redis 事务中根本是不可能的:一旦事务打开,你就无法获取数据:你的操作已排队。幸运的是,还有另外两个命令可以帮助我们:WATCHUNWATCH

    WATCH {key} 告诉 redis 我们对指定的 key 感兴趣,以便进行事务操作。redis 将自动跟踪该 key,并且任何更改都会使事务回滚: EXECDISCARD 的作用相同(调用方可以检测到该错误并从头开始重试)。 因此,你可以的是:WATCH 一个 key,以通常的方式检查该 key 中的数据,然后 MULTI/EXEC 进行更改。如果在检查数据时发现实际上不需要事务,则可以使用 UNWATCH 来忘记所有监视的 key。注意,在 EXECDISCARD 期间,被监视的 key 也会被重置:

    WATCH {custKey}
    HEXISTS {custKey} "UniqueId"
    (check the reply, then either:)
    MULTI
    HSET {custKey} "UniqueId" {newId}
    EXEC
    (or, if we find there was already an unique-id:)
    UNWATCH
    

    这看起来很奇怪:在一个 MULTI/EXEC 操作内,如果其他人更改了 key(跟踪所有其他链接对{custKey}的更改),事务将中止。

    在 StackExchange.Redis 如何做?

    由于 StackExchange.Redis 使用多路复用方法,因此使情况更加复杂。我们不能简单地让并发调用者发出 WATCH / UNWATCH / MULTI / EXEC / DISCARD:它们会混杂在一起。因此,提供了一个附加的抽象(使得事情变得更容易正确处理):* constraints*。 Constraints 基本上是预先包装好的涉及 WATCH 的测试,某种测试以及对结果的检查。如果所有约束都通过,则发出 MULTI/EXEC;否则发出 UNWATCH。所有这些都以防止命令与其他调用者混合在一起的方式完成。 因此我们的示例变为:

    var newId = CreateNewId();
    var tran = db.CreateTransaction();
    tran.AddCondition(Condition.HashNotExists(custKey, "UniqueID"));
    tran.HashSetAsync(custKey, "UniqueID", newId);
    bool committed = tran.Execute();
    // ^^^ if true: it was applied; if false: it was rolled back
    

    请注意,从 CreateTransaction 返回的对象只能访问 async 方法:因为每个操作的结果只有在 Execute(或 ExecuteAsync)完成后才能知道。如果操作未开始,则所有 Task 都将标记为已取消,否则,在命令执行后,你可以正常获取每个结果。

    可用的 条件 集并不广泛,但涵盖了最常见的情况; 如果你还有其他条件需要查看,请与我联系(或者更好的方法是:提交 pull-request)。

    通过 When 进行内置操作

    还应注意,redis 已预见到许多常见情况(特别是:key/hash 的存在,如上所示),并且存在单操作原子命令。这些可以通过 When 参数来访问。因此,我们前面的示例也可以写为:

    var newId = CreateNewId();
    bool wasSet = db.HashSet(custKey, "UniqueID", newId, When.NotExists);
    

    (这里,When.NotExists 导致使用 HSETNX 命令,而不是 HSET

    Lua

    你还应该记住,redis 2.6及更高版本支持Lua脚本,这是一种多功能工具,可在服务器上作为单个原子单元执行多项操作。由于在 Lua 脚本中没有其他连接得到服务,因此它的行为很像事务,但是没有 MULTI/EXEC 等复杂性。这也避免了调用者和服务器之间的带宽和等待时间等问题,但是代价是它在脚本持续时间内垄断服务器。

    在 redis 层(并假设 HSETNX 不存在)可以实现为:

    EVAL "if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end" 1 {custKey} {newId}
    

    可以通过 StackExchange.Redis 实现:

    var wasSet = (bool) db.ScriptEvaluate(@"if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end",
            new RedisKey[] { custKey }, new RedisValue[] { newId });
    

    (请注意,来自 ScriptEvaluateScriptEvaluateAsync 的响应是可变的,具体取决于你的脚本;响应可以通过强制转换来解释:通常为 bool

    原文地址:Transactions

  • 相关阅读:
    课时15.DTD文档声明下(了解)
    Python-01 学习第一节
    常用数据库备份还原命令
    Oracle排除记录集
    存储过程分页语句
    TFS统计编码行数语句
    数据库所有表替换所有列的特定字符串
    MSSQL查询所有数据库表,指定数据库的字段、索引
    统计整个库所有表的记录数
    执​行​o​r​a​c​l​e​函​数​的​四​种​方​法
  • 原文地址:https://www.cnblogs.com/liang24/p/13847149.html
Copyright © 2020-2023  润新知