-- ============================================================================ -- pat_med_outhosp 系列表按 serialnum 加索引(配合门急诊按天全量采集) -- -- 为什么要加: -- 1) 采集任务每个半小时把当天上万个诊疗记录整轮拉一遍,靠"整页批量查重"过滤已入库的, -- 查重 SQL 是 where serialnum in (500个)。没有索引就是全表扫描,季度分表几百万行时一次就很慢; -- 2) 唯一索引是"不重复"的最后一道防线:极端并发、断点回退、分页偏移都不会插出重复记录。 -- -- 执行顺序: -- 第 1 步 看清有哪些表、现在有没有索引 -- 第 2 步 查重复数据(有重复的话第 3 步加唯一索引会失败,先处理重复) -- 第 3 步 加唯一索引 uk_serialnum(脚本会自动跳过已有唯一索引的表) -- 第 4 步 校验结果 -- -- 注意:ADD INDEX 走 online DDL(ALGORITHM=INPLACE, LOCK=NONE),不阻塞读写, -- 但大表仍会消耗 IO,建议放在业务低峰执行。数据量大时分表一张一张来也可以。 -- 如果某张表报错,先单独执行第 2 步看它有没有重复数据。 -- ============================================================================ -- ---------------------------------------------------------------------------- -- 第 1 步:看有哪些 pat_med_outhosp 表,以及 serialnum 上的索引情况 -- has_unique_index = 1 表示已经有唯一索引(不用再加) -- has_any_index = 0 表示完全没索引(查询会很慢,必须加) -- ---------------------------------------------------------------------------- SELECT t.TABLE_NAME, t.TABLE_ROWS, (SELECT COUNT(*) FROM information_schema.STATISTICS s WHERE s.TABLE_SCHEMA = t.TABLE_SCHEMA AND s.TABLE_NAME = t.TABLE_NAME AND s.COLUMN_NAME = 'serialnum' AND s.NON_UNIQUE = 0) AS has_unique_index, (SELECT COUNT(*) FROM information_schema.STATISTICS s WHERE s.TABLE_SCHEMA = t.TABLE_SCHEMA AND s.TABLE_NAME = t.TABLE_NAME AND s.COLUMN_NAME = 'serialnum') AS has_any_index FROM information_schema.TABLES t WHERE t.TABLE_SCHEMA = DATABASE() AND (t.TABLE_NAME = 'pat_med_outhosp' OR t.TABLE_NAME REGEXP '^pat_med_outhosp_[0-9]{4,8}$') ORDER BY t.TABLE_NAME; -- ---------------------------------------------------------------------------- -- 第 2 步:查重复的 serialnum(有重复就必须先清理,否则唯一索引建不上) -- 执行后先看 dup_serialnum_total,等于 0 就可以直接进第 3 步 -- ---------------------------------------------------------------------------- DROP PROCEDURE IF EXISTS check_outhosp_serialnum_dup; DELIMITER $$ CREATE PROCEDURE check_outhosp_serialnum_dup() BEGIN DECLARE done INT DEFAULT 0; DECLARE tbl VARCHAR(64); DECLARE cur CURSOR FOR SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND (TABLE_NAME = 'pat_med_outhosp' OR TABLE_NAME REGEXP '^pat_med_outhosp_[0-9]{4,8}$'); DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; DROP TEMPORARY TABLE IF EXISTS tmp_dup_serialnum; CREATE TEMPORARY TABLE tmp_dup_serialnum ( table_name VARCHAR(64), serialnum VARCHAR(64), cnt INT ); OPEN cur; read_loop: LOOP FETCH cur INTO tbl; IF done THEN LEAVE read_loop; END IF; SET @s = CONCAT('INSERT INTO tmp_dup_serialnum SELECT ''', tbl, ''', serialnum, COUNT(*) FROM `', tbl, '` WHERE serialnum IS NOT NULL AND serialnum <> ''''', ' GROUP BY serialnum HAVING COUNT(*) > 1'); PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st; END LOOP; CLOSE cur; SELECT * FROM tmp_dup_serialnum ORDER BY cnt DESC LIMIT 200; SELECT COUNT(*) AS dup_serialnum_total FROM tmp_dup_serialnum; END$$ DELIMITER ; CALL check_outhosp_serialnum_dup(); -- 查完可以删掉这个过程:DROP PROCEDURE IF EXISTS check_outhosp_serialnum_dup; -- 如果有重复,按下面的思路清理(保留 id 最小的那条,先备份再删): -- CREATE TABLE bak_dup_<表名>_<日期> AS -- SELECT * FROM `pat_med_outhosp_20261012` -- WHERE serialnum IN ('重复的serialnum1','重复的serialnum2'); -- DELETE t1 FROM `pat_med_outhosp_20261012` t1 -- JOIN `pat_med_outhosp_20261012` t2 -- ON t1.serialnum = t2.serialnum AND t1.id > t2.id -- WHERE t1.serialnum IN ('重复的serialnum1','重复的serialnum2'); -- ---------------------------------------------------------------------------- -- 第 3 步:给所有 pat_med_outhosp 表加唯一索引 uk_serialnum -- 已有唯一索引的表自动跳过;如果之前建过同名的普通索引 idx_serialnum,会先删掉再建唯一的 -- ---------------------------------------------------------------------------- DROP PROCEDURE IF EXISTS add_outhosp_serialnum_index; DELIMITER $$ CREATE PROCEDURE add_outhosp_serialnum_index() BEGIN DECLARE done INT DEFAULT 0; DECLARE tbl VARCHAR(64); DECLARE has_unique INT DEFAULT 0; DECLARE has_normal INT DEFAULT 0; DECLARE cur CURSOR FOR SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND (TABLE_NAME = 'pat_med_outhosp' OR TABLE_NAME REGEXP '^pat_med_outhosp_[0-9]{4,8}$'); DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; OPEN cur; add_loop: LOOP FETCH cur INTO tbl; IF done THEN LEAVE add_loop; END IF; SELECT COUNT(*) INTO has_unique FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = tbl AND COLUMN_NAME = 'serialnum' AND NON_UNIQUE = 0; IF has_unique > 0 THEN SELECT CONCAT('[跳过] ', tbl, ' 已有 serialnum 唯一索引') AS msg; ELSE -- 先清掉可能存在的非唯一索引(保留唯一索引一个就够) SELECT COUNT(*) INTO has_normal FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = tbl AND COLUMN_NAME = 'serialnum' AND NON_UNIQUE = 1 AND INDEX_NAME = 'idx_serialnum'; IF has_normal > 0 THEN SET @s = CONCAT('ALTER TABLE `', tbl, '` DROP INDEX idx_serialnum'); PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st; END IF; SET @s = CONCAT('ALTER TABLE `', tbl, '` ADD UNIQUE INDEX uk_serialnum (serialnum)'); PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st; SELECT CONCAT('[完成] ', tbl, ' 已添加唯一索引 uk_serialnum') AS msg; END IF; END LOOP; CLOSE cur; END$$ DELIMITER ; CALL add_outhosp_serialnum_index(); -- 完成后删掉过程:DROP PROCEDURE IF EXISTS add_outhosp_serialnum_index; -- 如果某张表报 "Duplicate entry ... for key 'uk_serialnum'",说明它还有重复数据, -- 回到第 2 步清理该表后,单独执行: -- ALTER TABLE `pat_med_outhosp_20261012` ADD UNIQUE INDEX uk_serialnum (serialnum); -- ---------------------------------------------------------------------------- -- 第 4 步:校验(每张表都应该 has_unique_index = 1) -- ---------------------------------------------------------------------------- SELECT t.TABLE_NAME, (SELECT COUNT(*) FROM information_schema.STATISTICS s WHERE s.TABLE_SCHEMA = t.TABLE_SCHEMA AND s.TABLE_NAME = t.TABLE_NAME AND s.COLUMN_NAME = 'serialnum' AND s.NON_UNIQUE = 0) AS has_unique_index FROM information_schema.TABLES t WHERE t.TABLE_SCHEMA = DATABASE() AND (t.TABLE_NAME = 'pat_med_outhosp' OR t.TABLE_NAME REGEXP '^pat_med_outhosp_[0-9]{4,8}$') ORDER BY t.TABLE_NAME;