这是一个演示,演示了没有事务“锁定”的问题。它使用 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 个版本的记录。我不确定在某些情况下您是否需要更多。而且我不确定如何插入时间戳。