mm_mysql 2.3.8 → 2.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +92 -386
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -22,7 +22,7 @@ class Mysql extends Base {
|
|
|
22
22
|
charset: 'utf8mb4',
|
|
23
23
|
timezone: '+08:00',
|
|
24
24
|
connect_timeout: 15000,
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
// 连接池优化配置(针对高并发写入场景)
|
|
27
27
|
connection_limit: 100, // 减少连接数,避免资源竞争
|
|
28
28
|
acquire_timeout: 30000, // 延长获取连接超时时间
|
|
@@ -30,11 +30,6 @@ class Mysql extends Base {
|
|
|
30
30
|
idle_timeout: 300000, // 延长空闲连接超时时间(5分钟)
|
|
31
31
|
max_idle: 30, // 减少最大空闲连接数
|
|
32
32
|
queue_timeout: 60000, // 队列超时时间
|
|
33
|
-
|
|
34
|
-
// 预处理语句缓存优化配置
|
|
35
|
-
statement_cache_size: 50, // 减少缓存大小,避免内存泄漏
|
|
36
|
-
max_prepared_statement_age: 300000, // 延长过期时间(5分钟)
|
|
37
|
-
enable_statement_pooling: true // 启用预处理语句缓存
|
|
38
33
|
};
|
|
39
34
|
|
|
40
35
|
/**
|
|
@@ -45,130 +40,14 @@ class Mysql extends Base {
|
|
|
45
40
|
super(config);
|
|
46
41
|
this._pool = null;
|
|
47
42
|
// 移除单连接支持,统一使用连接池
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
|
|
50
44
|
// 错误信息
|
|
51
45
|
this.error = null;
|
|
52
46
|
// 最近执行的SQL语句
|
|
53
47
|
this.sql = '';
|
|
54
|
-
|
|
55
|
-
// 预处理语句缓存
|
|
56
|
-
this._prepared_stmts = new Map();
|
|
57
|
-
this._stmt_cache_size = this.config.statement_cache_size || 100;
|
|
58
|
-
this._cache_hits = 0;
|
|
59
|
-
this._cache_misses = 0;
|
|
60
|
-
|
|
61
|
-
// 启动定期清理任务
|
|
62
|
-
this._startCleanupTask();
|
|
63
48
|
}
|
|
64
49
|
}
|
|
65
50
|
|
|
66
|
-
/**
|
|
67
|
-
* 启动预处理语句清理任务
|
|
68
|
-
* @private
|
|
69
|
-
*/
|
|
70
|
-
Mysql.prototype._startCleanupTask = function() {
|
|
71
|
-
if (this._cleanup_interval) {
|
|
72
|
-
clearInterval(this._cleanup_interval);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
this._cleanup_interval = setInterval(() => {
|
|
76
|
-
this._cleanupExpiredStatements();
|
|
77
|
-
}, 60000); // 每分钟清理一次
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* 清理过期预处理语句
|
|
82
|
-
* @private
|
|
83
|
-
*/
|
|
84
|
-
Mysql.prototype._cleanupExpiredStatements = function() {
|
|
85
|
-
const now = Date.now();
|
|
86
|
-
const max_age = this.config.max_prepared_statement_age || 300000;
|
|
87
|
-
|
|
88
|
-
for (const [key, statement_info] of this._prepared_stmts) {
|
|
89
|
-
if (now - statement_info.last_used > max_age) {
|
|
90
|
-
// 异步关闭语句,不阻塞主线程
|
|
91
|
-
if (statement_info.statement && typeof statement_info.statement.close === 'function') {
|
|
92
|
-
statement_info.statement.close().catch(() => {});
|
|
93
|
-
}
|
|
94
|
-
this._prepared_stmts.delete(key);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* 生成预处理语句缓存键
|
|
101
|
-
* @private
|
|
102
|
-
* @param {string} sql - SQL语句
|
|
103
|
-
* @returns {string} 缓存键
|
|
104
|
-
*/
|
|
105
|
-
Mysql.prototype._getStatementCacheKey = function(sql) {
|
|
106
|
-
// 简化SQL语句,移除多余空格和换行,提高缓存命中率
|
|
107
|
-
return sql.trim().replace(/\s+/g, ' ').toLowerCase();
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* 缓存预处理语句
|
|
112
|
-
* @private
|
|
113
|
-
* @param {string} key - 缓存键
|
|
114
|
-
* @param {object} statement - 预处理语句对象
|
|
115
|
-
*/
|
|
116
|
-
Mysql.prototype._cacheStatement = function(key, statement) {
|
|
117
|
-
if (this._prepared_stmts.size >= this._stmt_cache_size) {
|
|
118
|
-
// 移除最久未使用的语句
|
|
119
|
-
const oldest_key = this._findOldestStatement();
|
|
120
|
-
if (oldest_key) {
|
|
121
|
-
const oldest_statement = this._prepared_stmts.get(oldest_key);
|
|
122
|
-
if (oldest_statement.statement && typeof oldest_statement.statement.close === 'function') {
|
|
123
|
-
oldest_statement.statement.close().catch(() => {});
|
|
124
|
-
}
|
|
125
|
-
this._prepared_stmts.delete(oldest_key);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
this._prepared_stmts.set(key, {
|
|
130
|
-
statement: statement,
|
|
131
|
-
last_used: Date.now(),
|
|
132
|
-
use_count: 0
|
|
133
|
-
});
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* 查找最久未使用的预处理语句
|
|
138
|
-
* @private
|
|
139
|
-
* @returns {string} 缓存键
|
|
140
|
-
*/
|
|
141
|
-
Mysql.prototype._findOldestStatement = function() {
|
|
142
|
-
let oldest_key = null;
|
|
143
|
-
let oldest_time = Date.now();
|
|
144
|
-
|
|
145
|
-
for (const [key, statement_info] of this._prepared_stmts) {
|
|
146
|
-
if (statement_info.last_used < oldest_time) {
|
|
147
|
-
oldest_time = statement_info.last_used;
|
|
148
|
-
oldest_key = key;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return oldest_key;
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* 获取预处理语句缓存统计信息
|
|
157
|
-
* @returns {object} 统计信息
|
|
158
|
-
*/
|
|
159
|
-
Mysql.prototype.getStatementStats = function() {
|
|
160
|
-
const total = this._cache_hits + this._cache_misses;
|
|
161
|
-
const hit_rate = total > 0 ? (this._cache_hits / total) * 100 : 0;
|
|
162
|
-
|
|
163
|
-
return {
|
|
164
|
-
cached_statements: this._prepared_stmts.size,
|
|
165
|
-
cache_hits: this._cache_hits,
|
|
166
|
-
cache_misses: this._cache_misses,
|
|
167
|
-
cache_hit_rate: hit_rate.toFixed(2) + '%',
|
|
168
|
-
max_cache_size: this._stmt_cache_size
|
|
169
|
-
};
|
|
170
|
-
};
|
|
171
|
-
|
|
172
51
|
/**
|
|
173
52
|
* 获取MySQL2支持的配置参数
|
|
174
53
|
* 根据连接类型(连接池或单连接)过滤参数,避免警告
|
|
@@ -177,7 +56,7 @@ Mysql.prototype.getStatementStats = function() {
|
|
|
177
56
|
*/
|
|
178
57
|
Mysql.prototype._getValidConfig = function (is_pool = false) {
|
|
179
58
|
// 基础连接参数(适用于所有连接类型)
|
|
180
|
-
|
|
59
|
+
let base_map = {
|
|
181
60
|
'host': 'host',
|
|
182
61
|
'port': 'port',
|
|
183
62
|
'user': 'user',
|
|
@@ -189,17 +68,17 @@ Mysql.prototype._getValidConfig = function (is_pool = false) {
|
|
|
189
68
|
};
|
|
190
69
|
|
|
191
70
|
// 连接池专用参数
|
|
192
|
-
|
|
71
|
+
let pool_map = {
|
|
193
72
|
'connection_limit': 'connectionLimit',
|
|
194
73
|
'wait_for_connections': 'waitForConnections',
|
|
195
74
|
'idle_timeout': 'idleTimeout',
|
|
196
75
|
'max_idle': 'maxIdle'
|
|
197
76
|
};
|
|
198
77
|
|
|
199
|
-
|
|
78
|
+
let config = {};
|
|
200
79
|
|
|
201
80
|
// 添加基础参数
|
|
202
|
-
for (
|
|
81
|
+
for (let key in base_map) {
|
|
203
82
|
if (this.config[key] !== undefined) {
|
|
204
83
|
config[base_map[key]] = this.config[key];
|
|
205
84
|
}
|
|
@@ -207,7 +86,7 @@ Mysql.prototype._getValidConfig = function (is_pool = false) {
|
|
|
207
86
|
|
|
208
87
|
// 如果是连接池,添加连接池专用参数
|
|
209
88
|
if (is_pool) {
|
|
210
|
-
for (
|
|
89
|
+
for (let key in pool_map) {
|
|
211
90
|
if (this.config[key] !== undefined) {
|
|
212
91
|
config[pool_map[key]] = this.config[key];
|
|
213
92
|
}
|
|
@@ -216,7 +95,7 @@ Mysql.prototype._getValidConfig = function (is_pool = false) {
|
|
|
216
95
|
// 如果是单连接,确保不包含任何连接池专用参数
|
|
217
96
|
else {
|
|
218
97
|
// 移除所有连接池专用参数,避免mysql2内部警告
|
|
219
|
-
for (
|
|
98
|
+
for (let key in pool_map) {
|
|
220
99
|
if (pool_map[key] in config) {
|
|
221
100
|
delete config[pool_map[key]];
|
|
222
101
|
}
|
|
@@ -231,87 +110,63 @@ Mysql.prototype._getValidConfig = function (is_pool = false) {
|
|
|
231
110
|
* @returns {Promise<boolean>}
|
|
232
111
|
* @throws {TypeError} 当配置参数无效时
|
|
233
112
|
*/
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
113
|
+
Mysql.prototype.open = async function () {
|
|
114
|
+
// 参数校验
|
|
115
|
+
if (!this.config || typeof this.config !== 'object') {
|
|
116
|
+
throw new TypeError('config must be object');
|
|
117
|
+
}
|
|
239
118
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
// 测试连接池连接
|
|
246
|
-
const conn = await this._pool.getConnection();
|
|
247
|
-
conn.release();
|
|
248
|
-
|
|
249
|
-
this.log('info', '数据库连接池创建成功');
|
|
250
|
-
return true;
|
|
251
|
-
} catch (error) {
|
|
252
|
-
this.log('error', '数据库连接池创建失败', error);
|
|
253
|
-
return false;
|
|
254
|
-
}
|
|
255
|
-
};
|
|
119
|
+
try {
|
|
120
|
+
// 统一使用连接池模式
|
|
121
|
+
let valid_config = this._getValidConfig(true);
|
|
122
|
+
this._pool = mysql.createPool(valid_config);
|
|
256
123
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
*/
|
|
261
|
-
Mysql.prototype.close = async function () {
|
|
262
|
-
try {
|
|
263
|
-
// 先关闭连接池,再清理预处理语句(避免连接状态问题)
|
|
264
|
-
if (this._pool) {
|
|
265
|
-
await this._pool.end();
|
|
266
|
-
this._pool = null;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// 连接池关闭后,预处理语句会自动失效,只需清理缓存
|
|
270
|
-
if (this._prepared_stmts) {
|
|
271
|
-
// 不再尝试关闭预处理语句,直接清空缓存
|
|
272
|
-
this._prepared_stmts.clear();
|
|
273
|
-
}
|
|
124
|
+
// 测试连接池连接
|
|
125
|
+
let conn = await this._pool.getConnection();
|
|
126
|
+
conn.release();
|
|
274
127
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
128
|
+
this.log('info', '数据库连接池创建成功');
|
|
129
|
+
return true;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
this.log('error', '数据库连接池创建失败', error);
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
282
135
|
|
|
283
136
|
/**
|
|
284
|
-
*
|
|
285
|
-
* @
|
|
286
|
-
* @param {boolean} is_pool_conn - 是否为连接池连接
|
|
287
|
-
* @returns {Promise<void>}
|
|
137
|
+
* 关闭数据库连接(统一使用连接池)
|
|
138
|
+
* @returns {Promise<boolean>}
|
|
288
139
|
*/
|
|
289
|
-
async function
|
|
290
|
-
if (!conn) return;
|
|
291
|
-
|
|
140
|
+
Mysql.prototype.close = async function () {
|
|
292
141
|
try {
|
|
293
|
-
|
|
294
|
-
|
|
142
|
+
// 先关闭连接池(避免连接状态问题)
|
|
143
|
+
if (this._pool) {
|
|
144
|
+
await this._pool.end();
|
|
145
|
+
this._pool = null;
|
|
295
146
|
}
|
|
296
|
-
|
|
297
|
-
|
|
147
|
+
|
|
148
|
+
this.log('info', '数据库连接池已关闭');
|
|
149
|
+
return true;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
this.log('error', '关闭连接池失败', error);
|
|
152
|
+
return false;
|
|
298
153
|
}
|
|
299
154
|
};
|
|
300
155
|
|
|
301
156
|
/**
|
|
302
|
-
*
|
|
157
|
+
* 安全释放数据库连接
|
|
303
158
|
* @param {object} conn - 数据库连接对象
|
|
304
159
|
* @returns {Promise<void>}
|
|
305
160
|
*/
|
|
306
|
-
|
|
161
|
+
async function releaseConn(conn) {
|
|
307
162
|
if (!conn) return;
|
|
308
|
-
|
|
163
|
+
|
|
309
164
|
try {
|
|
310
|
-
if (
|
|
165
|
+
if (typeof conn.release === 'function') {
|
|
311
166
|
await conn.release();
|
|
312
167
|
}
|
|
313
|
-
} catch (
|
|
314
|
-
|
|
168
|
+
} catch (release_err) {
|
|
169
|
+
$.log.error('释放连接失败', release_err);
|
|
315
170
|
}
|
|
316
171
|
};
|
|
317
172
|
|
|
@@ -320,24 +175,23 @@ Mysql.prototype._releaseConn = async function(conn) {
|
|
|
320
175
|
* @returns {Promise<object>}
|
|
321
176
|
* @throws {TypeError} 当连接池不存在时
|
|
322
177
|
*/
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
178
|
+
Mysql.prototype.getConn = async function () {
|
|
179
|
+
// 参数校验
|
|
180
|
+
if (!this._pool) {
|
|
181
|
+
await this.open();
|
|
182
|
+
}
|
|
328
183
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
};
|
|
184
|
+
try {
|
|
185
|
+
// 统一从连接池获取连接
|
|
186
|
+
return await this._pool.getConnection();
|
|
187
|
+
} catch (error) {
|
|
188
|
+
this.log('error', '从连接池获取连接失败', error);
|
|
189
|
+
throw error; // 重新抛出错误,让调用方处理
|
|
190
|
+
}
|
|
191
|
+
};
|
|
338
192
|
|
|
339
193
|
/**
|
|
340
|
-
* 执行SQL
|
|
194
|
+
* 执行SQL查询
|
|
341
195
|
* @param {string} sql - SQL语句
|
|
342
196
|
* @param {Array} params - 参数数组
|
|
343
197
|
* @returns {Promise<object>}
|
|
@@ -354,97 +208,23 @@ Mysql.prototype.run = async function (sql, params = []) {
|
|
|
354
208
|
|
|
355
209
|
this.error = null;
|
|
356
210
|
let conn = null;
|
|
357
|
-
let
|
|
358
|
-
let connection_released = false;
|
|
359
|
-
|
|
211
|
+
let ret = [];
|
|
360
212
|
try {
|
|
361
213
|
// 获取连接
|
|
362
214
|
conn = await this.getConn();
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
// 检查是否启用预处理语句缓存
|
|
366
|
-
if (this.config.enable_statement_pooling && params.length > 0) {
|
|
367
|
-
const cache_key = this._getStatementCacheKey(sql);
|
|
368
|
-
let statement_info = this._prepared_stmts.get(cache_key);
|
|
369
|
-
|
|
370
|
-
if (statement_info && statement_info.statement) {
|
|
371
|
-
// 使用缓存的预处理语句
|
|
372
|
-
this._cache_hits++;
|
|
373
|
-
statement_info.last_used = Date.now();
|
|
374
|
-
statement_info.use_count++;
|
|
375
|
-
|
|
376
|
-
// 预先检查连接状态,避免不必要的错误
|
|
377
|
-
if (!conn || conn._fatalError || conn._closed) {
|
|
378
|
-
conn = await this.getConn();
|
|
379
|
-
// 连接已更新,需要重新创建预处理语句
|
|
380
|
-
this._prepared_stmts.delete(cache_key);
|
|
381
|
-
const statement = await conn.prepare(sql);
|
|
382
|
-
this._cacheStatement(cache_key, statement);
|
|
383
|
-
const [rows] = await statement.execute(params);
|
|
384
|
-
return rows;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
try {
|
|
388
|
-
const [rows] = await statement_info.statement.execute(params);
|
|
389
|
-
return rows;
|
|
390
|
-
} catch (stmt_err) {
|
|
391
|
-
// 预处理语句可能已失效(连接关闭等),重新创建
|
|
392
|
-
this.log('warn', '预处理语句执行失败,重新创建', stmt_err);
|
|
393
|
-
this._prepared_stmts.delete(cache_key);
|
|
394
|
-
|
|
395
|
-
// 检查连接是否有效,如果无效则重新获取连接
|
|
396
|
-
if (!conn || conn._fatalError || conn._closed) {
|
|
397
|
-
conn = await this.getConn();
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// 重新创建预处理语句
|
|
401
|
-
const statement = await conn.prepare(sql);
|
|
402
|
-
this._cacheStatement(cache_key, statement);
|
|
403
|
-
|
|
404
|
-
const [rows] = await statement.execute(params);
|
|
405
|
-
return rows;
|
|
406
|
-
}
|
|
407
|
-
} else {
|
|
408
|
-
// 创建新的预处理语句并缓存
|
|
409
|
-
this._cache_misses++;
|
|
410
|
-
const statement = await conn.prepare(sql);
|
|
411
|
-
this._cacheStatement(cache_key, statement);
|
|
412
|
-
|
|
413
|
-
const [rows] = await statement.execute(params);
|
|
414
|
-
return rows;
|
|
415
|
-
}
|
|
416
|
-
} else {
|
|
417
|
-
// 不使用预处理语句缓存,直接查询
|
|
418
|
-
const [rows] = await conn.query(sql, params);
|
|
419
|
-
return rows;
|
|
420
|
-
}
|
|
215
|
+
let [rows] = await conn.query(sql, params);
|
|
216
|
+
ret = rows;
|
|
421
217
|
} catch (err) {
|
|
422
218
|
this.sql = err.sql;
|
|
423
219
|
this.error = {
|
|
424
220
|
code: err.errno,
|
|
425
221
|
message: err.sqlMessage
|
|
426
222
|
};
|
|
427
|
-
// 在错误情况下也要确保连接释放
|
|
428
|
-
try {
|
|
429
|
-
await safeReleaseConn(conn, is_pool_conn);
|
|
430
|
-
} catch (release_err) {
|
|
431
|
-
this.log('error', '释放连接失败', release_err);
|
|
432
|
-
}
|
|
433
|
-
connection_released = true;
|
|
434
|
-
|
|
435
|
-
// 重新抛出错误,让调用方处理
|
|
436
|
-
throw err;
|
|
437
|
-
} finally {
|
|
438
|
-
// 确保连接被释放,避免资源泄漏
|
|
439
|
-
if (!connection_released) {
|
|
440
|
-
try {
|
|
441
|
-
await safeReleaseConn(conn, is_pool_conn);
|
|
442
|
-
} catch (release_err) {
|
|
443
|
-
this.log('error', '释放连接失败', release_err);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
223
|
}
|
|
447
|
-
|
|
224
|
+
finally {
|
|
225
|
+
await releaseConn(conn);
|
|
226
|
+
}
|
|
227
|
+
return ret;
|
|
448
228
|
};
|
|
449
229
|
|
|
450
230
|
/**
|
|
@@ -464,70 +244,12 @@ Mysql.prototype.exec = async function (sql, params = []) {
|
|
|
464
244
|
}
|
|
465
245
|
this.error = null;
|
|
466
246
|
let conn = null;
|
|
467
|
-
let
|
|
468
|
-
let connection_released = false;
|
|
469
|
-
|
|
247
|
+
let ret = 0;
|
|
470
248
|
try {
|
|
471
249
|
// 获取连接
|
|
472
250
|
conn = await this.getConn();
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
// 检查是否启用预处理语句缓存
|
|
476
|
-
if (this.config.enable_statement_pooling && params.length > 0) {
|
|
477
|
-
const cache_key = this._getStatementCacheKey(sql);
|
|
478
|
-
let statement_info = this._prepared_stmts.get(cache_key);
|
|
479
|
-
|
|
480
|
-
if (statement_info && statement_info.statement) {
|
|
481
|
-
// 使用缓存的预处理语句
|
|
482
|
-
this._cache_hits++;
|
|
483
|
-
statement_info.last_used = Date.now();
|
|
484
|
-
statement_info.use_count++;
|
|
485
|
-
|
|
486
|
-
// 预先检查连接状态,避免不必要的错误
|
|
487
|
-
if (!conn || conn._fatalError || conn._closed) {
|
|
488
|
-
conn = await this.getConn();
|
|
489
|
-
// 连接已更新,需要重新创建预处理语句
|
|
490
|
-
this._prepared_stmts.delete(cache_key);
|
|
491
|
-
const statement = await conn.prepare(sql);
|
|
492
|
-
this._cacheStatement(cache_key, statement);
|
|
493
|
-
const [result] = await statement.execute(params);
|
|
494
|
-
return result.insertId || result.affectedRows || result.changedRows || 1;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
try {
|
|
498
|
-
const [result] = await statement_info.statement.execute(params);
|
|
499
|
-
return result.insertId || result.affectedRows || result.changedRows || 1;
|
|
500
|
-
} catch (stmt_err) {
|
|
501
|
-
// 预处理语句可能已失效(连接关闭等),重新创建
|
|
502
|
-
this.log('warn', '预处理语句执行失败,重新创建', stmt_err);
|
|
503
|
-
this._prepared_stmts.delete(cache_key);
|
|
504
|
-
|
|
505
|
-
// 检查连接是否有效,如果无效则重新获取连接
|
|
506
|
-
if (!conn || conn._fatalError || conn._closed) {
|
|
507
|
-
conn = await this.getConn();
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// 重新创建预处理语句
|
|
511
|
-
const statement = await conn.prepare(sql);
|
|
512
|
-
this._cacheStatement(cache_key, statement);
|
|
513
|
-
|
|
514
|
-
const [result] = await statement.execute(params);
|
|
515
|
-
return result.insertId || result.affectedRows || result.changedRows || 1;
|
|
516
|
-
}
|
|
517
|
-
} else {
|
|
518
|
-
// 创建新的预处理语句并缓存
|
|
519
|
-
this._cache_misses++;
|
|
520
|
-
const statement = await conn.prepare(sql);
|
|
521
|
-
this._cacheStatement(cache_key, statement);
|
|
522
|
-
|
|
523
|
-
const [result] = await statement.execute(params);
|
|
524
|
-
return result.insertId || result.affectedRows || result.changedRows || 1;
|
|
525
|
-
}
|
|
526
|
-
} else {
|
|
527
|
-
// 对于写入语句,直接执行(不使用预处理语句缓存)
|
|
528
|
-
const [result] = await conn.execute(sql, params);
|
|
529
|
-
return result.insertId || result.affectedRows || result.changedRows || 1;
|
|
530
|
-
}
|
|
251
|
+
let [result] = await conn.execute(sql, params);
|
|
252
|
+
ret = result.insertId || result.affectedRows || result.changedRows || 1;
|
|
531
253
|
} catch (error) {
|
|
532
254
|
this.log('error', 'SQL执行失败', error);
|
|
533
255
|
this.sql = error.sql;
|
|
@@ -535,27 +257,11 @@ Mysql.prototype.exec = async function (sql, params = []) {
|
|
|
535
257
|
code: error.errno,
|
|
536
258
|
message: error.sqlMessage
|
|
537
259
|
};
|
|
538
|
-
// 在错误情况下也要确保连接释放
|
|
539
|
-
try {
|
|
540
|
-
await safeReleaseConn(conn, is_pool_conn);
|
|
541
|
-
} catch (release_err) {
|
|
542
|
-
this.log('error', '释放连接失败', release_err);
|
|
543
|
-
}
|
|
544
|
-
connection_released = true;
|
|
545
|
-
|
|
546
|
-
// 重新抛出错误,让调用方处理
|
|
547
|
-
throw error;
|
|
548
|
-
} finally {
|
|
549
|
-
// 确保连接被释放,避免资源泄漏
|
|
550
|
-
if (!connection_released) {
|
|
551
|
-
try {
|
|
552
|
-
await safeReleaseConn(conn, is_pool_conn);
|
|
553
|
-
} catch (release_err) {
|
|
554
|
-
this.log('error', '释放连接失败', release_err);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
260
|
}
|
|
558
|
-
|
|
261
|
+
finally {
|
|
262
|
+
await releaseConn(conn);
|
|
263
|
+
}
|
|
264
|
+
return ret;
|
|
559
265
|
};
|
|
560
266
|
|
|
561
267
|
/**
|
|
@@ -563,32 +269,32 @@ Mysql.prototype.exec = async function (sql, params = []) {
|
|
|
563
269
|
* @returns {Promise<object>} 事务连接对象
|
|
564
270
|
*/
|
|
565
271
|
Mysql.prototype.beginTransaction = async function () {
|
|
566
|
-
|
|
567
|
-
|
|
272
|
+
let conn = await this.getConn();
|
|
273
|
+
|
|
568
274
|
try {
|
|
569
275
|
await conn.beginTransaction();
|
|
570
|
-
|
|
276
|
+
|
|
571
277
|
let ended = false;
|
|
572
|
-
|
|
278
|
+
|
|
573
279
|
return {
|
|
574
280
|
connection: conn,
|
|
575
281
|
commit: async () => {
|
|
576
282
|
if (ended) return;
|
|
577
283
|
await conn.commit();
|
|
578
284
|
ended = true;
|
|
579
|
-
await
|
|
285
|
+
await releaseConn(conn);
|
|
580
286
|
},
|
|
581
287
|
rollback: async () => {
|
|
582
288
|
if (ended) return;
|
|
583
289
|
await conn.rollback();
|
|
584
290
|
ended = true;
|
|
585
|
-
await
|
|
291
|
+
await releaseConn(conn);
|
|
586
292
|
},
|
|
587
293
|
_is_ended: () => ended
|
|
588
294
|
};
|
|
589
295
|
} catch (error) {
|
|
590
296
|
// 如果事务开始失败,确保连接被释放
|
|
591
|
-
await
|
|
297
|
+
await releaseConn(conn);
|
|
592
298
|
this.log('error', '事务开始失败', error);
|
|
593
299
|
throw error;
|
|
594
300
|
}
|
|
@@ -605,16 +311,16 @@ Mysql.prototype.transaction = async function (callback) {
|
|
|
605
311
|
throw new TypeError('callback must be function');
|
|
606
312
|
}
|
|
607
313
|
|
|
608
|
-
|
|
609
|
-
|
|
314
|
+
let transaction = await this.beginTransaction();
|
|
315
|
+
|
|
610
316
|
try {
|
|
611
|
-
|
|
612
|
-
|
|
317
|
+
let result = await callback(transaction);
|
|
318
|
+
|
|
613
319
|
// 如果事务未结束,自动提交
|
|
614
320
|
if (!transaction._is_ended()) {
|
|
615
321
|
await transaction.commit();
|
|
616
322
|
}
|
|
617
|
-
|
|
323
|
+
|
|
618
324
|
return result;
|
|
619
325
|
} catch (error) {
|
|
620
326
|
// 如果事务未结束,自动回滚
|
|
@@ -623,7 +329,7 @@ Mysql.prototype.transaction = async function (callback) {
|
|
|
623
329
|
// 简化错误处理,避免频繁日志
|
|
624
330
|
});
|
|
625
331
|
}
|
|
626
|
-
|
|
332
|
+
|
|
627
333
|
throw error;
|
|
628
334
|
}
|
|
629
335
|
};
|
|
@@ -650,12 +356,12 @@ Mysql.prototype.read = async function (table, condition = {}, options = {}) {
|
|
|
650
356
|
|
|
651
357
|
try {
|
|
652
358
|
let sql = `SELECT * FROM ${table}`;
|
|
653
|
-
|
|
359
|
+
let params = [];
|
|
654
360
|
|
|
655
361
|
// 处理条件
|
|
656
362
|
if (Object.keys(condition).length > 0) {
|
|
657
|
-
|
|
658
|
-
for (
|
|
363
|
+
let whereClauses = [];
|
|
364
|
+
for (let [field, value] of Object.entries(condition)) {
|
|
659
365
|
whereClauses.push(`${field} = ?`);
|
|
660
366
|
params.push(value);
|
|
661
367
|
}
|
|
@@ -715,7 +421,7 @@ Mysql.prototype.healthCheck = async function () {
|
|
|
715
421
|
}
|
|
716
422
|
|
|
717
423
|
// 执行简单的查询来检查连接状态
|
|
718
|
-
|
|
424
|
+
let result = await this.run('SELECT 1 as health_check');
|
|
719
425
|
|
|
720
426
|
if (result && result.length > 0 && result[0].health_check === 1) {
|
|
721
427
|
return {
|
|
@@ -787,7 +493,7 @@ function _acquireLock(key) {
|
|
|
787
493
|
}
|
|
788
494
|
|
|
789
495
|
let release;
|
|
790
|
-
|
|
496
|
+
let lockPromise = $.pool.mysql._locks[key].then(() => {
|
|
791
497
|
return new Promise(resolve => {
|
|
792
498
|
release = resolve;
|
|
793
499
|
});
|