feat: 优化设备位置冲突检测和前端虚拟滚动
为设备表添加复合索引优化位置冲突检测性能 在设备位置检查中添加悲观锁防止竞态条件 添加 react-window 和 react-virtualized-auto-sizer 支持虚拟滚动
This commit is contained in:
@@ -103,6 +103,9 @@ const Device = sequelize.define(
|
|||||||
{ fields: ['createdAt'] },
|
{ fields: ['createdAt'] },
|
||||||
{ fields: ['status', 'type'] },
|
{ fields: ['status', 'type'] },
|
||||||
{ fields: ['name'] },
|
{ fields: ['name'] },
|
||||||
|
// 优化位置冲突检测的复合索引
|
||||||
|
{ fields: ['rackId', 'position'] },
|
||||||
|
{ fields: ['rackId', 'position', 'isIdle'] },
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ async function checkPositionAvailable(
|
|||||||
|
|
||||||
if (transaction) {
|
if (transaction) {
|
||||||
queryOptions.transaction = transaction;
|
queryOptions.transaction = transaction;
|
||||||
|
// 使用悲观锁防止竞态条件
|
||||||
|
queryOptions.lock = transaction.LOCK ? transaction.LOCK.UPDATE : 'UPDATE';
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingDevices = await Device.findAll(queryOptions);
|
const existingDevices = await Device.findAll(queryOptions);
|
||||||
@@ -126,6 +128,8 @@ async function checkBatchPositions(rackId, devices, excludeDeviceIds = [], trans
|
|||||||
|
|
||||||
if (transaction) {
|
if (transaction) {
|
||||||
queryOptions.transaction = transaction;
|
queryOptions.transaction = transaction;
|
||||||
|
// 使用悲观锁防止竞态条件
|
||||||
|
queryOptions.lock = transaction.LOCK ? transaction.LOCK.UPDATE : 'UPDATE';
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingDevices = await Device.findAll(queryOptions);
|
const existingDevices = await Device.findAll(queryOptions);
|
||||||
|
|||||||
@@ -1033,6 +1033,8 @@ async function checkPositionAvailable(
|
|||||||
|
|
||||||
if (transaction) {
|
if (transaction) {
|
||||||
queryOptions.transaction = transaction;
|
queryOptions.transaction = transaction;
|
||||||
|
// 使用悲观锁防止竞态条件
|
||||||
|
queryOptions.lock = transaction.LOCK ? transaction.LOCK.UPDATE : 'UPDATE';
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingDevices = await Device.findAll(queryOptions);
|
const existingDevices = await Device.findAll(queryOptions);
|
||||||
|
|||||||
@@ -95,6 +95,11 @@ const migrations = [
|
|||||||
description: '为 deviceFields 表添加 isSystem 字段,标记系统字段不可删除',
|
description: '为 deviceFields 表添加 isSystem 字段,标记系统字段不可删除',
|
||||||
migrate: migrateDeviceFieldsIsSystem,
|
migrate: migrateDeviceFieldsIsSystem,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: '设备位置索引优化',
|
||||||
|
description: '为 devices 表添加复合索引,优化位置冲突检测和悲观锁性能',
|
||||||
|
migrate: migrateDevicePositionIndexes,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
async function runMigrations() {
|
async function runMigrations() {
|
||||||
@@ -724,6 +729,48 @@ async function migrateIdleDeviceAndBusiness() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function migrateDevicePositionIndexes() {
|
||||||
|
const dialect = sequelize.getDialect();
|
||||||
|
const tableName = 'devices';
|
||||||
|
|
||||||
|
if (!(await tableExists(tableName))) {
|
||||||
|
console.log(` ${tableName} 表不存在,跳过`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(` → 为 ${tableName} 表添加复合索引 rackId_position...`);
|
||||||
|
try {
|
||||||
|
if (dialect === 'sqlite') {
|
||||||
|
await sequelize.query(`CREATE INDEX IF NOT EXISTS devices_rackId_position ON ${tableName}(rackId, position)`);
|
||||||
|
} else {
|
||||||
|
await sequelize.query(`CREATE INDEX IF NOT EXISTS \`devices_rackId_position\` ON \`${tableName}\`(\`rackId\`, \`position\`)`);
|
||||||
|
}
|
||||||
|
console.log(' ✓ 索引创建成功');
|
||||||
|
} catch (error) {
|
||||||
|
if (error.message.includes('already exists') || error.message.includes('Duplicate key name')) {
|
||||||
|
console.log(' → 索引已存在,跳过');
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(` → 为 ${tableName} 表添加复合索引 rackId_position_isIdle...`);
|
||||||
|
try {
|
||||||
|
if (dialect === 'sqlite') {
|
||||||
|
await sequelize.query(`CREATE INDEX IF NOT EXISTS devices_rackId_position_isIdle ON ${tableName}(rackId, position, isIdle)`);
|
||||||
|
} else {
|
||||||
|
await sequelize.query(`CREATE INDEX IF NOT EXISTS \`devices_rackId_position_isIdle\` ON \`${tableName}\`(\`rackId\`, \`position\`, \`isIdle\`)`);
|
||||||
|
}
|
||||||
|
console.log(' ✓ 索引创建成功');
|
||||||
|
} catch (error) {
|
||||||
|
if (error.message.includes('already exists') || error.message.includes('Duplicate key name')) {
|
||||||
|
console.log(' → 索引已存在,跳过');
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 执行迁移
|
// 执行迁移
|
||||||
runMigrations().catch(error => {
|
runMigrations().catch(error => {
|
||||||
console.error('迁移执行失败:', error);
|
console.error('迁移执行失败:', error);
|
||||||
|
|||||||
Generated
+22
@@ -21,6 +21,8 @@
|
|||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-router-dom": "^6.15.0",
|
"react-router-dom": "^6.15.0",
|
||||||
"react-transition-group": "^4.4.5",
|
"react-transition-group": "^4.4.5",
|
||||||
|
"react-virtualized-auto-sizer": "^2.0.3",
|
||||||
|
"react-window": "^2.2.7",
|
||||||
"reactflow": "^11.11.4",
|
"reactflow": "^11.11.4",
|
||||||
"styled-components": "^6.3.9",
|
"styled-components": "^6.3.9",
|
||||||
"swr": "^2.4.0",
|
"swr": "^2.4.0",
|
||||||
@@ -7691,6 +7693,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-virtualized-auto-sizer": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-nonmCSUIh5HtbzazGcQ1NhnMFps/ZBu/UKJyhCt0Fhi7ondLAUZNETtRCWM8RWYZDzVlMYOQGgBmIxUutIhqgw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-window": {
|
||||||
|
"version": "2.2.7",
|
||||||
|
"resolved": "https://registry.npmmirror.com/react-window/-/react-window-2.2.7.tgz",
|
||||||
|
"integrity": "sha512-SH5nvfUQwGHYyriDUAOt7wfPsfG9Qxd6OdzQxl5oQ4dsSsUicqQvjV7dR+NqZ4coY0fUn3w1jnC5PwzIUWEg5w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/reactflow": {
|
"node_modules/reactflow": {
|
||||||
"version": "11.11.4",
|
"version": "11.11.4",
|
||||||
"resolved": "https://registry.npmmirror.com/reactflow/-/reactflow-11.11.4.tgz",
|
"resolved": "https://registry.npmmirror.com/reactflow/-/reactflow-11.11.4.tgz",
|
||||||
|
|||||||
@@ -26,6 +26,8 @@
|
|||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-router-dom": "^6.15.0",
|
"react-router-dom": "^6.15.0",
|
||||||
"react-transition-group": "^4.4.5",
|
"react-transition-group": "^4.4.5",
|
||||||
|
"react-virtualized-auto-sizer": "^2.0.3",
|
||||||
|
"react-window": "^2.2.7",
|
||||||
"reactflow": "^11.11.4",
|
"reactflow": "^11.11.4",
|
||||||
"styled-components": "^6.3.9",
|
"styled-components": "^6.3.9",
|
||||||
"swr": "^2.4.0",
|
"swr": "^2.4.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user