/** * 重新排序数据库中所有表的主键ID * @file reorder-primary-keys.js * @description 将所有表的主键ID重新排序,从1开始升序,同时更新外键引用 */ const { sequelize } = require('./config/database-simple'); const { QueryTypes } = require('sequelize'); // 根据依赖关系确定的处理顺序(被引用的表优先) const TABLE_ORDER = [ 'roles', 'users', 'farms', 'devices', 'products', 'animals', 'alerts', 'orders', 'sensor_data', 'order_items', 'user_roles', 'seeds' ]; // 外键映射关系 const FOREIGN_KEY_MAPPINGS = { 'animals': [{ column: 'farm_id', referencedTable: 'farms' }], 'alerts': [ { column: 'device_id', referencedTable: 'devices' }, { column: 'farm_id', referencedTable: 'farms' } ], 'devices': [{ column: 'farm_id', referencedTable: 'farms' }], 'order_items': [ { column: 'order_id', referencedTable: 'orders' }, { column: 'product_id', referencedTable: 'products' } ], 'orders': [{ column: 'user_id', referencedTable: 'users' }], 'sensor_data': [ { column: 'device_id', referencedTable: 'devices' }, { column: 'farm_id', referencedTable: 'farms' } ], 'user_roles': [ { column: 'role_id', referencedTable: 'roles' }, { column: 'user_id', referencedTable: 'users' } ] }; class IDReorderManager { constructor() { this.idMappings = new Map(); // 存储旧ID到新ID的映射 this.transaction = null; } async reorderAllTables() { this.transaction = await sequelize.transaction(); try { console.log('=== 开始重新排序所有表的主键ID ===\n'); // 临时禁用外键检查 await sequelize.query('SET FOREIGN_KEY_CHECKS = 0', { transaction: this.transaction }); // 按顺序处理每个表 for (const tableName of TABLE_ORDER) { await this.reorderTableIDs(tableName); } // 重新启用外键检查 await sequelize.query('SET FOREIGN_KEY_CHECKS = 1', { transaction: this.transaction }); // 验证数据完整性 await this.verifyIntegrity(); await this.transaction.commit(); console.log('\n✅ 所有表的ID重新排序完成!'); } catch (error) { await this.transaction.rollback(); console.error('❌ ID重新排序失败:', error); throw error; } } async reorderTableIDs(tableName) { try { console.log(`\n🔄 处理表: ${tableName}`); // 检查表是否存在且有数据 const tableExists = await this.checkTableExists(tableName); if (!tableExists) { console.log(`⚠️ 表 ${tableName} 不存在,跳过`); return; } const recordCount = await this.getRecordCount(tableName); if (recordCount === 0) { console.log(`ℹ️ 表 ${tableName} 无数据,跳过`); return; } console.log(` 记录数: ${recordCount}`); // 获取当前所有记录(按ID排序) const records = await sequelize.query( `SELECT * FROM ${tableName} ORDER BY id`, { type: QueryTypes.SELECT, transaction: this.transaction } ); if (records.length === 0) { console.log(` 无记录需要处理`); return; } // 创建ID映射 const oldToNewIdMap = new Map(); records.forEach((record, index) => { const oldId = record.id; const newId = index + 1; oldToNewIdMap.set(oldId, newId); }); // 存储映射供其他表使用 this.idMappings.set(tableName, oldToNewIdMap); // 检查是否需要重新排序 const needsReorder = records.some((record, index) => record.id !== index + 1); if (!needsReorder) { console.log(` ✅ ID已经是连续的,无需重新排序`); return; } console.log(` 🔧 重新排序ID: ${records[0].id}-${records[records.length-1].id} -> 1-${records.length}`); // 更新外键引用(如果有的话) await this.updateForeignKeyReferences(tableName, records); // 创建临时表 const tempTableName = `${tableName}_temp_reorder`; await this.createTempTable(tableName, tempTableName); // 将数据插入临时表(使用新ID) await this.insertDataWithNewIDs(tableName, tempTableName, records, oldToNewIdMap); // 删除原表数据 await sequelize.query( `DELETE FROM ${tableName}`, { transaction: this.transaction } ); // 重置自增ID await sequelize.query( `ALTER TABLE ${tableName} AUTO_INCREMENT = 1`, { transaction: this.transaction } ); // 将临时表数据复制回原表 await this.copyDataFromTempTable(tableName, tempTableName); // 删除临时表 await sequelize.query( `DROP TABLE ${tempTableName}`, { transaction: this.transaction } ); console.log(` ✅ 表 ${tableName} ID重新排序完成`); } catch (error) { console.error(`❌ 处理表 ${tableName} 失败:`, error.message); throw error; } } async checkTableExists(tableName) { const result = await sequelize.query( `SELECT COUNT(*) as count FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = '${tableName}'`, { type: QueryTypes.SELECT, transaction: this.transaction } ); return result[0].count > 0; } async getRecordCount(tableName) { const result = await sequelize.query( `SELECT COUNT(*) as count FROM ${tableName}`, { type: QueryTypes.SELECT, transaction: this.transaction } ); return parseInt(result[0].count); } async updateForeignKeyReferences(tableName, records) { const foreignKeys = FOREIGN_KEY_MAPPINGS[tableName]; if (!foreignKeys) return; console.log(` 🔗 更新外键引用...`); for (const fk of foreignKeys) { const referencedTableMapping = this.idMappings.get(fk.referencedTable); if (!referencedTableMapping) { console.log(` ⚠️ 引用表 ${fk.referencedTable} 的映射不存在,跳过外键 ${fk.column}`); continue; } // 更新外键值 for (const record of records) { const oldForeignKeyValue = record[fk.column]; if (oldForeignKeyValue && referencedTableMapping.has(oldForeignKeyValue)) { const newForeignKeyValue = referencedTableMapping.get(oldForeignKeyValue); record[fk.column] = newForeignKeyValue; } } } } async createTempTable(originalTable, tempTable) { // 先删除临时表(如果存在) try { await sequelize.query( `DROP TABLE IF EXISTS ${tempTable}`, { transaction: this.transaction } ); } catch (error) { // 忽略删除错误 } await sequelize.query( `CREATE TABLE ${tempTable} LIKE ${originalTable}`, { transaction: this.transaction } ); } async insertDataWithNewIDs(originalTable, tempTable, records, idMapping) { if (records.length === 0) return; // 获取表结构 const columns = await sequelize.query( `SELECT COLUMN_NAME FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = '${originalTable}' ORDER BY ordinal_position`, { type: QueryTypes.SELECT, transaction: this.transaction } ); const columnNames = columns.map(col => col.COLUMN_NAME); // 批量插入数据 for (let i = 0; i < records.length; i += 100) { const batch = records.slice(i, i + 100); const values = batch.map((record, batchIndex) => { const newId = i + batchIndex + 1; const values = columnNames.map(col => { if (col === 'id') { return newId; } const value = record[col]; if (value === null || value === undefined) { return 'NULL'; } if (typeof value === 'string') { return `'${value.replace(/'/g, "''")}'`; } if (value instanceof Date) { return `'${value.toISOString().slice(0, 19).replace('T', ' ')}'`; } if (typeof value === 'object') { return `'${JSON.stringify(value).replace(/'/g, "''")}'`; } return value; }); return `(${values.join(', ')})`; }); await sequelize.query( `INSERT INTO ${tempTable} (${columnNames.join(', ')}) VALUES ${values.join(', ')}`, { transaction: this.transaction } ); } } async copyDataFromTempTable(originalTable, tempTable) { await sequelize.query( `INSERT INTO ${originalTable} SELECT * FROM ${tempTable}`, { transaction: this.transaction } ); } async verifyIntegrity() { console.log('\n🔍 验证数据完整性...'); for (const [tableName, foreignKeys] of Object.entries(FOREIGN_KEY_MAPPINGS)) { if (!foreignKeys) continue; for (const fk of foreignKeys) { try { const result = await sequelize.query(` SELECT COUNT(*) as invalid_count FROM ${tableName} t WHERE t.${fk.column} IS NOT NULL AND t.${fk.column} NOT IN ( SELECT id FROM ${fk.referencedTable} WHERE id IS NOT NULL ) `, { type: QueryTypes.SELECT, transaction: this.transaction }); const invalidCount = parseInt(result[0].invalid_count); if (invalidCount > 0) { throw new Error(`表 ${tableName}.${fk.column} 有 ${invalidCount} 个无效的外键引用`); } } catch (error) { console.error(`❌ 完整性检查失败: ${tableName}.${fk.column}`, error.message); throw error; } } } console.log('✅ 数据完整性验证通过'); } } async function reorderPrimaryKeys() { const manager = new IDReorderManager(); await manager.reorderAllTables(); } // 如果直接运行此脚本 if (require.main === module) { reorderPrimaryKeys() .then(() => { console.log('\n🎉 主键ID重新排序完成!'); process.exit(0); }) .catch(error => { console.error('💥 主键ID重新排序失败:', error); process.exit(1); }) .finally(() => { sequelize.close(); }); } module.exports = { reorderPrimaryKeys, IDReorderManager };