mm_mysql 2.3.8 → 2.4.0
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 +96 -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>}
|
|
@@ -353,98 +207,26 @@ Mysql.prototype.run = async function (sql, params = []) {
|
|
|
353
207
|
}
|
|
354
208
|
|
|
355
209
|
this.error = null;
|
|
210
|
+
this.sql = '';
|
|
356
211
|
let conn = null;
|
|
357
|
-
let
|
|
358
|
-
let connection_released = false;
|
|
359
|
-
|
|
212
|
+
let ret = [];
|
|
360
213
|
try {
|
|
361
214
|
// 获取连接
|
|
362
215
|
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
|
-
}
|
|
216
|
+
this.sql = sql;
|
|
217
|
+
let [rows] = await conn.query(sql, params);
|
|
218
|
+
ret = rows;
|
|
421
219
|
} catch (err) {
|
|
422
220
|
this.sql = err.sql;
|
|
423
221
|
this.error = {
|
|
424
222
|
code: err.errno,
|
|
425
223
|
message: err.sqlMessage
|
|
426
224
|
};
|
|
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
225
|
}
|
|
447
|
-
|
|
226
|
+
finally {
|
|
227
|
+
await releaseConn(conn);
|
|
228
|
+
}
|
|
229
|
+
return ret;
|
|
448
230
|
};
|
|
449
231
|
|
|
450
232
|
/**
|
|
@@ -463,71 +245,15 @@ Mysql.prototype.exec = async function (sql, params = []) {
|
|
|
463
245
|
throw new TypeError('params must be array');
|
|
464
246
|
}
|
|
465
247
|
this.error = null;
|
|
248
|
+
this.sql = '';
|
|
466
249
|
let conn = null;
|
|
467
|
-
let
|
|
468
|
-
let connection_released = false;
|
|
469
|
-
|
|
250
|
+
let ret = 0;
|
|
470
251
|
try {
|
|
471
252
|
// 获取连接
|
|
472
253
|
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
|
-
}
|
|
254
|
+
this.sql = sql;
|
|
255
|
+
let [result] = await conn.execute(sql, params);
|
|
256
|
+
ret = result.insertId || result.affectedRows || result.changedRows || 1;
|
|
531
257
|
} catch (error) {
|
|
532
258
|
this.log('error', 'SQL执行失败', error);
|
|
533
259
|
this.sql = error.sql;
|
|
@@ -535,27 +261,11 @@ Mysql.prototype.exec = async function (sql, params = []) {
|
|
|
535
261
|
code: error.errno,
|
|
536
262
|
message: error.sqlMessage
|
|
537
263
|
};
|
|
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
264
|
}
|
|
558
|
-
|
|
265
|
+
finally {
|
|
266
|
+
await releaseConn(conn);
|
|
267
|
+
}
|
|
268
|
+
return ret;
|
|
559
269
|
};
|
|
560
270
|
|
|
561
271
|
/**
|
|
@@ -563,32 +273,32 @@ Mysql.prototype.exec = async function (sql, params = []) {
|
|
|
563
273
|
* @returns {Promise<object>} 事务连接对象
|
|
564
274
|
*/
|
|
565
275
|
Mysql.prototype.beginTransaction = async function () {
|
|
566
|
-
|
|
567
|
-
|
|
276
|
+
let conn = await this.getConn();
|
|
277
|
+
|
|
568
278
|
try {
|
|
569
279
|
await conn.beginTransaction();
|
|
570
|
-
|
|
280
|
+
|
|
571
281
|
let ended = false;
|
|
572
|
-
|
|
282
|
+
|
|
573
283
|
return {
|
|
574
284
|
connection: conn,
|
|
575
285
|
commit: async () => {
|
|
576
286
|
if (ended) return;
|
|
577
287
|
await conn.commit();
|
|
578
288
|
ended = true;
|
|
579
|
-
await
|
|
289
|
+
await releaseConn(conn);
|
|
580
290
|
},
|
|
581
291
|
rollback: async () => {
|
|
582
292
|
if (ended) return;
|
|
583
293
|
await conn.rollback();
|
|
584
294
|
ended = true;
|
|
585
|
-
await
|
|
295
|
+
await releaseConn(conn);
|
|
586
296
|
},
|
|
587
297
|
_is_ended: () => ended
|
|
588
298
|
};
|
|
589
299
|
} catch (error) {
|
|
590
300
|
// 如果事务开始失败,确保连接被释放
|
|
591
|
-
await
|
|
301
|
+
await releaseConn(conn);
|
|
592
302
|
this.log('error', '事务开始失败', error);
|
|
593
303
|
throw error;
|
|
594
304
|
}
|
|
@@ -605,16 +315,16 @@ Mysql.prototype.transaction = async function (callback) {
|
|
|
605
315
|
throw new TypeError('callback must be function');
|
|
606
316
|
}
|
|
607
317
|
|
|
608
|
-
|
|
609
|
-
|
|
318
|
+
let transaction = await this.beginTransaction();
|
|
319
|
+
|
|
610
320
|
try {
|
|
611
|
-
|
|
612
|
-
|
|
321
|
+
let result = await callback(transaction);
|
|
322
|
+
|
|
613
323
|
// 如果事务未结束,自动提交
|
|
614
324
|
if (!transaction._is_ended()) {
|
|
615
325
|
await transaction.commit();
|
|
616
326
|
}
|
|
617
|
-
|
|
327
|
+
|
|
618
328
|
return result;
|
|
619
329
|
} catch (error) {
|
|
620
330
|
// 如果事务未结束,自动回滚
|
|
@@ -623,7 +333,7 @@ Mysql.prototype.transaction = async function (callback) {
|
|
|
623
333
|
// 简化错误处理,避免频繁日志
|
|
624
334
|
});
|
|
625
335
|
}
|
|
626
|
-
|
|
336
|
+
|
|
627
337
|
throw error;
|
|
628
338
|
}
|
|
629
339
|
};
|
|
@@ -650,12 +360,12 @@ Mysql.prototype.read = async function (table, condition = {}, options = {}) {
|
|
|
650
360
|
|
|
651
361
|
try {
|
|
652
362
|
let sql = `SELECT * FROM ${table}`;
|
|
653
|
-
|
|
363
|
+
let params = [];
|
|
654
364
|
|
|
655
365
|
// 处理条件
|
|
656
366
|
if (Object.keys(condition).length > 0) {
|
|
657
|
-
|
|
658
|
-
for (
|
|
367
|
+
let whereClauses = [];
|
|
368
|
+
for (let [field, value] of Object.entries(condition)) {
|
|
659
369
|
whereClauses.push(`${field} = ?`);
|
|
660
370
|
params.push(value);
|
|
661
371
|
}
|
|
@@ -715,7 +425,7 @@ Mysql.prototype.healthCheck = async function () {
|
|
|
715
425
|
}
|
|
716
426
|
|
|
717
427
|
// 执行简单的查询来检查连接状态
|
|
718
|
-
|
|
428
|
+
let result = await this.run('SELECT 1 as health_check');
|
|
719
429
|
|
|
720
430
|
if (result && result.length > 0 && result[0].health_check === 1) {
|
|
721
431
|
return {
|
|
@@ -787,7 +497,7 @@ function _acquireLock(key) {
|
|
|
787
497
|
}
|
|
788
498
|
|
|
789
499
|
let release;
|
|
790
|
-
|
|
500
|
+
let lockPromise = $.pool.mysql._locks[key].then(() => {
|
|
791
501
|
return new Promise(resolve => {
|
|
792
502
|
release = resolve;
|
|
793
503
|
});
|