IndexedDB 事务的冲突目的

Conflicting purposes of IndexedDB transactions

据我了解,将多个 IndexedDB 操作放在单个事务中而不是为每个操作使用唯一事务有三个不同的原因:

  1. 性能。如果您要对对象存储进行大量写入,如果它们发生在一个事务中,速度会快得多。
  2. 确保在继续之前写入数据。 Waiting for the “oncomplete” event is the only way to be sure that a subsequent IndexedDB query won’t return stale data.
  3. 执行一组原子数据库操作。基本上,“做所有这些事情,但如果其中一个失败,则全部回滚”。

#1 可以,大多数数据库都具有相同的特性。

#2 更独特一些,与#3 一起考虑时会出现问题。假设我有一些简单的函数,可以将某些内容写入数据库并在结束时运行回调:

function putWhatever(obj, cb) {
    var tx = db.transaction("whatever", "readwrite");
    tx.objectStore("whatever").put(obj);
    tx.oncomplete = function () { cb(); };
}

效果很好。但是现在,如果您想将该函数作为一组操作的一部分来调用,您希望以原子方式提交或失败,这是不可能的。你必须做这样的事情:

function putWhatever(tx, obj, cb) {
    tx.objectStore("whatever").put(obj).onsuccess = function () { cb(); };
}

第二个版本的函数与第一个版本有很大不同,因为回调在保证数据写入数据库之前运行。如果您尝试回读刚刚写入的对象,您可能会得到一个过时的值。

基本上,问题是您只能利用#2 或#3 之一。有时选择是明确的,但有时不是。这让我写出了可怕的代码,比如:

function putWhatever(tx, obj, cb) {
    if (tx === undefined) {
        tx = db.transaction("whatever", "readwrite");
        tx.objectStore("whatever").put(obj);
        tx.oncomplete = function () { cb(); };
    } else {
        tx.objectStore("whatever").put(obj).onsuccess = function () { cb(); };
    }
}

然而,这仍然不是通用的解决方案,在某些情况下可能会失败。

还有其他人遇到过这个问题吗?你如何解决?还是我只是以某种方式误解了事情?

以下只是意见,因为这看起来不像是 'one right answer' 问题。

首先,性能是无关紧要的考虑因素。完全避免这个因素,除非稍后的分析表明存在 material 问题。性能问题的可能性低得离谱。

其次,我更喜欢将请求组织成事务,只是为了保持完整性。诚信至上。我在这里定义的完整性仅仅意味着数据库在任何一个时间点都不包含冲突或不稳定的数据。本质上,数据库永远无法进入 'bad' 状态。例如,强加一条规则,即跨存储对象引用指向其他存储中的有效和现有对象(a.k.a。引用完整性),或者防止重复请求,例如双重 add/put/delete。显然,如果该应用程序类似于 credits/debits 帐户的银行应用程序,或者心脏病发作监控器应用程序,那么事情可能会变得非常糟糕。

我自己的经验让我相信涉及 indexedDB 的代码不容易出现传统的外观模式。我发现,就将请求组织到不同的包装函数而言,最有效的方法是围绕事务设计函数。我发现很少有 DRY violations,因为每个请求对于其事务上下文几乎总是唯一的。换句话说,虽然类似的 'put object' 请求可能出现在多个交易中,但鉴于其独立的上下文,它的行为非常不同,因此值得违反 DRY。

如果你按照请求路由执行函数,我不确定你为什么要检查事务参数是否未定义。让调用者创建函数,然后依次将其传递给请求。期望 tx 始终被定义并且不要过分热心地防范它。如果它从未被定义,那么 indexedDB 或您的调用函数中存在严重错误。

明确地说,类似于:

function doTransaction1(db, onComplete) {
  var tx = db.transaction(...);
  tx.onComplete = onComplete;
  doRequest1(tx);
  doRequest2(tx);
  doRequest3(tx);
}
function doRequest1(tx) {
  var store = tx.objectStore(...);
  // ...
}
// ...

如果请求不应该并行执行,而必须运行串联,那么这表明一个更大更困难的设计问题。