forked from TimelordUK/node-sqlserver-v8
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtxn.js
203 lines (181 loc) · 6.95 KB
/
txn.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
const mssql = require('msnodesqlv8')
const { GetConnection } = require('./get-connection')
const connectionString = new GetConnection().getConnection('linux')
const saString = new GetConnection().getConnection('sa')
console.log(`connectionString = ${connectionString}`)
const table = '_customer2'
async function create () {
const theConnection = await mssql.promises.open(connectionString)
const mgr = theConnection.tableMgr()
const res = await theConnection.promises.query('SELECT db_NAME() as [db]')
const builder = mgr.makeBuilder(table, res.first[0].db || 'node')
builder.addColumn('id').asInt().isIdentity(true, 1, 1).isPrimaryKey(1)
builder.addColumn('name').asVarChar(256)
builder.toTable()
console.log(builder.createTableSql)
await builder.drop()
await builder.create()
await theConnection.promises.close()
}
async function promised (i, connections, sql, mode, param) {
if (!sql || sql.length === 0) return
console.log(`iteration [${i}] mode = ${mode}: sql = ${sql}`)
const promises = connections.map(conn => param
? conn.promises.query(sql, [param])
: conn.promises.query(sql))
mode = mode || 'all'
switch (mode) {
case 'all': {
return await Promise.all(promises)
}
case 'settled': {
return await Promise.allSettled(promises)
}
}
}
async function dbLocks (connection) {
const sql = `SELECT resource_type,
resource_database_id,
request_mode, request_status FROM sys.dm_tran_locks
WHERE resource_database_id = DB_ID()
AND resource_associated_entity_id = OBJECT_ID(N'${table}')`
const res = await connection.promises.query(sql)
const modes = res.first ? res.first.map(x => x.request_mode) : []
console.log(`modes = ${modes.join(', ')}`)
return res.first ? res.first.length : 0
}
async function locks (saConnection) {
const lockCount = await dbLocks(saConnection)
console.log(`lock count ${lockCount}`)
}
async function showCounts (i, scenario, sql) {
const connections = scenario.connections
const res = await promised(i, connections, sql, 'settled')
const counts = res.map(r => r.value.first[0].count)
console.log(`iteration ${i} counts = ${counts.join(', ')}`)
}
async function once (i, scenario) {
const saConnection = scenario.saConnection
const connections = scenario.connections
const noLockCount = `select count(*) as count from ${table} with (nolock)`
try {
await locks(saConnection)
await promised(i, connections, scenario.beginSql, scenario.mode)
await promised(i, connections, scenario.insertSql, scenario.mode, 'foo')
await showCounts(i, scenario, noLockCount)
await locks(saConnection)
const to = setTimeout(() => locks(saConnection), 1000)
if (scenario.commitAllButOne) {
const [first, ...rest] = connections
console.log(`commit ${rest.length}`)
await promised(i, rest, scenario.commitSql, scenario.mode)
await promised(i, [first], scenario.selectSql(i), scenario.mode)
await promised(i, [first], scenario.commitSql, scenario.mode)
await promised(i, connections, scenario.selectSql(i), scenario.mode)
} else {
await promised(i, connections, scenario.selectSql(i), scenario.mode)
await promised(i, connections, scenario.commitSql, scenario.mode)
}
clearTimeout(to)
await locks(saConnection)
await showCounts(i, scenario, scenario.countSql)
} catch (e) {
console.log(e)
promised(i, connections, scenario.rollbackSql, scenario.mode).catch(() => {
console.log(e)
}).finally(() => locks(saConnection))
}
}
// with nolock clause will iterate but dangerous dirty reads
async function runner () {
const scenario1 = {
connections: 10,
iterations: 10000,
beginSql: 'begin tran',
insertSql: `INSERT INTO ${table} (name) OUTPUT INSERTED.id, INSERTED.name VALUES (?)`,
selectSql: (i) => `select top 10 * from ${table} with (nolock) order by id DESC`,
commitSql: 'commit tran',
rollbackSql: 'rollback tran',
countSql: `select count(*) as count from ${table} with (nolock)`
}
// 1 connection will iterate fine
const scenario2 = {
connections: 1,
iterations: 10000,
beginSql: 'begin tran',
insertSql: `INSERT INTO ${table} (name) OUTPUT INSERTED.id, INSERTED.name VALUES (?)`,
selectSql: (i) => `select top 10 * from ${table}`,
commitSql: 'commit tran',
rollbackSql: 'rollback tran',
countSql: `select count(*) as count from ${table}`
}
// this will deadlock
const scenario3 = {
mode: 'all',
connections: 3,
iterations: 5,
beginSql: 'begin tran',
insertSql: `INSERT INTO ${table} (name) OUTPUT INSERTED.id, INSERTED.name VALUES (?)`,
selectSql: (i) => `select * from ${table} where id = ${i}`,
commitSql: 'commit tran',
rollbackSql: 'rollback tran',
countSql: `select count(*) as count from ${table}`
}
// will iterate but again with dirty read clause to avoid deadlock
const scenario4 = {
connections: 3,
iterations: 50,
beginSql: 'begin tran',
insertSql: `INSERT INTO ${table} (name) OUTPUT INSERTED.id, INSERTED.name VALUES (?);`,
selectSql: (i) => `select top 10 * from ${table} with (READUNCOMMITTED);`,
commitSql: 'commit tran',
rollbackSql: 'rollback tran',
countSql: `select count(*) as count from ${table}`
}
// same as 4 but here we set isolation level on transaction
const scenario5 = {
mode: 'all',
connections: 3,
iterations: 10,
beginSql: 'SET TRANSACTION ISOLATION LEVEL read uncommitted; begin tran',
insertSql: `INSERT INTO ${table} (name) OUTPUT INSERTED.id, INSERTED.name VALUES (?);`,
selectSql: () => `select top 10 * from ${table};`,
commitSql: 'commit tran',
rollbackSql: 'rollback tran',
countSql: `select count(*) as count from ${table}`
}
const scenario7 = {
mode: 'all',
commitAllButOne: true,
connections: 10,
iterations: 500,
beginSql: 'begin tran',
insertSql: `INSERT INTO ${table} (name) OUTPUT INSERTED.id, INSERTED.name VALUES (?)`,
selectSql: (i) => `select top 10 * from ${table} order by id DESC`,
commitSql: 'commit tran',
rollbackSql: 'rollback tran',
countSql: `select count(*) as count from ${table}`
}
const scenario = scenario7
const saConnection = await mssql.promises.open(saString)
const opens = Array(scenario.connections).fill(0).map(() => mssql.promises.open(connectionString))
const connections = await Promise.all(opens)
const sp = await promised(0, connections, 'select @@SPID as spid', 'settled')
const spids = sp.map(s => s.value.first[0].spid)
console.log(`using pool ${scenario.connections} spids ${spids.join(', ')}`)
scenario.saConnection = saConnection
scenario.connections = connections
scenario.spids = spids
await create()
for (let i = 0; i < scenario.iterations; ++i) {
console.log(`[${i}] ...`)
await once(i, scenario)
console.log('')
}
const closes = connections.map(c => c.promises.close())
closes.push(saConnection.promises.close())
await Promise.allSettled(closes)
}
runner().then(() => {
console.log('done')
})