/** * 分析数据库中表之间的外键关系 * @file analyze-foreign-keys.js * @description 识别所有外键约束和引用关系,为ID重排做准备 */ const { sequelize } = require('./config/database-simple'); const { QueryTypes } = require('sequelize'); async function analyzeForeignKeys() { try { console.log('=== 分析数据库外键关系 ===\n'); // 获取所有外键约束 const foreignKeys = await sequelize.query(` SELECT kcu.TABLE_NAME as table_name, kcu.COLUMN_NAME as column_name, kcu.REFERENCED_TABLE_NAME as referenced_table, kcu.REFERENCED_COLUMN_NAME as referenced_column, rc.CONSTRAINT_NAME as constraint_name, rc.UPDATE_RULE as update_rule, rc.DELETE_RULE as delete_rule FROM information_schema.KEY_COLUMN_USAGE kcu JOIN information_schema.REFERENTIAL_CONSTRAINTS rc ON kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME AND kcu.TABLE_SCHEMA = rc.CONSTRAINT_SCHEMA WHERE kcu.TABLE_SCHEMA = DATABASE() AND kcu.REFERENCED_TABLE_NAME IS NOT NULL ORDER BY kcu.TABLE_NAME, kcu.COLUMN_NAME `, { type: QueryTypes.SELECT }); console.log(`发现 ${foreignKeys.length} 个外键关系:\n`); const relationshipMap = new Map(); const tablesWithForeignKeys = new Set(); const referencedTables = new Set(); foreignKeys.forEach(fk => { const key = `${fk.table_name}.${fk.column_name}`; const reference = `${fk.referenced_table}.${fk.referenced_column}`; relationshipMap.set(key, { table: fk.table_name, column: fk.column_name, referencedTable: fk.referenced_table, referencedColumn: fk.referenced_column, constraintName: fk.constraint_name, updateRule: fk.update_rule, deleteRule: fk.delete_rule }); tablesWithForeignKeys.add(fk.table_name); referencedTables.add(fk.referenced_table); console.log(`🔗 ${fk.table_name}.${fk.column_name} -> ${fk.referenced_table}.${fk.referenced_column}`); console.log(` 约束名: ${fk.constraint_name}`); console.log(` 更新规则: ${fk.update_rule}`); console.log(` 删除规则: ${fk.delete_rule}`); console.log(''); }); // 分析每个外键字段的数据分布 console.log('\n=== 外键字段数据分布 ===\n'); const foreignKeyStats = []; for (const [key, relationship] of relationshipMap) { const { table, column, referencedTable, referencedColumn } = relationship; try { // 获取外键字段的统计信息 const stats = await sequelize.query(` SELECT COUNT(*) as total_count, COUNT(DISTINCT ${column}) as unique_count, MIN(${column}) as min_value, MAX(${column}) as max_value, COUNT(CASE WHEN ${column} IS NULL THEN 1 END) as null_count FROM ${table} `, { type: QueryTypes.SELECT }); const stat = stats[0]; // 检查引用完整性 const integrityCheck = await sequelize.query(` SELECT COUNT(*) as invalid_references FROM ${table} t WHERE t.${column} IS NOT NULL AND t.${column} NOT IN ( SELECT ${referencedColumn} FROM ${referencedTable} WHERE ${referencedColumn} IS NOT NULL ) `, { type: QueryTypes.SELECT }); const invalidRefs = parseInt(integrityCheck[0].invalid_references); const fkStat = { table, column, referencedTable, referencedColumn, totalCount: parseInt(stat.total_count), uniqueCount: parseInt(stat.unique_count), minValue: stat.min_value, maxValue: stat.max_value, nullCount: parseInt(stat.null_count), invalidReferences: invalidRefs, hasIntegrityIssues: invalidRefs > 0 }; foreignKeyStats.push(fkStat); console.log(`📊 ${table}.${column} -> ${referencedTable}.${referencedColumn}:`); console.log(` - 总记录数: ${fkStat.totalCount}`); console.log(` - 唯一值数: ${fkStat.uniqueCount}`); console.log(` - 值范围: ${fkStat.minValue} - ${fkStat.maxValue}`); console.log(` - NULL值数: ${fkStat.nullCount}`); console.log(` - 无效引用: ${fkStat.invalidReferences}`); console.log(` - 完整性问题: ${fkStat.hasIntegrityIssues ? '是' : '否'}`); console.log(''); } catch (error) { console.log(`❌ ${table}.${column}: 分析失败 - ${error.message}`); } } // 生成依赖关系图 console.log('\n=== 表依赖关系 ===\n'); const dependencyGraph = new Map(); foreignKeys.forEach(fk => { if (!dependencyGraph.has(fk.table_name)) { dependencyGraph.set(fk.table_name, new Set()); } dependencyGraph.get(fk.table_name).add(fk.referenced_table); }); // 计算更新顺序(拓扑排序) const updateOrder = []; const visited = new Set(); const visiting = new Set(); function topologicalSort(table) { if (visiting.has(table)) { console.log(`⚠️ 检测到循环依赖: ${table}`); return; } if (visited.has(table)) { return; } visiting.add(table); const dependencies = dependencyGraph.get(table) || new Set(); for (const dep of dependencies) { topologicalSort(dep); } visiting.delete(table); visited.add(table); updateOrder.push(table); } // 对所有表进行拓扑排序 const allTables = new Set([...tablesWithForeignKeys, ...referencedTables]); for (const table of allTables) { topologicalSort(table); } console.log('建议的ID重排顺序(被引用的表优先):'); updateOrder.reverse().forEach((table, index) => { const deps = dependencyGraph.get(table); const depList = deps ? Array.from(deps).join(', ') : '无'; console.log(`${index + 1}. ${table} (依赖: ${depList})`); }); // 汇总报告 console.log('\n=== 汇总报告 ==='); console.log(`外键关系总数: ${foreignKeys.length}`); console.log(`涉及外键的表: ${tablesWithForeignKeys.size}`); console.log(`被引用的表: ${referencedTables.size}`); const tablesWithIssues = foreignKeyStats.filter(stat => stat.hasIntegrityIssues); if (tablesWithIssues.length > 0) { console.log(`\n⚠️ 发现完整性问题的表:`); tablesWithIssues.forEach(stat => { console.log(`- ${stat.table}.${stat.column}: ${stat.invalidReferences} 个无效引用`); }); } else { console.log('\n✅ 所有外键关系完整性正常'); } return { foreignKeys, foreignKeyStats, updateOrder: updateOrder.reverse(), relationshipMap, tablesWithIssues }; } catch (error) { console.error('分析外键关系失败:', error); throw error; } finally { await sequelize.close(); } } // 如果直接运行此脚本 if (require.main === module) { analyzeForeignKeys() .then(() => { console.log('\n分析完成!'); process.exit(0); }) .catch(error => { console.error('分析失败:', error); process.exit(1); }); } module.exports = { analyzeForeignKeys };