@aetherframework/database 1.1.1 → 1.1.2
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/examples/mysql-test-pressure.js +1530 -0
- package/examples/test-direct.js +116 -0
- package/examples/transaction_example.js +127 -0
- package/package.json +3 -1
- package/src/DatabaseManager.js +565 -0
- package/src/core/ConnectionManager.js +351 -0
- package/src/core/DatabaseFactory.js +188 -0
- package/src/core/MongoQueryBuilder.js +576 -0
- package/src/core/PluginManager.js +968 -0
- package/src/core/QueryBuilder.js +4394 -0
- package/src/core/TransactionManager.js +40 -0
- package/src/drivers/clickhouse-driver.js +272 -0
- package/src/drivers/index.js +273 -0
- package/src/drivers/mongodb-driver.js +87 -0
- package/src/drivers/mssql-driver.js +117 -0
- package/src/drivers/mysql-driver.js +169 -0
- package/src/drivers/oracle-driver.js +101 -0
- package/src/drivers/postgres-driver.js +234 -0
- package/src/drivers/redis-driver.js +52 -0
- package/src/drivers/sqlite-driver.js +67 -0
- package/src/middleware/connection-pool.js +455 -0
- package/src/middleware/performance-monitor.js +652 -0
- package/src/middleware/query-cache.js +500 -0
- package/src/middleware/query-logger.js +262 -0
- package/src/plugins/AuditPlugin.js +447 -0
- package/src/plugins/BasePlugin.js +418 -0
- package/src/plugins/BatchOperationPlugin.js +165 -0
- package/src/plugins/CachePlugin.js +407 -0
- package/src/plugins/CtePlugin.js +523 -0
- package/src/plugins/DistributedPlugin.js +543 -0
- package/src/plugins/EncryptionPlugin.js +211 -0
- package/src/plugins/FullTextSearchPlugin.js +164 -0
- package/src/plugins/GeospatialPlugin.js +219 -0
- package/src/plugins/GraphQLPlugin.js +162 -0
- package/src/plugins/HookPlugin.js +211 -0
- package/src/plugins/JsonPlugin.js +366 -0
- package/src/plugins/OptimisticLockPlugin.js +374 -0
- package/src/plugins/PerformancePlugin.js +175 -0
- package/src/plugins/ResiliencePlugin.js +114 -0
- package/src/plugins/ShardingPlugin.js +227 -0
- package/src/plugins/SoftDeletePlugin.js +258 -0
- package/src/plugins/SyncPlugin.js +373 -0
- package/src/plugins/VersioningPlugin.js +314 -0
- package/src/plugins/WindowFunctionPlugin.js +343 -0
- package/src/utils/config-loader.js +632 -0
- package/src/utils/error-handler.js +724 -0
- package/src/utils/migration-runner.js +1066 -0
|
@@ -0,0 +1,1530 @@
|
|
|
1
|
+
// test-pressure.js - 数据库压力测试脚本(集成CachePlugin版本)
|
|
2
|
+
import DatabaseManager from "../src/DatabaseManager.js";
|
|
3
|
+
import { performance } from "perf_hooks";
|
|
4
|
+
|
|
5
|
+
// 内存缓存驱动实现(用于测试)
|
|
6
|
+
class MemoryCacheDriver {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.cache = new Map();
|
|
9
|
+
this.stats = {
|
|
10
|
+
hits: 0,
|
|
11
|
+
misses: 0,
|
|
12
|
+
sets: 0,
|
|
13
|
+
deletes: 0,
|
|
14
|
+
size: 0,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async get(key) {
|
|
19
|
+
const item = this.cache.get(key);
|
|
20
|
+
if (!item) {
|
|
21
|
+
this.stats.misses++;
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (item.expiry && Date.now() > item.expiry) {
|
|
26
|
+
this.cache.delete(key);
|
|
27
|
+
this.stats.misses++;
|
|
28
|
+
this.stats.size = this.cache.size;
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.stats.hits++;
|
|
33
|
+
return item.value;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async set(key, value, ttl = 300) {
|
|
37
|
+
const expiry = ttl > 0 ? Date.now() + ttl * 1000 : null;
|
|
38
|
+
this.cache.set(key, { value, expiry });
|
|
39
|
+
this.stats.sets++;
|
|
40
|
+
this.stats.size = this.cache.size;
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async del(key) {
|
|
45
|
+
const deleted = this.cache.delete(key);
|
|
46
|
+
if (deleted) {
|
|
47
|
+
this.stats.deletes++;
|
|
48
|
+
this.stats.size = this.cache.size;
|
|
49
|
+
}
|
|
50
|
+
return deleted;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async keys(pattern) {
|
|
54
|
+
const regex = new RegExp(pattern.replace(/\*/g, ".*"));
|
|
55
|
+
const keys = [];
|
|
56
|
+
for (const key of this.cache.keys()) {
|
|
57
|
+
if (regex.test(key)) {
|
|
58
|
+
keys.push(key);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return keys;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async clearPattern(pattern) {
|
|
65
|
+
const keys = await this.keys(pattern);
|
|
66
|
+
for (const key of keys) {
|
|
67
|
+
await this.del(key);
|
|
68
|
+
}
|
|
69
|
+
return keys.length;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async clear() {
|
|
73
|
+
const size = this.cache.size;
|
|
74
|
+
this.cache.clear();
|
|
75
|
+
this.stats.deletes += size;
|
|
76
|
+
this.stats.size = 0;
|
|
77
|
+
return size;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async setWithTags(key, value, ttl, tags) {
|
|
81
|
+
await this.set(key, value, ttl);
|
|
82
|
+
// 简化版标签实现
|
|
83
|
+
for (const tag of tags) {
|
|
84
|
+
const tagKey = `tag:${tag}`;
|
|
85
|
+
const tagData = (await this.get(tagKey)) || [];
|
|
86
|
+
tagData.push(key);
|
|
87
|
+
await this.set(tagKey, tagData, ttl);
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async clearByTags(...tags) {
|
|
93
|
+
let totalDeleted = 0;
|
|
94
|
+
for (const tag of tags) {
|
|
95
|
+
const tagKey = `tag:${tag}`;
|
|
96
|
+
const keys = (await this.get(tagKey)) || [];
|
|
97
|
+
for (const key of keys) {
|
|
98
|
+
await this.del(key);
|
|
99
|
+
totalDeleted++;
|
|
100
|
+
}
|
|
101
|
+
await this.del(tagKey);
|
|
102
|
+
}
|
|
103
|
+
return totalDeleted;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getStats() {
|
|
107
|
+
const total = this.stats.hits + this.stats.misses;
|
|
108
|
+
const hitRate =
|
|
109
|
+
total > 0 ? ((this.stats.hits / total) * 100).toFixed(2) : 0;
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
...this.stats,
|
|
113
|
+
hitRate: `${hitRate}%`,
|
|
114
|
+
totalKeys: this.cache.size,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
resetStats() {
|
|
119
|
+
const oldStats = { ...this.stats };
|
|
120
|
+
this.stats = {
|
|
121
|
+
hits: 0,
|
|
122
|
+
misses: 0,
|
|
123
|
+
sets: 0,
|
|
124
|
+
deletes: 0,
|
|
125
|
+
size: this.cache.size,
|
|
126
|
+
};
|
|
127
|
+
return oldStats;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const config = {
|
|
132
|
+
default: "primary",
|
|
133
|
+
connections: {
|
|
134
|
+
primary: {
|
|
135
|
+
type: "mysql",
|
|
136
|
+
enabled: true,
|
|
137
|
+
host: "127.0.0.1",
|
|
138
|
+
port: 3306,
|
|
139
|
+
user: "root",
|
|
140
|
+
password: "123456",
|
|
141
|
+
database: "test_db",
|
|
142
|
+
// 优化连接池参数
|
|
143
|
+
connectionLimit: 200, // 增加到200个连接
|
|
144
|
+
waitForConnections: true,
|
|
145
|
+
queueLimit: 1000, // 增加队列长度
|
|
146
|
+
acquireTimeout: 30000, // 增加获取连接超时时间
|
|
147
|
+
// 性能优化参数
|
|
148
|
+
charset: 'utf8mb4',
|
|
149
|
+
timezone: '+08:00',
|
|
150
|
+
multipleStatements: true, // 允许多条语句
|
|
151
|
+
// 连接池优化
|
|
152
|
+
maxIdleTime: 60000, // 最大空闲时间60秒
|
|
153
|
+
maxLifeTime: 1800000, // 最大生命周期30分钟
|
|
154
|
+
// 查询优化
|
|
155
|
+
queryTimeout: 30000, // 查询超时时间
|
|
156
|
+
// 批量操作优化
|
|
157
|
+
bulkInsertBatchSize: 5000, // 批量插入批次大小
|
|
158
|
+
// 事务优化
|
|
159
|
+
transactionIsolationLevel: 'READ-COMMITTED', // 降低隔离级别
|
|
160
|
+
// 网络优化
|
|
161
|
+
connectTimeout: 10000, // 连接超时时间
|
|
162
|
+
socketTimeout: 60000, // Socket超时时间
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
plugins: {
|
|
166
|
+
cache: {
|
|
167
|
+
enabled: true,
|
|
168
|
+
defaultTtl: 60,
|
|
169
|
+
prefix: "pressure_test:",
|
|
170
|
+
tagsEnabled: true,
|
|
171
|
+
compression: false
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class EnhancedPressureTester {
|
|
178
|
+
constructor() {
|
|
179
|
+
this.db = new DatabaseManager(config);
|
|
180
|
+
this.cacheDriver = new MemoryCacheDriver();
|
|
181
|
+
this.testResults = {
|
|
182
|
+
phases: {
|
|
183
|
+
preparation: { start: 0, end: 0, duration: 0 },
|
|
184
|
+
testing: { start: 0, end: 0, duration: 0 },
|
|
185
|
+
total: { start: 0, end: 0, duration: 0 },
|
|
186
|
+
},
|
|
187
|
+
operations: {
|
|
188
|
+
select: {
|
|
189
|
+
count: 0,
|
|
190
|
+
totalTime: 0,
|
|
191
|
+
avgTime: 0,
|
|
192
|
+
minTime: Infinity,
|
|
193
|
+
maxTime: 0,
|
|
194
|
+
opsPerSecond: 0,
|
|
195
|
+
cacheHits: 0,
|
|
196
|
+
cacheMisses: 0,
|
|
197
|
+
},
|
|
198
|
+
insert: {
|
|
199
|
+
count: 0,
|
|
200
|
+
totalTime: 0,
|
|
201
|
+
avgTime: 0,
|
|
202
|
+
minTime: Infinity,
|
|
203
|
+
maxTime: 0,
|
|
204
|
+
opsPerSecond: 0,
|
|
205
|
+
},
|
|
206
|
+
update: {
|
|
207
|
+
count: 0,
|
|
208
|
+
totalTime: 0,
|
|
209
|
+
avgTime: 0,
|
|
210
|
+
minTime: Infinity,
|
|
211
|
+
maxTime: 0,
|
|
212
|
+
opsPerSecond: 0,
|
|
213
|
+
},
|
|
214
|
+
delete: {
|
|
215
|
+
count: 0,
|
|
216
|
+
totalTime: 0,
|
|
217
|
+
avgTime: 0,
|
|
218
|
+
minTime: Infinity,
|
|
219
|
+
maxTime: 0,
|
|
220
|
+
opsPerSecond: 0,
|
|
221
|
+
},
|
|
222
|
+
batchInsert: {
|
|
223
|
+
count: 0,
|
|
224
|
+
recordsCount: 0,
|
|
225
|
+
totalTime: 0,
|
|
226
|
+
avgTimePerBatch: 0,
|
|
227
|
+
avgTimePerRecord: 0,
|
|
228
|
+
minTime: Infinity,
|
|
229
|
+
maxTime: 0,
|
|
230
|
+
opsPerSecond: 0,
|
|
231
|
+
recordsPerSecond: 0,
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
concurrency: {
|
|
235
|
+
threads: 0,
|
|
236
|
+
totalOperations: 0,
|
|
237
|
+
successfulThreads: 0,
|
|
238
|
+
failedThreads: 0,
|
|
239
|
+
threadResults: [],
|
|
240
|
+
},
|
|
241
|
+
errors: {
|
|
242
|
+
total: 0,
|
|
243
|
+
byOperation: {
|
|
244
|
+
select: 0,
|
|
245
|
+
insert: 0,
|
|
246
|
+
update: 0,
|
|
247
|
+
delete: 0,
|
|
248
|
+
batchInsert: 0,
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
cache: {
|
|
252
|
+
enabled: false,
|
|
253
|
+
hits: 0,
|
|
254
|
+
misses: 0,
|
|
255
|
+
hitRate: 0,
|
|
256
|
+
driver: "memory",
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async init(enableCache = true) {
|
|
262
|
+
console.log("🔄 初始化数据库连接...");
|
|
263
|
+
await this.db.init();
|
|
264
|
+
|
|
265
|
+
if (enableCache) {
|
|
266
|
+
console.log("🔄 初始化缓存插件...");
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
// 启用缓存插件
|
|
270
|
+
this.db.enablePlugin("cache", {
|
|
271
|
+
defaultTtl: 60,
|
|
272
|
+
prefix: "pressure_test:",
|
|
273
|
+
tagsEnabled: true,
|
|
274
|
+
compression: false,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// 创建 QueryBuilder 实例并设置缓存驱动
|
|
278
|
+
const queryBuilder = this.db.table("pressure_test_users");
|
|
279
|
+
|
|
280
|
+
// 检查 QueryBuilder 是否有 cachePlugin
|
|
281
|
+
if (queryBuilder.cachePlugin) {
|
|
282
|
+
// 设置缓存驱动
|
|
283
|
+
queryBuilder.cachePlugin.setCacheDriver(this.cacheDriver, {
|
|
284
|
+
defaultTtl: 60,
|
|
285
|
+
prefix: "pressure_test:",
|
|
286
|
+
tagsEnabled: true,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
this.testResults.cache.enabled = true;
|
|
290
|
+
this.testResults.cache.driver = "memory";
|
|
291
|
+
console.log("✅ CachePlugin 初始化成功");
|
|
292
|
+
} else {
|
|
293
|
+
console.log("⚠️ QueryBuilder 上没有 cachePlugin,跳过缓存功能");
|
|
294
|
+
this.testResults.cache.enabled = false;
|
|
295
|
+
}
|
|
296
|
+
} catch (error) {
|
|
297
|
+
console.error("❌ CachePlugin 初始化失败:", error.message);
|
|
298
|
+
console.log("⚠️ 跳过缓存功能");
|
|
299
|
+
this.testResults.cache.enabled = false;
|
|
300
|
+
}
|
|
301
|
+
} else {
|
|
302
|
+
console.log("⚠️ 缓存已禁用");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
console.log("✅ DatabaseManager 初始化成功\n");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// 辅助方法:确保 QueryBuilder 的缓存插件已启用
|
|
309
|
+
ensureCacheEnabled(queryBuilder) {
|
|
310
|
+
if (!queryBuilder.cachePlugin) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (!queryBuilder.cachePlugin.cacheEnabled) {
|
|
315
|
+
// 设置缓存驱动
|
|
316
|
+
queryBuilder.cachePlugin.setCacheDriver(this.cacheDriver, {
|
|
317
|
+
defaultTtl: 60,
|
|
318
|
+
prefix: "pressure_test:",
|
|
319
|
+
tagsEnabled: true,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return queryBuilder.cachePlugin.cacheEnabled;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async cleanup() {
|
|
327
|
+
// 清理缓存
|
|
328
|
+
if (this.cacheDriver) {
|
|
329
|
+
await this.cacheDriver.clear();
|
|
330
|
+
console.log("🧹 缓存已清理");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
await this.db.close().catch(() => {});
|
|
334
|
+
console.log("🔌 数据库连接已关闭");
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async measureOperation(
|
|
338
|
+
operationName,
|
|
339
|
+
operation,
|
|
340
|
+
recordCount = 1,
|
|
341
|
+
useCache = false,
|
|
342
|
+
) {
|
|
343
|
+
const startTime = performance.now();
|
|
344
|
+
try {
|
|
345
|
+
let result;
|
|
346
|
+
let cacheHit = false;
|
|
347
|
+
|
|
348
|
+
if (useCache && operationName === "select") {
|
|
349
|
+
// 使用带缓存的执行
|
|
350
|
+
const query = operation();
|
|
351
|
+
|
|
352
|
+
if (query && query.executeWithCache) {
|
|
353
|
+
// 确保缓存插件已启用
|
|
354
|
+
if (!this.ensureCacheEnabled(query)) {
|
|
355
|
+
console.warn("⚠️ 缓存插件未启用,使用普通查询");
|
|
356
|
+
result = await query.execute();
|
|
357
|
+
} else {
|
|
358
|
+
result = await query.executeWithCache();
|
|
359
|
+
// 检查缓存命中情况
|
|
360
|
+
if (result && result._cache && result._cache.hit === true) {
|
|
361
|
+
cacheHit = true;
|
|
362
|
+
this.testResults.operations.select.cacheHits++;
|
|
363
|
+
} else {
|
|
364
|
+
this.testResults.operations.select.cacheMisses++;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
} else {
|
|
368
|
+
result = await operation();
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
result = await operation();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const endTime = performance.now();
|
|
375
|
+
const duration = endTime - startTime;
|
|
376
|
+
|
|
377
|
+
const opStats = this.testResults.operations[operationName];
|
|
378
|
+
|
|
379
|
+
if (operationName === "batchInsert") {
|
|
380
|
+
opStats.count++;
|
|
381
|
+
opStats.recordsCount += recordCount;
|
|
382
|
+
opStats.totalTime += duration;
|
|
383
|
+
opStats.avgTimePerBatch = opStats.totalTime / opStats.count;
|
|
384
|
+
opStats.avgTimePerRecord = opStats.totalTime / opStats.recordsCount;
|
|
385
|
+
} else {
|
|
386
|
+
opStats.count += recordCount;
|
|
387
|
+
opStats.totalTime += duration;
|
|
388
|
+
opStats.avgTime = opStats.totalTime / opStats.count;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
opStats.minTime = Math.min(opStats.minTime, duration);
|
|
392
|
+
opStats.maxTime = Math.max(opStats.maxTime, duration);
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
success: true,
|
|
396
|
+
result,
|
|
397
|
+
duration,
|
|
398
|
+
cacheHit,
|
|
399
|
+
};
|
|
400
|
+
} catch (error) {
|
|
401
|
+
const endTime = performance.now();
|
|
402
|
+
const duration = endTime - startTime;
|
|
403
|
+
|
|
404
|
+
this.testResults.errors.total++;
|
|
405
|
+
this.testResults.errors.byOperation[operationName]++;
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
success: false,
|
|
409
|
+
error,
|
|
410
|
+
duration,
|
|
411
|
+
cacheHit: false,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async prepareTestData() {
|
|
417
|
+
console.log("📊 准备测试数据...");
|
|
418
|
+
|
|
419
|
+
await this.db.execute(`
|
|
420
|
+
CREATE TABLE IF NOT EXISTS pressure_test_users (
|
|
421
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
422
|
+
name VARCHAR(255) NOT NULL,
|
|
423
|
+
email VARCHAR(255) UNIQUE,
|
|
424
|
+
age INT,
|
|
425
|
+
status BOOLEAN DEFAULT true,
|
|
426
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
427
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
428
|
+
-- 优化索引策略
|
|
429
|
+
INDEX idx_age_status (age, status), -- 复合索引
|
|
430
|
+
INDEX idx_name_email (name, email), -- 复合索引
|
|
431
|
+
INDEX idx_created_at_status (created_at, status), -- 时间+状态索引
|
|
432
|
+
INDEX idx_status_age (status, age), -- 状态+年龄索引
|
|
433
|
+
INDEX idx_email_status (email, status), -- 邮箱+状态索引
|
|
434
|
+
INDEX idx_full_cover (age, status, created_at) -- 覆盖索引
|
|
435
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
|
|
436
|
+
`);
|
|
437
|
+
|
|
438
|
+
await this.db.execute("TRUNCATE TABLE pressure_test_users");
|
|
439
|
+
|
|
440
|
+
console.log("📥 预插入10000条测试数据...");
|
|
441
|
+
const batchSize = 1000;
|
|
442
|
+
const totalRecords = 10000;
|
|
443
|
+
|
|
444
|
+
for (let i = 0; i < totalRecords; i += batchSize) {
|
|
445
|
+
const batch = [];
|
|
446
|
+
const currentBatchSize = Math.min(batchSize, totalRecords - i);
|
|
447
|
+
|
|
448
|
+
for (let j = 0; j < currentBatchSize; j++) {
|
|
449
|
+
batch.push({
|
|
450
|
+
name: `测试用户${i + j}`,
|
|
451
|
+
email: `test${i + j}@example.com`,
|
|
452
|
+
age: Math.floor(Math.random() * 50) + 18,
|
|
453
|
+
status: Math.random() > 0.5,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
await this.db.table("pressure_test_users").insert(batch).execute();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
console.log(`✅ 已插入 ${totalRecords} 条测试数据\n`);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
async testSingleSelect(iterations = 100, useCache = false) {
|
|
465
|
+
console.log(
|
|
466
|
+
`🔍 测试单条查询 (${iterations}次, 缓存: ${useCache ? "启用" : "禁用"})...`,
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
for (let i = 0; i < iterations; i++) {
|
|
470
|
+
const age = Math.floor(Math.random() * 50) + 18;
|
|
471
|
+
|
|
472
|
+
const result = await this.measureOperation(
|
|
473
|
+
"select",
|
|
474
|
+
async () => {
|
|
475
|
+
// 每次创建新的 QueryBuilder 实例
|
|
476
|
+
const query = this.db
|
|
477
|
+
.table("pressure_test_users")
|
|
478
|
+
.select("id", "name", "age")
|
|
479
|
+
.where("age", ">", age)
|
|
480
|
+
.limit(10);
|
|
481
|
+
|
|
482
|
+
if (useCache && this.testResults.cache.enabled) {
|
|
483
|
+
// 确保缓存插件已启用
|
|
484
|
+
if (!this.ensureCacheEnabled(query)) {
|
|
485
|
+
return query.execute();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// 启用缓存并执行
|
|
489
|
+
query.cache(30); // 缓存30秒
|
|
490
|
+
return query.executeWithCache();
|
|
491
|
+
} else {
|
|
492
|
+
return query.execute();
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
1,
|
|
496
|
+
useCache,
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
if (!result.success) {
|
|
500
|
+
console.error(`❌ 第${i + 1}次查询失败:`, result.error.message);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
console.log("✅ 单条查询测试完成\n");
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async testBatchSelect(iterations = 50, useCache = false) {
|
|
508
|
+
console.log(
|
|
509
|
+
`🔍 测试批量查询 (${iterations}次,每次100条, 缓存: ${useCache ? "启用" : "禁用"})...`,
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
for (let i = 0; i < iterations; i++) {
|
|
513
|
+
const result = await this.measureOperation(
|
|
514
|
+
"select",
|
|
515
|
+
() => {
|
|
516
|
+
const query = this.db
|
|
517
|
+
.table("pressure_test_users")
|
|
518
|
+
.select("*")
|
|
519
|
+
.where("status", "=", true)
|
|
520
|
+
.orderBy("age", "DESC")
|
|
521
|
+
.limit(100);
|
|
522
|
+
|
|
523
|
+
if (useCache && this.testResults.cache.enabled) {
|
|
524
|
+
// 确保缓存插件已启用
|
|
525
|
+
if (!this.ensureCacheEnabled(query)) {
|
|
526
|
+
return query.execute();
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
query.cache(60); // 缓存60秒
|
|
530
|
+
return query.executeWithCache();
|
|
531
|
+
} else {
|
|
532
|
+
return query.execute();
|
|
533
|
+
}
|
|
534
|
+
},
|
|
535
|
+
100,
|
|
536
|
+
useCache,
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
if (!result.success) {
|
|
540
|
+
console.error(`❌ 第${i + 1}次批量查询失败:`, result.error.message);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
console.log("✅ 批量查询测试完成\n");
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async testSingleInsert(iterations = 100) {
|
|
548
|
+
console.log(`📝 测试单条插入 (${iterations}次)...`);
|
|
549
|
+
|
|
550
|
+
for (let i = 0; i < iterations; i++) {
|
|
551
|
+
const user = {
|
|
552
|
+
name: `压力测试用户${i}`,
|
|
553
|
+
email: `pressure${i}@test.com`,
|
|
554
|
+
age: Math.floor(Math.random() * 50) + 18,
|
|
555
|
+
status: true,
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
const result = await this.measureOperation("insert", () =>
|
|
559
|
+
this.db.table("pressure_test_users").insert(user).execute(),
|
|
560
|
+
);
|
|
561
|
+
|
|
562
|
+
if (!result.success) {
|
|
563
|
+
console.error(`❌ 第${i + 1}次插入失败:`, result.error.message);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
console.log("✅ 单条插入测试完成\n");
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
async testBatchInsert(iterations = 100, batchSize = 1000) {
|
|
571
|
+
console.log(`📝 测试批量插入 (${iterations}批次,每批次${batchSize}条,总计${iterations * batchSize}条)...`);
|
|
572
|
+
|
|
573
|
+
for (let i = 0; i < iterations; i++) {
|
|
574
|
+
const batch = [];
|
|
575
|
+
for (let j = 0; j < batchSize; j++) {
|
|
576
|
+
batch.push({
|
|
577
|
+
name: `批量用户${i}_${j}`,
|
|
578
|
+
email: `batch${i}_${j}@test.com`,
|
|
579
|
+
age: Math.floor(Math.random() * 50) + 18,
|
|
580
|
+
status: Math.random() > 0.5,
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const result = await this.measureOperation(
|
|
585
|
+
"batchInsert",
|
|
586
|
+
() => this.db.table("pressure_test_users").insert(batch).execute(),
|
|
587
|
+
batchSize
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
if (!result.success) {
|
|
591
|
+
console.error(`❌ 第${i + 1}次批量插入失败:`, result.error.message);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
console.log("✅ 批量插入测试完成\n");
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
async testBatchUpdate(iterations = 100, batchSize = 100) {
|
|
599
|
+
console.log(`🔄 测试批量更新 (${iterations}批次,每批次${batchSize}条)...`);
|
|
600
|
+
|
|
601
|
+
// 先获取一批ID用于更新
|
|
602
|
+
const allUsers = await this.db
|
|
603
|
+
.table("pressure_test_users")
|
|
604
|
+
.select("id")
|
|
605
|
+
.limit(iterations * batchSize)
|
|
606
|
+
.execute();
|
|
607
|
+
|
|
608
|
+
const userIds = allUsers.map(u => u.id);
|
|
609
|
+
|
|
610
|
+
for (let i = 0; i < iterations; i++) {
|
|
611
|
+
const batchIds = userIds.slice(i * batchSize, (i + 1) * batchSize);
|
|
612
|
+
|
|
613
|
+
if (batchIds.length === 0) break;
|
|
614
|
+
|
|
615
|
+
const result = await this.measureOperation("update", async () => {
|
|
616
|
+
// 使用IN子句批量更新
|
|
617
|
+
await this.db
|
|
618
|
+
.table("pressure_test_users")
|
|
619
|
+
.whereIn("id", batchIds)
|
|
620
|
+
.update({
|
|
621
|
+
age: Math.floor(Math.random() * 50) + 18,
|
|
622
|
+
status: Math.random() > 0.5,
|
|
623
|
+
updated_at: new Date()
|
|
624
|
+
})
|
|
625
|
+
.execute();
|
|
626
|
+
}, batchIds.length);
|
|
627
|
+
|
|
628
|
+
if (!result.success) {
|
|
629
|
+
console.error(`❌ 第${i + 1}次批量更新失败:`, result.error.message);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
console.log("✅ 批量更新测试完成\n");
|
|
634
|
+
}
|
|
635
|
+
async testBatchDelete(iterations = 50, batchSize = 200) {
|
|
636
|
+
console.log(`🗑️ 测试批量删除 (${iterations}批次,每批次${batchSize}条)...`);
|
|
637
|
+
|
|
638
|
+
// 先插入测试数据
|
|
639
|
+
const totalRecords = iterations * batchSize;
|
|
640
|
+
const tempData = [];
|
|
641
|
+
|
|
642
|
+
for (let i = 0; i < totalRecords; i++) {
|
|
643
|
+
tempData.push({
|
|
644
|
+
name: `批量删除测试${i}`,
|
|
645
|
+
email: `batch_delete${i}@test.com`,
|
|
646
|
+
age: 30,
|
|
647
|
+
status: true,
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// 批量插入
|
|
652
|
+
await this.db.table("pressure_test_users").insert(tempData).execute();
|
|
653
|
+
|
|
654
|
+
// 批量删除
|
|
655
|
+
for (let i = 0; i < iterations; i++) {
|
|
656
|
+
const result = await this.measureOperation("delete", async () => {
|
|
657
|
+
await this.db
|
|
658
|
+
.table("pressure_test_users")
|
|
659
|
+
.where("name", "LIKE", `批量删除测试%`)
|
|
660
|
+
.limit(batchSize)
|
|
661
|
+
.delete()
|
|
662
|
+
.execute();
|
|
663
|
+
}, batchSize);
|
|
664
|
+
|
|
665
|
+
if (!result.success) {
|
|
666
|
+
console.error(`❌ 第${i + 1}次批量删除失败:`, result.error.message);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
console.log("✅ 批量删除测试完成\n");
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
async testUpdate(iterations = 100) {
|
|
674
|
+
console.log(`🔄 测试更新操作 (${iterations}次)...`);
|
|
675
|
+
|
|
676
|
+
const users = await this.db
|
|
677
|
+
.table("pressure_test_users")
|
|
678
|
+
.select("id")
|
|
679
|
+
.limit(iterations)
|
|
680
|
+
.execute();
|
|
681
|
+
|
|
682
|
+
const userIds = Array.isArray(users) ? users.map((u) => u.id) : [];
|
|
683
|
+
|
|
684
|
+
for (let i = 0; i < Math.min(iterations, userIds.length); i++) {
|
|
685
|
+
const result = await this.measureOperation("update", () =>
|
|
686
|
+
this.db
|
|
687
|
+
.table("pressure_test_users")
|
|
688
|
+
.where("id", "=", userIds[i])
|
|
689
|
+
.update({
|
|
690
|
+
age: Math.floor(Math.random() * 50) + 18,
|
|
691
|
+
status: Math.random() > 0.5,
|
|
692
|
+
})
|
|
693
|
+
.execute(),
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
if (!result.success) {
|
|
697
|
+
console.error(`❌ 第${i + 1}次更新失败:`, result.error.message);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
console.log("✅ 更新测试完成\n");
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
async testDelete(iterations = 50) {
|
|
705
|
+
console.log(`🗑️ 测试删除操作 (${iterations}次)...`);
|
|
706
|
+
|
|
707
|
+
const tempData = [];
|
|
708
|
+
for (let i = 0; i < iterations; i++) {
|
|
709
|
+
tempData.push({
|
|
710
|
+
name: `删除测试${i}`,
|
|
711
|
+
email: `delete${i}@test.com`,
|
|
712
|
+
age: 30,
|
|
713
|
+
status: true,
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
if (tempData.length > 0) {
|
|
718
|
+
await this.db.table("pressure_test_users").insert(tempData).execute();
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
for (let i = 0; i < iterations; i++) {
|
|
722
|
+
const result = await this.measureOperation("delete", () =>
|
|
723
|
+
this.db
|
|
724
|
+
.table("pressure_test_users")
|
|
725
|
+
.where("name", "=", `删除测试${i}`)
|
|
726
|
+
.delete()
|
|
727
|
+
.execute(),
|
|
728
|
+
);
|
|
729
|
+
|
|
730
|
+
if (!result.success) {
|
|
731
|
+
console.error(`❌ 第${i + 1}次删除失败:`, result.error.message);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
console.log("✅ 删除测试完成\n");
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
async testConcurrentOperations(
|
|
739
|
+
concurrentCount = 10,
|
|
740
|
+
operationsPerThread = 20,
|
|
741
|
+
useCache = false,
|
|
742
|
+
) {
|
|
743
|
+
console.log(
|
|
744
|
+
`⚡ 测试并发操作 (${concurrentCount}个并发线程,每个${operationsPerThread}次操作,缓存: ${useCache ? "启用" : "禁用"})...`,
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
const promises = [];
|
|
748
|
+
const threadStartTimes = new Array(concurrentCount).fill(0);
|
|
749
|
+
|
|
750
|
+
for (let threadId = 0; threadId < concurrentCount; threadId++) {
|
|
751
|
+
promises.push(
|
|
752
|
+
this.runConcurrentThread(
|
|
753
|
+
threadId,
|
|
754
|
+
operationsPerThread,
|
|
755
|
+
threadStartTimes,
|
|
756
|
+
useCache,
|
|
757
|
+
),
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const threadResults = await Promise.allSettled(promises);
|
|
762
|
+
|
|
763
|
+
threadResults.forEach((result, index) => {
|
|
764
|
+
if (result.status === "fulfilled") {
|
|
765
|
+
const { operations, totalTime } = result.value;
|
|
766
|
+
|
|
767
|
+
if (!operations || typeof operations !== "object") {
|
|
768
|
+
console.error(`❌ 线程 ${index} 返回的 operations 无效:`, operations);
|
|
769
|
+
this.testResults.concurrency.threadResults.push({
|
|
770
|
+
threadId: index,
|
|
771
|
+
success: false,
|
|
772
|
+
error: "Invalid operations object",
|
|
773
|
+
totalTime: totalTime || 0,
|
|
774
|
+
});
|
|
775
|
+
this.testResults.concurrency.failedThreads++;
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
this.testResults.concurrency.threadResults.push({
|
|
780
|
+
threadId: index,
|
|
781
|
+
success: true,
|
|
782
|
+
operations,
|
|
783
|
+
totalTime,
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
if (operations && typeof operations === "object") {
|
|
787
|
+
Object.entries(operations).forEach(([op, count]) => {
|
|
788
|
+
if (op !== "errors" && typeof count === "number" && count > 0) {
|
|
789
|
+
if (this.testResults.operations[op]) {
|
|
790
|
+
this.testResults.operations[op].count += count;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
this.testResults.concurrency.totalOperations +=
|
|
797
|
+
(operations.select || 0) +
|
|
798
|
+
(operations.insert || 0) +
|
|
799
|
+
(operations.update || 0) +
|
|
800
|
+
(operations.delete || 0);
|
|
801
|
+
this.testResults.concurrency.successfulThreads++;
|
|
802
|
+
} else {
|
|
803
|
+
this.testResults.concurrency.threadResults.push({
|
|
804
|
+
threadId: index,
|
|
805
|
+
success: false,
|
|
806
|
+
error: result.reason?.message || "Unknown error",
|
|
807
|
+
});
|
|
808
|
+
this.testResults.concurrency.failedThreads++;
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
this.testResults.concurrency.threads = concurrentCount;
|
|
813
|
+
console.log("✅ 并发测试完成\n");
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
async runConcurrentThread(
|
|
817
|
+
threadId,
|
|
818
|
+
operations,
|
|
819
|
+
startTimes,
|
|
820
|
+
useCache = false,
|
|
821
|
+
) {
|
|
822
|
+
const threadResults = {
|
|
823
|
+
select: 0,
|
|
824
|
+
insert: 0,
|
|
825
|
+
update: 0,
|
|
826
|
+
delete: 0,
|
|
827
|
+
errors: [],
|
|
828
|
+
totalTime: 0,
|
|
829
|
+
};
|
|
830
|
+
|
|
831
|
+
const startTime = performance.now();
|
|
832
|
+
startTimes[threadId] = startTime;
|
|
833
|
+
|
|
834
|
+
for (let i = 0; i < operations; i++) {
|
|
835
|
+
const operationType = Math.floor(Math.random() * 4);
|
|
836
|
+
|
|
837
|
+
try {
|
|
838
|
+
switch (operationType) {
|
|
839
|
+
case 0:
|
|
840
|
+
const selectQuery = this.db
|
|
841
|
+
.table("pressure_test_users")
|
|
842
|
+
.select("id", "name")
|
|
843
|
+
.where("age", ">", 20)
|
|
844
|
+
.limit(5);
|
|
845
|
+
|
|
846
|
+
if (useCache && this.testResults.cache.enabled) {
|
|
847
|
+
// 确保缓存插件已启用
|
|
848
|
+
if (this.ensureCacheEnabled(selectQuery)) {
|
|
849
|
+
selectQuery.cache(30); // 缓存30秒
|
|
850
|
+
await selectQuery.executeWithCache();
|
|
851
|
+
} else {
|
|
852
|
+
await selectQuery.execute();
|
|
853
|
+
}
|
|
854
|
+
} else {
|
|
855
|
+
await selectQuery.execute();
|
|
856
|
+
}
|
|
857
|
+
threadResults.select++;
|
|
858
|
+
break;
|
|
859
|
+
|
|
860
|
+
case 1:
|
|
861
|
+
await this.db
|
|
862
|
+
.table("pressure_test_users")
|
|
863
|
+
.insert({
|
|
864
|
+
name: `并发用户${threadId}_${i}`,
|
|
865
|
+
email: `concurrent${threadId}_${i}@test.com`,
|
|
866
|
+
age: 25,
|
|
867
|
+
status: true,
|
|
868
|
+
})
|
|
869
|
+
.execute();
|
|
870
|
+
threadResults.insert++;
|
|
871
|
+
break;
|
|
872
|
+
|
|
873
|
+
case 2:
|
|
874
|
+
await this.db
|
|
875
|
+
.table("pressure_test_users")
|
|
876
|
+
.where("status", "=", true)
|
|
877
|
+
.limit(1)
|
|
878
|
+
.update({ status: false })
|
|
879
|
+
.execute();
|
|
880
|
+
threadResults.update++;
|
|
881
|
+
break;
|
|
882
|
+
|
|
883
|
+
case 3:
|
|
884
|
+
await this.db
|
|
885
|
+
.table("pressure_test_users")
|
|
886
|
+
.where("name", "=", `并发用户${threadId}_${i}`)
|
|
887
|
+
.delete()
|
|
888
|
+
.execute();
|
|
889
|
+
threadResults.delete++;
|
|
890
|
+
break;
|
|
891
|
+
}
|
|
892
|
+
} catch (error) {
|
|
893
|
+
threadResults.errors.push({
|
|
894
|
+
operation: ["SELECT", "INSERT", "UPDATE", "DELETE"][operationType],
|
|
895
|
+
error: error.message,
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
threadResults.totalTime = performance.now() - startTime;
|
|
901
|
+
return {
|
|
902
|
+
operations: threadResults,
|
|
903
|
+
totalTime: threadResults.totalTime,
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
async testCachePerformance(iterations = 100) {
|
|
908
|
+
console.log(`🧪 测试缓存性能 (${iterations}次查询,对比有无缓存)...\n`);
|
|
909
|
+
|
|
910
|
+
// 重置缓存统计
|
|
911
|
+
this.cacheDriver.resetStats();
|
|
912
|
+
|
|
913
|
+
// 测试无缓存
|
|
914
|
+
console.log("1. 无缓存查询测试...");
|
|
915
|
+
const noCacheStart = performance.now();
|
|
916
|
+
await this.testSingleSelect(iterations, false);
|
|
917
|
+
const noCacheTime = performance.now() - noCacheStart;
|
|
918
|
+
|
|
919
|
+
// 测试有缓存
|
|
920
|
+
console.log("2. 有缓存查询测试...");
|
|
921
|
+
const cacheStart = performance.now();
|
|
922
|
+
await this.testSingleSelect(iterations, true);
|
|
923
|
+
const cacheTime = performance.now() - cacheStart;
|
|
924
|
+
|
|
925
|
+
// 获取缓存统计
|
|
926
|
+
const cacheStats = this.cacheDriver.getStats();
|
|
927
|
+
const totalCacheOps = cacheStats.hits + cacheStats.misses;
|
|
928
|
+
const cacheHitRate =
|
|
929
|
+
totalCacheOps > 0
|
|
930
|
+
? ((cacheStats.hits / totalCacheOps) * 100).toFixed(2)
|
|
931
|
+
: 0;
|
|
932
|
+
|
|
933
|
+
console.log("📊 缓存性能对比:");
|
|
934
|
+
console.log(` 无缓存总耗时: ${noCacheTime.toFixed(2)}ms`);
|
|
935
|
+
console.log(` 有缓存总耗时: ${cacheTime.toFixed(2)}ms`);
|
|
936
|
+
console.log(
|
|
937
|
+
` 性能提升: ${(((noCacheTime - cacheTime) / noCacheTime) * 100).toFixed(2)}%`,
|
|
938
|
+
);
|
|
939
|
+
console.log(` 缓存命中率: ${cacheHitRate}%`);
|
|
940
|
+
console.log(` 缓存命中次数: ${cacheStats.hits}`);
|
|
941
|
+
console.log(` 缓存未命中次数: ${cacheStats.misses}\n`);
|
|
942
|
+
|
|
943
|
+
this.testResults.cache.hits = cacheStats.hits;
|
|
944
|
+
this.testResults.cache.misses = cacheStats.misses;
|
|
945
|
+
this.testResults.cache.hitRate = cacheHitRate;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
async testCacheInvalidation() {
|
|
949
|
+
console.log("🧪 测试缓存失效机制...");
|
|
950
|
+
|
|
951
|
+
// 先执行一个带缓存的查询
|
|
952
|
+
const query = this.db
|
|
953
|
+
.table("pressure_test_users")
|
|
954
|
+
.select("*")
|
|
955
|
+
.where("status", "=", true)
|
|
956
|
+
.limit(5);
|
|
957
|
+
|
|
958
|
+
// 确保缓存插件已启用
|
|
959
|
+
if (!this.ensureCacheEnabled(query)) {
|
|
960
|
+
console.log("⚠️ 缓存插件未启用,跳过缓存失效测试");
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
query.cache(60); // 缓存60秒
|
|
965
|
+
|
|
966
|
+
console.log("1. 第一次执行(缓存未命中)...");
|
|
967
|
+
const start1 = performance.now();
|
|
968
|
+
const result1 = await query.executeWithCache();
|
|
969
|
+
const time1 = performance.now() - start1;
|
|
970
|
+
console.log(` 耗时: ${time1.toFixed(2)}ms`);
|
|
971
|
+
|
|
972
|
+
console.log("2. 第二次执行(缓存命中)...");
|
|
973
|
+
const start2 = performance.now();
|
|
974
|
+
const result2 = await query.executeWithCache();
|
|
975
|
+
const time2 = performance.now() - start2;
|
|
976
|
+
console.log(` 耗时: ${time2.toFixed(2)}ms`);
|
|
977
|
+
console.log(
|
|
978
|
+
` 缓存加速: ${(((time1 - time2) / time1) * 100).toFixed(2)}%`,
|
|
979
|
+
);
|
|
980
|
+
|
|
981
|
+
// 插入数据,应该自动清除相关缓存
|
|
982
|
+
console.log("3. 插入新数据(触发缓存清除)...");
|
|
983
|
+
await this.db
|
|
984
|
+
.table("pressure_test_users")
|
|
985
|
+
.insert({
|
|
986
|
+
name: "缓存测试用户",
|
|
987
|
+
email: "cache_test@example.com",
|
|
988
|
+
age: 30,
|
|
989
|
+
status: true,
|
|
990
|
+
})
|
|
991
|
+
.execute();
|
|
992
|
+
|
|
993
|
+
console.log("4. 第三次执行(缓存失效后重新查询)...");
|
|
994
|
+
const start3 = performance.now();
|
|
995
|
+
const result3 = await query.executeWithCache();
|
|
996
|
+
const time3 = performance.now() - start3;
|
|
997
|
+
console.log(` 耗时: ${time3.toFixed(2)}ms`);
|
|
998
|
+
|
|
999
|
+
console.log("✅ 缓存失效测试完成\n");
|
|
1000
|
+
}
|
|
1001
|
+
async testParallelBatchOperations(operationType = 'insert', concurrentBatches = 20, batchSize = 500) {
|
|
1002
|
+
console.log(`⚡ 测试并行${operationType.toUpperCase()}操作 (${concurrentBatches}个并行批次,每批次${batchSize}条)...`);
|
|
1003
|
+
|
|
1004
|
+
const totalRecords = concurrentBatches * batchSize;
|
|
1005
|
+
console.log(`总计操作: ${totalRecords}条记录`);
|
|
1006
|
+
|
|
1007
|
+
const startTime = performance.now();
|
|
1008
|
+
|
|
1009
|
+
// 并行执行多个批量操作
|
|
1010
|
+
const promises = [];
|
|
1011
|
+
for (let i = 0; i < concurrentBatches; i++) {
|
|
1012
|
+
promises.push(this.executeParallelBatch(i, batchSize, operationType));
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
const results = await Promise.allSettled(promises);
|
|
1016
|
+
|
|
1017
|
+
const endTime = performance.now();
|
|
1018
|
+
const totalTime = endTime - startTime;
|
|
1019
|
+
|
|
1020
|
+
const successfulBatches = results.filter(r => r.status === 'fulfilled').length;
|
|
1021
|
+
const recordsPerSecond = totalRecords / (totalTime / 1000);
|
|
1022
|
+
|
|
1023
|
+
console.log(`✅ 并行${operationType.toUpperCase()}操作完成`);
|
|
1024
|
+
console.log(` 成功批次: ${successfulBatches}/${concurrentBatches}`);
|
|
1025
|
+
console.log(` 总耗时: ${totalTime.toFixed(2)}ms`);
|
|
1026
|
+
console.log(` 吞吐量: ${recordsPerSecond.toFixed(2)} records/s`);
|
|
1027
|
+
console.log(` 平均每批次: ${(totalTime / concurrentBatches).toFixed(2)}ms\n`);
|
|
1028
|
+
|
|
1029
|
+
return {
|
|
1030
|
+
totalRecords,
|
|
1031
|
+
totalTime,
|
|
1032
|
+
recordsPerSecond,
|
|
1033
|
+
successfulBatches,
|
|
1034
|
+
totalBatches: concurrentBatches
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
async executeParallelBatch(batchId, batchSize, operationType) {
|
|
1039
|
+
switch (operationType) {
|
|
1040
|
+
case 'insert':
|
|
1041
|
+
const insertBatch = [];
|
|
1042
|
+
for (let j = 0; j < batchSize; j++) {
|
|
1043
|
+
insertBatch.push({
|
|
1044
|
+
name: `并行用户${batchId}_${j}`,
|
|
1045
|
+
email: `parallel${batchId}_${j}@test.com`,
|
|
1046
|
+
age: Math.floor(Math.random() * 50) + 18,
|
|
1047
|
+
status: Math.random() > 0.5,
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
return this.db.table("pressure_test_users").insert(insertBatch).execute();
|
|
1051
|
+
|
|
1052
|
+
case 'update':
|
|
1053
|
+
// 获取一批ID进行更新
|
|
1054
|
+
const users = await this.db
|
|
1055
|
+
.table("pressure_test_users")
|
|
1056
|
+
.select("id")
|
|
1057
|
+
.limit(batchSize)
|
|
1058
|
+
.offset(batchId * batchSize)
|
|
1059
|
+
.execute();
|
|
1060
|
+
|
|
1061
|
+
if (users.length === 0) return;
|
|
1062
|
+
|
|
1063
|
+
const updateIds = users.map(u => u.id);
|
|
1064
|
+
return this.db
|
|
1065
|
+
.table("pressure_test_users")
|
|
1066
|
+
.whereIn("id", updateIds)
|
|
1067
|
+
.update({
|
|
1068
|
+
age: Math.floor(Math.random() * 50) + 18,
|
|
1069
|
+
status: Math.random() > 0.5,
|
|
1070
|
+
updated_at: new Date()
|
|
1071
|
+
})
|
|
1072
|
+
.execute();
|
|
1073
|
+
|
|
1074
|
+
case 'delete':
|
|
1075
|
+
// 插入临时数据然后删除
|
|
1076
|
+
const tempBatch = [];
|
|
1077
|
+
for (let j = 0; j < batchSize; j++) {
|
|
1078
|
+
tempBatch.push({
|
|
1079
|
+
name: `并行删除${batchId}_${j}`,
|
|
1080
|
+
email: `parallel_delete${batchId}_${j}@test.com`,
|
|
1081
|
+
age: 30,
|
|
1082
|
+
status: true,
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
await this.db.table("pressure_test_users").insert(tempBatch).execute();
|
|
1087
|
+
|
|
1088
|
+
return this.db
|
|
1089
|
+
.table("pressure_test_users")
|
|
1090
|
+
.where("name", "LIKE", `并行删除${batchId}_%`)
|
|
1091
|
+
.delete()
|
|
1092
|
+
.execute();
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
async runAllTests(enableCache = true) {
|
|
1097
|
+
console.log(
|
|
1098
|
+
`🚀 开始数据库压力测试(缓存: ${enableCache ? "启用" : "禁用"})\n`,
|
|
1099
|
+
);
|
|
1100
|
+
|
|
1101
|
+
this.testResults.phases.total.start = performance.now();
|
|
1102
|
+
|
|
1103
|
+
try {
|
|
1104
|
+
this.testResults.phases.preparation.start = performance.now();
|
|
1105
|
+
await this.init(enableCache);
|
|
1106
|
+
await this.prepareTestData();
|
|
1107
|
+
this.testResults.phases.preparation.end = performance.now();
|
|
1108
|
+
this.testResults.phases.preparation.duration =
|
|
1109
|
+
this.testResults.phases.preparation.end -
|
|
1110
|
+
this.testResults.phases.preparation.start;
|
|
1111
|
+
|
|
1112
|
+
this.testResults.phases.testing.start = performance.now();
|
|
1113
|
+
|
|
1114
|
+
// 基础性能测试
|
|
1115
|
+
console.log("=== 基础性能测试 ===\n");
|
|
1116
|
+
await this.testSingleSelect(100, enableCache);
|
|
1117
|
+
await this.testBatchSelect(50, enableCache);
|
|
1118
|
+
await this.testSingleInsert(100);
|
|
1119
|
+
await this.testBatchInsert(20, 50);
|
|
1120
|
+
await this.testUpdate(100);
|
|
1121
|
+
await this.testDelete(50);
|
|
1122
|
+
await this.testConcurrentOperations(10, 20, enableCache);
|
|
1123
|
+
|
|
1124
|
+
// 如果启用缓存,测试缓存性能
|
|
1125
|
+
if (enableCache) {
|
|
1126
|
+
console.log("=== 缓存性能测试 ===\n");
|
|
1127
|
+
await this.testCachePerformance(100);
|
|
1128
|
+
await this.testCacheInvalidation();
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
this.testResults.phases.testing.end = performance.now();
|
|
1132
|
+
this.testResults.phases.testing.duration =
|
|
1133
|
+
this.testResults.phases.testing.end -
|
|
1134
|
+
this.testResults.phases.testing.start;
|
|
1135
|
+
|
|
1136
|
+
this.testResults.phases.total.end = performance.now();
|
|
1137
|
+
this.testResults.phases.total.duration =
|
|
1138
|
+
this.testResults.phases.total.end - this.testResults.phases.total.start;
|
|
1139
|
+
|
|
1140
|
+
this.generateEnhancedReport();
|
|
1141
|
+
} catch (error) {
|
|
1142
|
+
console.error("❌ 压力测试执行失败:", error.message);
|
|
1143
|
+
console.error(error.stack);
|
|
1144
|
+
} finally {
|
|
1145
|
+
await this.cleanup();
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
generateEnhancedReport() {
|
|
1150
|
+
console.log("\n📊 =============== 增强版压力测试报告 ===============\n");
|
|
1151
|
+
|
|
1152
|
+
console.log("📈 测试概况:");
|
|
1153
|
+
console.log(
|
|
1154
|
+
` 准备阶段耗时: ${this.testResults.phases.preparation.duration.toFixed(2)}ms`,
|
|
1155
|
+
);
|
|
1156
|
+
console.log(
|
|
1157
|
+
` 性能测试耗时: ${this.testResults.phases.testing.duration.toFixed(2)}ms`,
|
|
1158
|
+
);
|
|
1159
|
+
console.log(
|
|
1160
|
+
` 总测试时间: ${this.testResults.phases.total.duration.toFixed(2)}ms\n`,
|
|
1161
|
+
);
|
|
1162
|
+
|
|
1163
|
+
console.log("🔧 操作性能详情:");
|
|
1164
|
+
this.printOperationDetails();
|
|
1165
|
+
|
|
1166
|
+
console.log("\n⚡ 并发测试结果:");
|
|
1167
|
+
this.printConcurrencyResults();
|
|
1168
|
+
|
|
1169
|
+
if (this.testResults.cache.enabled) {
|
|
1170
|
+
console.log("\n🧠 缓存性能分析:");
|
|
1171
|
+
this.printCacheAnalysis();
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
console.log("\n❌ 错误统计:");
|
|
1175
|
+
this.printErrorStatistics();
|
|
1176
|
+
|
|
1177
|
+
console.log("\n⚠️ 性能瓶颈分析:");
|
|
1178
|
+
this.printPerformanceBottleneck();
|
|
1179
|
+
|
|
1180
|
+
console.log("\n💡 优化建议:");
|
|
1181
|
+
this.printOptimizationSuggestions();
|
|
1182
|
+
|
|
1183
|
+
console.log("\n🎯 性能总结:");
|
|
1184
|
+
this.printPerformanceSummary();
|
|
1185
|
+
|
|
1186
|
+
console.log("\n✅ =============== 测试完成 ===============");
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
printOperationDetails() {
|
|
1190
|
+
Object.entries(this.testResults.operations).forEach(([op, stats]) => {
|
|
1191
|
+
if (stats.count > 0 || (op === "batchInsert" && stats.recordsCount > 0)) {
|
|
1192
|
+
console.log(`\n${op.toUpperCase()}:`);
|
|
1193
|
+
|
|
1194
|
+
if (op === "batchInsert") {
|
|
1195
|
+
console.log(` 执行批次: ${stats.count}次`);
|
|
1196
|
+
console.log(` 总记录数: ${stats.recordsCount}条`);
|
|
1197
|
+
console.log(` 总耗时: ${stats.totalTime.toFixed(2)}ms`);
|
|
1198
|
+
console.log(
|
|
1199
|
+
` 平均每批次耗时: ${stats.avgTimePerBatch.toFixed(2)}ms`,
|
|
1200
|
+
);
|
|
1201
|
+
console.log(
|
|
1202
|
+
` 平均每条记录耗时: ${stats.avgTimePerRecord.toFixed(4)}ms`,
|
|
1203
|
+
);
|
|
1204
|
+
console.log(` 最小时耗: ${stats.minTime.toFixed(2)}ms`);
|
|
1205
|
+
console.log(` 最大时耗: ${stats.maxTime.toFixed(2)}ms`);
|
|
1206
|
+
console.log(
|
|
1207
|
+
` 批次操作每秒: ${(stats.count / (stats.totalTime / 1000)).toFixed(2)} ops/s`,
|
|
1208
|
+
);
|
|
1209
|
+
console.log(
|
|
1210
|
+
` 记录操作每秒: ${(stats.recordsCount / (stats.totalTime / 1000)).toFixed(2)} records/s`,
|
|
1211
|
+
);
|
|
1212
|
+
} else {
|
|
1213
|
+
console.log(` 执行次数: ${stats.count}次`);
|
|
1214
|
+
console.log(` 总耗时: ${stats.totalTime.toFixed(2)}ms`);
|
|
1215
|
+
console.log(` 平均耗时: ${stats.avgTime.toFixed(2)}ms`);
|
|
1216
|
+
console.log(` 最小时耗: ${stats.minTime.toFixed(2)}ms`);
|
|
1217
|
+
console.log(` 最大时耗: ${stats.maxTime.toFixed(2)}ms`);
|
|
1218
|
+
console.log(
|
|
1219
|
+
` 平均每秒: ${(stats.count / (stats.totalTime / 1000)).toFixed(2)} ops/s`,
|
|
1220
|
+
);
|
|
1221
|
+
|
|
1222
|
+
if (op === "select" && stats.cacheHits !== undefined) {
|
|
1223
|
+
const totalSelects = stats.cacheHits + stats.cacheMisses;
|
|
1224
|
+
const hitRate =
|
|
1225
|
+
totalSelects > 0
|
|
1226
|
+
? ((stats.cacheHits / totalSelects) * 100).toFixed(2)
|
|
1227
|
+
: 0;
|
|
1228
|
+
console.log(` 缓存命中: ${stats.cacheHits}次`);
|
|
1229
|
+
console.log(` 缓存未命中: ${stats.cacheMisses}次`);
|
|
1230
|
+
console.log(` 缓存命中率: ${hitRate}%`);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
printCacheAnalysis() {
|
|
1238
|
+
const { hits, misses, hitRate, enabled, driver } = this.testResults.cache;
|
|
1239
|
+
const totalCacheOps = hits + misses;
|
|
1240
|
+
|
|
1241
|
+
console.log(` 缓存状态: ${enabled ? "✅ 已启用" : "❌ 未启用"}`);
|
|
1242
|
+
console.log(` 缓存驱动: ${driver}`);
|
|
1243
|
+
console.log(` 缓存总操作: ${totalCacheOps}次`);
|
|
1244
|
+
console.log(` 缓存命中: ${hits}次`);
|
|
1245
|
+
console.log(` 缓存未命中: ${misses}次`);
|
|
1246
|
+
console.log(` 缓存命中率: ${hitRate}%`);
|
|
1247
|
+
|
|
1248
|
+
if (this.cacheDriver) {
|
|
1249
|
+
const cacheStats = this.cacheDriver.getStats();
|
|
1250
|
+
console.log(` 缓存大小: ${cacheStats.totalKeys}条记录`);
|
|
1251
|
+
console.log(` 缓存设置: ${cacheStats.sets}次`);
|
|
1252
|
+
console.log(` 缓存删除: ${cacheStats.deletes}次`);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// 缓存性能分析
|
|
1256
|
+
const selectStats = this.testResults.operations.select;
|
|
1257
|
+
if (selectStats.cacheHits > 0) {
|
|
1258
|
+
const avgCacheTime =
|
|
1259
|
+
selectStats.totalTime /
|
|
1260
|
+
(selectStats.cacheHits + selectStats.cacheMisses);
|
|
1261
|
+
const estimatedSavings = selectStats.cacheHits * avgCacheTime * 0.7; // 假设缓存比数据库快70%
|
|
1262
|
+
console.log(` 预估节省时间: ${estimatedSavings.toFixed(2)}ms`);
|
|
1263
|
+
console.log(` 性能提升比例: ${(hitRate * 0.7).toFixed(2)}%`);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
printConcurrencyResults() {
|
|
1268
|
+
const {
|
|
1269
|
+
threads,
|
|
1270
|
+
successfulThreads,
|
|
1271
|
+
failedThreads,
|
|
1272
|
+
totalOperations,
|
|
1273
|
+
threadResults,
|
|
1274
|
+
} = this.testResults.concurrency;
|
|
1275
|
+
|
|
1276
|
+
console.log(` 总线程数: ${threads}`);
|
|
1277
|
+
console.log(` 成功线程: ${successfulThreads}`);
|
|
1278
|
+
console.log(` 失败线程: ${failedThreads}`);
|
|
1279
|
+
console.log(` 并发总操作数: ${totalOperations}`);
|
|
1280
|
+
|
|
1281
|
+
if (threadResults && threadResults.length > 0 && successfulThreads > 0) {
|
|
1282
|
+
const successfulThreadsArray = threadResults.filter(
|
|
1283
|
+
(t) => t && t.success,
|
|
1284
|
+
);
|
|
1285
|
+
if (successfulThreadsArray.length > 0) {
|
|
1286
|
+
const avgThreadTime =
|
|
1287
|
+
successfulThreadsArray.reduce(
|
|
1288
|
+
(sum, t) => sum + (t.totalTime || 0),
|
|
1289
|
+
0,
|
|
1290
|
+
) / successfulThreadsArray.length;
|
|
1291
|
+
console.log(` 平均线程执行时间: ${avgThreadTime.toFixed(2)}ms`);
|
|
1292
|
+
|
|
1293
|
+
const maxTime = Math.max(
|
|
1294
|
+
...successfulThreadsArray.map((t) => t.totalTime || 0),
|
|
1295
|
+
);
|
|
1296
|
+
if (maxTime > 0) {
|
|
1297
|
+
const concurrentOpsPerSecond = totalOperations / (maxTime / 1000);
|
|
1298
|
+
console.log(
|
|
1299
|
+
` 并发操作每秒: ${concurrentOpsPerSecond.toFixed(2)} ops/s`,
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
printErrorStatistics() {
|
|
1307
|
+
const { total, byOperation } = this.testResults.errors;
|
|
1308
|
+
|
|
1309
|
+
console.log(` 总错误数: ${total}`);
|
|
1310
|
+
if (total > 0) {
|
|
1311
|
+
console.log(" 按操作类型分布:");
|
|
1312
|
+
Object.entries(byOperation).forEach(([op, count]) => {
|
|
1313
|
+
if (count > 0) {
|
|
1314
|
+
console.log(` ${op.toUpperCase()}: ${count}次`);
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
} else {
|
|
1318
|
+
console.log(" ✅ 无错误发生");
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
printPerformanceBottleneck() {
|
|
1322
|
+
const opsArray = Object.entries(this.testResults.operations)
|
|
1323
|
+
.filter(([name, stats]) => {
|
|
1324
|
+
if (name === "batchInsert") return false;
|
|
1325
|
+
if (!stats || typeof stats !== "object") return false;
|
|
1326
|
+
if (stats.count === undefined || stats.count <= 0) return false;
|
|
1327
|
+
if (stats.avgTime === undefined || isNaN(stats.avgTime)) return false;
|
|
1328
|
+
return true;
|
|
1329
|
+
})
|
|
1330
|
+
.map(([name, stats]) => ({
|
|
1331
|
+
name,
|
|
1332
|
+
avgTime: stats.avgTime || 0,
|
|
1333
|
+
count: stats.count || 0,
|
|
1334
|
+
opsPerSecond:
|
|
1335
|
+
stats.count > 0 ? stats.count / (stats.totalTime / 1000) : 0,
|
|
1336
|
+
}));
|
|
1337
|
+
|
|
1338
|
+
if (opsArray.length === 0) {
|
|
1339
|
+
console.log(" 无有效测试数据,无法分析瓶颈");
|
|
1340
|
+
|
|
1341
|
+
const batchStats = this.testResults.operations.batchInsert;
|
|
1342
|
+
if (batchStats && batchStats.count > 0) {
|
|
1343
|
+
console.log(
|
|
1344
|
+
` 📦 批量插入: ${batchStats.count}批次, ${batchStats.recordsCount}条记录`,
|
|
1345
|
+
);
|
|
1346
|
+
console.log(
|
|
1347
|
+
` 平均每批次: ${batchStats.avgTimePerBatch?.toFixed(2) || 0}ms`,
|
|
1348
|
+
);
|
|
1349
|
+
console.log(
|
|
1350
|
+
` 平均每条记录: ${batchStats.avgTimePerRecord?.toFixed(4) || 0}ms`,
|
|
1351
|
+
);
|
|
1352
|
+
console.log(
|
|
1353
|
+
` 记录吞吐量: ${(batchStats.recordsCount / (batchStats.totalTime / 1000)).toFixed(2)} records/s`,
|
|
1354
|
+
);
|
|
1355
|
+
}
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
opsArray.sort((a, b) => b.avgTime - a.avgTime);
|
|
1360
|
+
|
|
1361
|
+
// 修复这里:使用 opsArray[0] 而不是 opsArray
|
|
1362
|
+
const slowest = opsArray[0];
|
|
1363
|
+
const fastest = opsArray[opsArray.length - 1];
|
|
1364
|
+
|
|
1365
|
+
if (!slowest || !fastest) {
|
|
1366
|
+
console.log(" 无法确定最快和最慢操作");
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
console.log(
|
|
1371
|
+
` 🐢 最慢操作: ${slowest.name?.toUpperCase() || "未知"} (平均 ${slowest.avgTime?.toFixed(2) || 0}ms, ${slowest.opsPerSecond?.toFixed(2) || 0} ops/s)`,
|
|
1372
|
+
);
|
|
1373
|
+
console.log(
|
|
1374
|
+
` 🐇 最快操作: ${fastest.name?.toUpperCase() || "未知"} (平均 ${fastest.avgTime?.toFixed(2) || 0}ms, ${fastest.opsPerSecond?.toFixed(2) || 0} ops/s)`,
|
|
1375
|
+
);
|
|
1376
|
+
|
|
1377
|
+
const batchStats = this.testResults.operations.batchInsert;
|
|
1378
|
+
if (batchStats && batchStats.count > 0) {
|
|
1379
|
+
console.log(
|
|
1380
|
+
` 📦 批量插入: ${batchStats.count}批次, ${batchStats.recordsCount}条记录`,
|
|
1381
|
+
);
|
|
1382
|
+
console.log(
|
|
1383
|
+
` 平均每批次: ${batchStats.avgTimePerBatch?.toFixed(2) || 0}ms`,
|
|
1384
|
+
);
|
|
1385
|
+
console.log(
|
|
1386
|
+
` 平均每条记录: ${batchStats.avgTimePerRecord?.toFixed(4) || 0}ms`,
|
|
1387
|
+
);
|
|
1388
|
+
console.log(
|
|
1389
|
+
` 记录吞吐量: ${(batchStats.recordsCount / (batchStats.totalTime / 1000)).toFixed(2)} records/s`,
|
|
1390
|
+
);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
printOptimizationSuggestions() {
|
|
1395
|
+
const suggestions = [];
|
|
1396
|
+
const ops = this.testResults.operations;
|
|
1397
|
+
|
|
1398
|
+
if (ops.select && ops.select.avgTime > 10) {
|
|
1399
|
+
suggestions.push("1. SELECT 操作较慢,考虑添加更多索引或优化查询条件");
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
if (ops.insert && ops.insert.avgTime > 20) {
|
|
1403
|
+
suggestions.push("2. 单条插入较慢,建议使用批量插入替代频繁的单条插入");
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
if (ops.batchInsert && ops.batchInsert.avgTimePerRecord > 0.5) {
|
|
1407
|
+
suggestions.push(
|
|
1408
|
+
"3. 批量插入每条记录耗时较高,考虑调整批次大小或检查数据库配置",
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
if (ops.update && ops.update.avgTime > 30) {
|
|
1413
|
+
suggestions.push("4. UPDATE 操作较慢,检查是否有锁竞争或索引问题");
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
if (this.testResults.concurrency.failedThreads > 0) {
|
|
1417
|
+
suggestions.push(
|
|
1418
|
+
"5. 并发测试中有失败线程,建议增加连接池大小或优化并发控制",
|
|
1419
|
+
);
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
if (ops.batchInsert && ops.batchInsert.count > 0) {
|
|
1423
|
+
const recordsPerSecond =
|
|
1424
|
+
ops.batchInsert.recordsCount / (ops.batchInsert.totalTime / 1000);
|
|
1425
|
+
if (recordsPerSecond < 1000) {
|
|
1426
|
+
suggestions.push(
|
|
1427
|
+
`6. 批量插入吞吐量较低 (${recordsPerSecond.toFixed(2)} records/s),建议调整批次大小或使用事务批量提交`,
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
// 缓存相关建议
|
|
1433
|
+
const selectStats = this.testResults.operations.select;
|
|
1434
|
+
if (selectStats && selectStats.cacheHits > 0) {
|
|
1435
|
+
const totalCacheOps = selectStats.cacheHits + selectStats.cacheMisses;
|
|
1436
|
+
const cacheHitRate =
|
|
1437
|
+
totalCacheOps > 0 ? (selectStats.cacheHits / totalCacheOps) * 100 : 0;
|
|
1438
|
+
|
|
1439
|
+
if (cacheHitRate < 50) {
|
|
1440
|
+
suggestions.push(
|
|
1441
|
+
`7. 缓存命中率较低 (${cacheHitRate.toFixed(2)}%),考虑增加缓存TTL或优化查询模式`,
|
|
1442
|
+
);
|
|
1443
|
+
} else if (cacheHitRate > 80) {
|
|
1444
|
+
suggestions.push(
|
|
1445
|
+
`8. 缓存命中率良好 (${cacheHitRate.toFixed(2)}%),可考虑增加缓存容量`,
|
|
1446
|
+
);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
if (suggestions.length === 0) {
|
|
1451
|
+
suggestions.push("数据库性能良好,继续保持当前配置");
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
suggestions.forEach((suggestion, index) => {
|
|
1455
|
+
console.log(` ${suggestion}`);
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
printPerformanceSummary() {
|
|
1459
|
+
const totalOperations = Object.values(this.testResults.operations).reduce(
|
|
1460
|
+
(sum, op) => {
|
|
1461
|
+
if (!op) return sum;
|
|
1462
|
+
|
|
1463
|
+
if (op === this.testResults.operations.batchInsert) {
|
|
1464
|
+
return sum + (op.recordsCount || 0);
|
|
1465
|
+
}
|
|
1466
|
+
return sum + (op.count || 0);
|
|
1467
|
+
},
|
|
1468
|
+
0,
|
|
1469
|
+
);
|
|
1470
|
+
|
|
1471
|
+
const testDuration = this.testResults.phases.testing?.duration || 0;
|
|
1472
|
+
|
|
1473
|
+
const overallOpsPerSecond =
|
|
1474
|
+
testDuration > 0 ? totalOperations / (testDuration / 1000) : 0;
|
|
1475
|
+
|
|
1476
|
+
console.log(` 总操作数: ${totalOperations}次`);
|
|
1477
|
+
console.log(` 性能测试时间: ${testDuration.toFixed(2)}ms`);
|
|
1478
|
+
console.log(` 总体操作每秒: ${overallOpsPerSecond.toFixed(2)} ops/s`);
|
|
1479
|
+
|
|
1480
|
+
// 缓存性能统计
|
|
1481
|
+
const cacheStats = this.testResults.cache;
|
|
1482
|
+
if (cacheStats.enabled) {
|
|
1483
|
+
const totalCacheOps = cacheStats.hits + cacheStats.misses;
|
|
1484
|
+
if (totalCacheOps > 0) {
|
|
1485
|
+
console.log(` 缓存总操作: ${totalCacheOps}次`);
|
|
1486
|
+
console.log(` 缓存命中率: ${cacheStats.hitRate}%`);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
if (overallOpsPerSecond > 1000) {
|
|
1491
|
+
console.log(" 🚀 性能评级: 优秀 (>1000 ops/s)");
|
|
1492
|
+
} else if (overallOpsPerSecond > 500) {
|
|
1493
|
+
console.log(" 👍 性能评级: 良好 (500-1000 ops/s)");
|
|
1494
|
+
} else if (overallOpsPerSecond > 200) {
|
|
1495
|
+
console.log(" ⚠️ 性能评级: 中等 (200-500 ops/s)");
|
|
1496
|
+
} else {
|
|
1497
|
+
console.log(" 🐌 性能评级: 需要优化 (<200 ops/s)");
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
// 导出测试器类
|
|
1503
|
+
export default EnhancedPressureTester;
|
|
1504
|
+
|
|
1505
|
+
// 如果直接运行此文件,则执行测试
|
|
1506
|
+
if (import.meta.url.includes("mysql-test-pressure.js")) {
|
|
1507
|
+
const tester = new EnhancedPressureTester();
|
|
1508
|
+
|
|
1509
|
+
// 解析命令行参数
|
|
1510
|
+
const args = process.argv.slice(2);
|
|
1511
|
+
const enableCache = !args.includes("--no-cache");
|
|
1512
|
+
const iterations =
|
|
1513
|
+
parseInt(args.find((arg) => arg.startsWith("--iterations="))?.split("=")) ||
|
|
1514
|
+
100;
|
|
1515
|
+
|
|
1516
|
+
console.log(
|
|
1517
|
+
`🚀 启动压力测试 (缓存: ${enableCache ? "启用" : "禁用"}, 迭代次数: ${iterations})`,
|
|
1518
|
+
);
|
|
1519
|
+
|
|
1520
|
+
tester
|
|
1521
|
+
.runAllTests(enableCache)
|
|
1522
|
+
.then(() => {
|
|
1523
|
+
console.log("\n🎉 压力测试完成!");
|
|
1524
|
+
process.exit(0);
|
|
1525
|
+
})
|
|
1526
|
+
.catch((error) => {
|
|
1527
|
+
console.error("❌ 压力测试失败:", error);
|
|
1528
|
+
process.exit(1);
|
|
1529
|
+
});
|
|
1530
|
+
}
|