-- 仅用于测试环境复制库 meeting_im_sharding_preview。 -- 源库 meeting_im 和注册库 hotgo 不在本脚本修改范围内。 USE `meeting_im_sharding_preview`; -- 1. 共享业务表补充商户维度,复制库全部现有数据归属商户 2。 DELIMITER $$ DROP PROCEDURE IF EXISTS add_preview_tenant_columns$$ CREATE PROCEDURE add_preview_tenant_columns() BEGIN DECLARE done INT DEFAULT 0; DECLARE target_table VARCHAR(128); DECLARE has_id INT DEFAULT 0; DECLARE table_cursor CURSOR FOR SELECT t.table_name FROM information_schema.tables t WHERE t.table_schema = 'meeting_im_sharding_preview' AND t.table_type = 'BASE TABLE' AND t.table_name LIKE 'im\_%' AND NOT EXISTS ( SELECT 1 FROM information_schema.columns c WHERE c.table_schema=t.table_schema AND c.table_name=t.table_name AND c.column_name='tenant_id' ); DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; OPEN table_cursor; table_loop: LOOP FETCH table_cursor INTO target_table; IF done = 1 THEN LEAVE table_loop; END IF; SELECT COUNT(*) INTO has_id FROM information_schema.columns WHERE table_schema='meeting_im_sharding_preview' AND table_name=target_table AND column_name='id'; SET @position_sql = IF(has_id > 0, ' AFTER `id`', ' FIRST'); SET @alter_sql = CONCAT('ALTER TABLE `meeting_im_sharding_preview`.`', target_table, '` ADD COLUMN `tenant_id` BIGINT NOT NULL DEFAULT 2 COMMENT ''商户ID''', @position_sql); PREPARE stmt FROM @alter_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END LOOP; CLOSE table_cursor; END$$ DELIMITER ; CALL add_preview_tenant_columns(); DROP PROCEDURE IF EXISTS add_preview_tenant_columns; -- 2. 群消息路由元数据的唯一性增加商户维度。 ALTER TABLE `im_group_message_shards` DROP PRIMARY KEY, DROP INDEX `uk_im_group_message_shards_table`, DROP INDEX `idx_im_group_message_shards_status`, ADD PRIMARY KEY (`tenant_id`,`shard_month`), ADD UNIQUE INDEX `uk_im_group_message_shards_table` (`tenant_id`,`table_name`), ADD INDEX `idx_im_group_message_shards_status` (`tenant_id`,`status`,`shard_month`); ALTER TABLE `im_group_message_topic_shards` DROP PRIMARY KEY, DROP INDEX `idx_im_group_message_topic_shards_seq`, DROP INDEX `idx_im_group_message_topic_shards_month`, ADD PRIMARY KEY (`tenant_id`,`topic`,`shard_month`), ADD INDEX `idx_im_group_message_topic_shards_seq` (`tenant_id`,`topic`,`max_seq`), ADD INDEX `idx_im_group_message_topic_shards_month` (`tenant_id`,`shard_month`,`topic`); ALTER TABLE `im_group_message_dedup` DROP PRIMARY KEY, DROP INDEX `idx_im_group_message_dedup_topic_seq`, ADD PRIMARY KEY (`tenant_id`,`topic`,`from_user`,`client_msg_id`), ADD INDEX `idx_im_group_message_dedup_topic_seq` (`tenant_id`,`topic`,`seqid`); -- 3. 创建商户 2 的好友与单聊消息分表并复制快照数据。 CREATE TABLE `im_002_friendships` LIKE `im_friendships`; ALTER TABLE `im_002_friendships` MODIFY `tenant_id` BIGINT NOT NULL DEFAULT 2 COMMENT '商户ID'; INSERT INTO `im_002_friendships` SELECT * FROM `im_friendships` WHERE `tenant_id`=2; CREATE TABLE `im_002_messages` LIKE `im_messages`; ALTER TABLE `im_002_messages` MODIFY `tenant_id` BIGINT NOT NULL DEFAULT 2 COMMENT '商户ID'; INSERT INTO `im_002_messages` SELECT * FROM `im_messages` WHERE `tenant_id`=2; -- 4. 将所有现存标准月表复制为商户 2 的月表。 DELIMITER $$ DROP PROCEDURE IF EXISTS migrate_preview_group_month_tables$$ CREATE PROCEDURE migrate_preview_group_month_tables() BEGIN DECLARE done INT DEFAULT 0; DECLARE source_table VARCHAR(64); DECLARE month_code CHAR(6); DECLARE target_table VARCHAR(64); DECLARE month_cursor CURSOR FOR SELECT table_name FROM information_schema.tables WHERE table_schema='meeting_im_sharding_preview' AND table_name REGEXP '^im_group_messages_[0-9]{6}$'; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; OPEN month_cursor; month_loop: LOOP FETCH month_cursor INTO source_table; IF done = 1 THEN LEAVE month_loop; END IF; SET month_code = RIGHT(source_table, 6); SET target_table = CONCAT('im_002_group_messages_', month_code); SET @create_sql = CONCAT('CREATE TABLE `', target_table, '` LIKE `', source_table, '`'); PREPARE stmt FROM @create_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @alter_sql = CONCAT('ALTER TABLE `', target_table, '` MODIFY `tenant_id` BIGINT NOT NULL DEFAULT 2 COMMENT ''商户ID'''); PREPARE stmt FROM @alter_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @copy_sql = CONCAT('INSERT INTO `', target_table, '` SELECT * FROM `', source_table, '` WHERE `tenant_id`=2'); PREPARE stmt FROM @copy_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END LOOP; CLOSE month_cursor; END$$ DELIMITER ; CALL migrate_preview_group_month_tables(); DROP PROCEDURE IF EXISTS migrate_preview_group_month_tables; UPDATE `im_group_message_shards` SET `table_name`=CONCAT('im_002_group_messages_',`shard_month`) WHERE `tenant_id`=2; -- 5. 补齐 default 商户(ID=1,分表编号 001)的核心分表。 -- tinode 是 default 商户的原库,只从该库读取,不修改原表。 CREATE TABLE `im_001_friendships` LIKE `im_friendships`; ALTER TABLE `im_001_friendships` MODIFY `tenant_id` BIGINT NOT NULL DEFAULT 1 COMMENT '商户ID'; INSERT INTO `im_001_friendships` ( `id`,`tenant_id`,`user_id`,`peer_id`,`status`,`remark`,`topic`,`created_at`,`updated_at` ) SELECT `id`,1,`user_id`,`peer_id`,`status`,`remark`,`topic`,`created_at`,`updated_at` FROM `tinode`.`im_friendships`; CREATE TABLE `im_001_messages` LIKE `im_messages`; ALTER TABLE `im_001_messages` MODIFY `tenant_id` BIGINT NOT NULL DEFAULT 1 COMMENT '商户ID'; INSERT INTO `im_001_messages` ( `id`,`tenant_id`,`createdat`,`updatedat`,`deletedat`,`delid`,`seqid`,`topic`,`from`, `client_msg_id`,`version`,`recalled_at`,`recalled_by`,`head`,`content`,`content_nonce`,`key_version` ) SELECT `id`,1,`createdat`,`updatedat`,`deletedat`,`delid`,`seqid`,`topic`,`from`, `client_msg_id`,`version`,`recalled_at`,`recalled_by`,`head`,`content`,`content_nonce`,`key_version` FROM `tinode`.`im_messages`; DELIMITER $$ DROP PROCEDURE IF EXISTS migrate_default_group_month_tables$$ CREATE PROCEDURE migrate_default_group_month_tables() BEGIN DECLARE done INT DEFAULT 0; DECLARE source_table VARCHAR(64); DECLARE month_code CHAR(6); DECLARE target_table VARCHAR(64); DECLARE month_cursor CURSOR FOR SELECT table_name FROM information_schema.tables WHERE table_schema='tinode' AND table_name REGEXP '^im_group_messages_[0-9]{6}$'; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; OPEN month_cursor; month_loop: LOOP FETCH month_cursor INTO source_table; IF done = 1 THEN LEAVE month_loop; END IF; SET month_code = RIGHT(source_table, 6); SET target_table = CONCAT('im_001_group_messages_', month_code); SET @create_sql = CONCAT('CREATE TABLE `', target_table, '` LIKE `im_group_messages_', month_code, '`'); PREPARE stmt FROM @create_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @alter_sql = CONCAT('ALTER TABLE `', target_table, '` MODIFY `tenant_id` BIGINT NOT NULL DEFAULT 1 COMMENT ''商户ID'''); PREPARE stmt FROM @alter_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @copy_sql = CONCAT( 'INSERT INTO `', target_table, '` (`tenant_id`,`message_id`,`topic`,`group_id`,`seqid`,', '`from_user`,`client_msg_id`,`msg_type`,`status`,`content`,`content_nonce`,`key_version`,', '`head`,`mentioned_user_ids`,`mention_all`,`reply_seqid`,`root_seqid`,`file_count`,', '`content_size`,`has_risk`,`audit_status`,`version`,`recalled_at`,`recalled_by`,', '`deleted_at`,`expires_at`,`created_at`,`updated_at`) ', 'SELECT 1,`message_id`,`topic`,`group_id`,`seqid`,`from_user`,`client_msg_id`,`msg_type`,', '`status`,`content`,`content_nonce`,`key_version`,`head`,`mentioned_user_ids`,`mention_all`,', '`reply_seqid`,`root_seqid`,`file_count`,`content_size`,`has_risk`,`audit_status`,`version`,', '`recalled_at`,`recalled_by`,`deleted_at`,`expires_at`,`created_at`,`updated_at` ', 'FROM `tinode`.`', source_table, '`' ); PREPARE stmt FROM @copy_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END LOOP; CLOSE month_cursor; END$$ DELIMITER ; CALL migrate_default_group_month_tables(); DROP PROCEDURE IF EXISTS migrate_default_group_month_tables; INSERT INTO `im_group_message_shards` ( `tenant_id`,`shard_month`,`table_name`,`shard_start`,`shard_end`,`status`,`row_count`, `min_created_at`,`max_created_at`,`created_at`,`updated_at` ) SELECT 1,`shard_month`,CONCAT('im_001_group_messages_',`shard_month`),`shard_start`,`shard_end`, `status`,`row_count`,`min_created_at`,`max_created_at`,`created_at`,`updated_at` FROM `tinode`.`im_group_message_shards`; INSERT INTO `im_group_message_topic_shards` ( `tenant_id`,`topic`,`shard_month`,`min_seq`,`max_seq`,`message_count`, `min_created_at`,`max_created_at`,`created_at`,`updated_at` ) SELECT 1,`topic`,`shard_month`,`min_seq`,`max_seq`,`message_count`, `min_created_at`,`max_created_at`,`created_at`,`updated_at` FROM `tinode`.`im_group_message_topic_shards`; INSERT INTO `im_group_message_dedup` ( `tenant_id`,`topic`,`from_user`,`client_msg_id`,`seqid`,`shard_month`, `message_id`,`status`,`expires_at`,`created_at`,`updated_at` ) SELECT 1,`topic`,`from_user`,`client_msg_id`,`seqid`,`shard_month`, `message_id`,`status`,`expires_at`,`created_at`,`updated_at` FROM `tinode`.`im_group_message_dedup`; -- 以实际复制后的物理表为准刷新统计,避免源库路由统计滞后。 DELIMITER $$ DROP PROCEDURE IF EXISTS refresh_default_group_shard_stats$$ CREATE PROCEDURE refresh_default_group_shard_stats() BEGIN DECLARE done INT DEFAULT 0; DECLARE month_code CHAR(6); DECLARE target_table VARCHAR(64); DECLARE shard_cursor CURSOR FOR SELECT `shard_month` FROM `im_group_message_shards` WHERE `tenant_id`=1; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; OPEN shard_cursor; shard_loop: LOOP FETCH shard_cursor INTO month_code; IF done = 1 THEN LEAVE shard_loop; END IF; SET target_table = CONCAT('im_001_group_messages_', month_code); SET @stats_sql = CONCAT( 'UPDATE `im_group_message_shards` SET ', '`row_count`=(SELECT COUNT(*) FROM `', target_table, '`),', '`min_created_at`=(SELECT MIN(`created_at`) FROM `', target_table, '`),', '`max_created_at`=(SELECT MAX(`created_at`) FROM `', target_table, '`) ', 'WHERE `tenant_id`=1 AND `shard_month`=''', month_code, '''' ); PREPARE stmt FROM @stats_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END LOOP; CLOSE shard_cursor; END$$ DELIMITER ; CALL refresh_default_group_shard_stats(); DROP PROCEDURE IF EXISTS refresh_default_group_shard_stats; -- 6. 验证:missing_tenant_id 必须为 0,两个商户的分表与路由均存在。 SELECT COUNT(*) AS missing_tenant_id FROM information_schema.tables t WHERE t.table_schema='meeting_im_sharding_preview' AND t.table_type='BASE TABLE' AND t.table_name LIKE 'im\_%' AND NOT EXISTS ( SELECT 1 FROM information_schema.columns c WHERE c.table_schema=t.table_schema AND c.table_name=t.table_name AND c.column_name='tenant_id' ); SELECT 'friendships' AS data_type, (SELECT COUNT(*) FROM `im_friendships` WHERE `tenant_id`=2) AS source_count, (SELECT COUNT(*) FROM `im_002_friendships`) AS target_count UNION ALL SELECT 'messages', (SELECT COUNT(*) FROM `im_messages` WHERE `tenant_id`=2), (SELECT COUNT(*) FROM `im_002_messages`); SELECT s.`shard_month`,s.`table_name`,s.`status`,s.`row_count` FROM `im_group_message_shards` s WHERE s.`tenant_id` IN (1,2) ORDER BY s.`tenant_id`,s.`shard_month`; SELECT t.table_name FROM information_schema.tables t WHERE t.table_schema='meeting_im_sharding_preview' AND t.table_name REGEXP '^im_(001|002)_(friendships|messages|group_messages_[0-9]{6})$' ORDER BY t.table_name;