0

这是一个演示,演示了没有事务“锁定”的问题。它使用 setTimeout 模拟异步/并发。我从未用 C、Go 或 Rust 等语言处理过并发,所以我不太确定它在实现细节上是如何工作的,但我正在努力掌握MVCC的概念。

const db = {
  table1: {
    records: [
      { id: 1, name: 'foo', other: 'hello' },
      { id: 2, name: 'bar', other: 'world' },
    ]
  }
}

function readTable1(id) {
  return db.table1.records.find(x => x.id === id)
}

function writeTable1(id) {
  const record = readTable1(id)
  return new Promise((res, rej) => {
    console.log('transaction 1 start')
    setTimeout(() => {
      record.other = 'qwerty'

      setTimeout(() => {
        record.name = 'asdf'
        console.log('transaction 1 done')
        res()
      }, 1000)
    }, 1000)
  })
}

function wait(ms) {
  return new Promise((res) => setTimeout(res, ms))
}

async function test1() {
  writeTable1(1)
  console.log(readTable1(1))
  await wait(1100)
  console.log(readTable1(1))
  await wait(2200)
  console.log(readTable1(1))
}

test1()

它记录

transaction 1 start
{ id: 1, name: 'foo', other: 'hello' } // read
{ id: 1, name: 'foo', other: 'qwerty' } // read
transaction 1 done
{ id: 1, name: 'asdf', other: 'qwerty' } // read

在事务处理记录的中间,它改变了可以并发读取的真实记录。它没有锁,或者 MVCC 没有锁(使用多个版本的记录)。接下来我尝试实现我认为 MVCC 的工作方式,希望您能纠正我的理解。就是这样。

const db = {
  table1: {
    records: [
      [{ id: 1, name: 'foo', other: 'hello' }],
      [{ id: 2, name: 'bar', other: 'world' }],
    ]
  }
}

function readTable1(id) {
  const idx = db.table1.records.findIndex(x => x[0].id === id)
  return [idx, db.table1.records[idx][0]]
}

// this is a long transaction.
function writeTable1(id) {
  const [idx, record] = readTable1(id)
  // create a new version of record for transaction to act on.
  const newRecordVersion = {}
  Object.keys(record).forEach(key => newRecordVersion[key] = record[key])
  db.table1.records[idx].push(newRecordVersion)

  return new Promise((res, rej) => {
    console.log('transaction 2 start')
    setTimeout(() => {
      newRecordVersion.other = 'qwerty'

      setTimeout(() => {
        newRecordVersion.name = 'asdf'
        console.log('transaction 2 done')

        // now "commit" the changes
        commit()

        res();
      }, 1000)
    }, 1000)
  })

  function commit() {
    db.table1.records[idx].shift()
  }
}

function wait(ms) {
  return new Promise((res) => setTimeout(res, ms))
}

async function test1() {
  writeTable1(1)
  console.log(readTable1(1)[1])
  await wait(1100)
  console.log(readTable1(1)[1])
  await wait(2200)
  console.log(readTable1(1)[1])
  console.log(db.table1.records)
}

test1()

输出这个,这似乎是正确的。

transaction 2 start
{ id: 1, name: 'foo', other: 'hello' }
{ id: 1, name: 'foo', other: 'hello' }
transaction 2 done
{ id: 1, name: 'asdf', other: 'qwerty' }
[
  [ { id: 1, name: 'asdf', other: 'qwerty' } ],
  [ { id: 2, name: 'bar', other: 'world' } ]
]

这是正确的,通常它是如何工作的?主要是,在实际实现中每条记录创建了多少个版本?一次可以有两个以上的版本吗?如果是这样,一般来说在什么情况下会发生这种情况?时间戳是如何工作的?我在wiki 页面上阅读了有关时间戳的信息,但它并没有真正向我注册如何实现它。还有递增的事务 ID。所以基本上这三个部分是如何组合在一起的(版本控制、时间戳和事务 ID)。

我正在寻找某种对 JavaScript 中的时间戳和版本控制的模拟,因此我可以确保我在高层次上理解一般概念,但在某种实现级别的粗略近似。仅仅知道 MVCC 是什么并阅读几篇论文还不足以知道如何实现它。

在我的示例中,在一个事务期间只会有 2 个版本的记录。我不确定在某些情况下您是否需要更多。而且我不确定如何插入时间戳。

4

1 回答 1

0

简短的回答: “数据库”中的多版本并发控制涉及几个不同的事情;不同的供应商以几种不同的方式实现这些东西。

https://dba.stackexchange.com/questions/174791/does-sql-server-use-multiversion-concurrency-control-mvcc

SQL Server 真的在任何地方都实现了 MVCC

是的,从 SQL Server 2005 开始。

SQL Server 术语是“行版本控制隔离级别”。请参阅从Locking 和 Row Versioning开始的产品文档树。请特别注意,有两个单独的“MVCC”实现,使用行版本控制 (RCSI) 和快照隔离 (SI) 的读取提交隔离。

以及如何与 with (tablock, holdlock) 的想法相协调?

使用提示组合可以序列化对整个表的访问。它是可用的最少并发选项,因此使用这些提示应该非常少见。是否可以用 RCSI 或 SI 隔离代替特定用途取决于具体情况。如果您希望我们详细讨论该方面,您可以通过具体示例提出后续问题。

您可能还想阅读 关于 SQL Server 隔离级别的系列文章。

这是一个很大的话题,没有简短而简单的“一刀切”的回答。

至于“在 JavaScript 中模拟 MVCC”:

  • Javascript 本质上是单线程的;语言本身没有“线程”或“锁定”的概念。
  • “承诺”(如您所用)是“订购”异步回调的绝佳方式
  • 我们需要更多关于您想要完成的工作的详细信息。
于 2022-02-13T02:23:20.813 回答