これは、トランザクションの「ロック」がないことによる問題を示すデモです。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' } ]
]
これは正しいですか、一般的にどのように機能しますか? 主に、実際の実装ではレコードごとにいくつのバージョンが作成されますか? 一度に 2 つ以上のバージョンを作成できますか? もしそうなら、それは一般的にどのような状況で起こりますか? タイムスタンプはどのように機能しますか? ウィキページのタイムスタンプについて読みましたが、実装方法が実際には登録されていません。また、増加するトランザクション ID。つまり、基本的にこれら 3 つの部分 (バージョン管理、タイムスタンプ、およびトランザクション ID) がどのように組み合わされるかです。
JavaScript でのタイムスタンプとバージョン管理のある種のシミュレーションを探しているので、実装レベルの大まかな概算のようなものでありながら、一般的な概念を高いレベルで理解していることを確認できます。MVCC とは何かを知り、いくつかの論文を読むだけでは、MVCC の実装方法を知るには十分ではありません。
私の例では、トランザクション中に2 つのバージョンのレコードしか存在しません。それ以上必要な場合があるかどうかはわかりません。また、タイムスタンプを挿入する方法がわかりません。