diff --git a/docs/sql/IM-业务表增加租户字段-仅本地.sql b/docs/sql/IM-业务表增加租户字段-仅本地.sql new file mode 100644 index 0000000..ca65f04 --- /dev/null +++ b/docs/sql/IM-业务表增加租户字段-仅本地.sql @@ -0,0 +1,92 @@ +-- 仅用于本地环境:为 IM 业务表补充 tenant_id 字段。 +-- +-- 本脚本只修改表结构并回填现有数据,不修改索引、唯一约束或应用代码, +-- 也不会启用多商户共库能力。 + +USE `tinode`; + +DELIMITER $$ + +DROP PROCEDURE IF EXISTS add_im_tenant_columns$$ +CREATE PROCEDURE add_im_tenant_columns(IN target_schema VARCHAR(64), IN default_tenant_id BIGINT) +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 = target_schema + 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 = target_schema + 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 `', target_schema, '`.`', target_table, + '` ADD COLUMN `tenant_id` BIGINT NOT NULL DEFAULT ', + default_tenant_id, + ' COMMENT ''租户ID''', + @position_sql + ); + PREPARE alter_stmt FROM @alter_sql; + EXECUTE alter_stmt; + DEALLOCATE PREPARE alter_stmt; + END LOOP; + CLOSE table_cursor; +END$$ + +DELIMITER ; + +-- 本地默认业务库,现有数据归属 tenant_id=1。 +CALL add_im_tenant_columns('tinode', 1); + +-- 本地独立业务库,现有数据归属 tenant_id=2。 +CALL add_im_tenant_columns('meeting_im', 2); + +DROP PROCEDURE IF EXISTS add_im_tenant_columns; + +-- 验证:以下结果中 missing_count 应全部为 0。 +SELECT 'tinode' AS db_name, COUNT(*) AS missing_count +FROM information_schema.tables t +WHERE t.table_schema = 'tinode' + 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' + ) +UNION ALL +SELECT 'meeting_im', COUNT(*) +FROM information_schema.tables t +WHERE t.table_schema = 'meeting_im' + 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' + ); diff --git a/docs/sql/IM-共库核心分表-仅本地.sql b/docs/sql/IM-共库核心分表-仅本地.sql new file mode 100644 index 0000000..bd10a7b --- /dev/null +++ b/docs/sql/IM-共库核心分表-仅本地.sql @@ -0,0 +1,173 @@ +-- 仅用于本地环境:将高数据量表迁移为“共库、按商户分表”。 +-- 目标命名: +-- im_001_friendships +-- im_001_messages +-- im_001_group_messages_YYYYMM +-- 测试环境不得执行本文件。 + +-- 1. 商户编号改为事务序列分配,下一编号按当前最大 ID 顺序递增。 +USE `hotgo`; + +CREATE TABLE IF NOT EXISTS `im_tenant_id_sequence` ( + `sequence_name` VARCHAR(32) NOT NULL, + `next_id` BIGINT NOT NULL, + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + PRIMARY KEY (`sequence_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +INSERT INTO `im_tenant_id_sequence` (`sequence_name`, `next_id`) +SELECT 'tenant', COALESCE(MAX(`id`), 0) + 1 FROM `im_tenant` +ON DUPLICATE KEY UPDATE `next_id` = GREATEST(`next_id`, VALUES(`next_id`)); + +-- 禁止绕过序列表使用 AUTO_INCREMENT 生成跳号或历史大整数编号。 +ALTER TABLE `hg_admin_member` DROP FOREIGN KEY `fk_hg_admin_member_im_tenant`; +ALTER TABLE `im_tenant_config` DROP FOREIGN KEY `fk_im_tenant_config_tenant`; +ALTER TABLE `im_tenant` MODIFY `id` BIGINT NOT NULL; +ALTER TABLE `hg_admin_member` ADD CONSTRAINT `fk_hg_admin_member_im_tenant` + FOREIGN KEY (`tenant_id`) REFERENCES `im_tenant` (`id`); +ALTER TABLE `im_tenant_config` ADD CONSTRAINT `fk_im_tenant_config_tenant` + FOREIGN KEY (`tenant_id`) REFERENCES `im_tenant` (`id`); + +UPDATE `im_tenant` AS target +JOIN `im_tenant` AS shared ON shared.`code`='default' +SET target.`db_name`=shared.`db_name`, target.`db_dsn`=shared.`db_dsn` +WHERE target.`id`<>shared.`id`; + +-- 2. 月表路由元数据改为以 tenant_id 为第一维度。 +USE `tinode`; + +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. 创建好友与单聊消息分表并迁移现有数据。 +CREATE TABLE IF NOT EXISTS `im_001_friendships` LIKE `im_friendships`; +ALTER TABLE `im_001_friendships` MODIFY `tenant_id` BIGINT NOT NULL DEFAULT 1 COMMENT '商户ID'; +INSERT IGNORE INTO `im_001_friendships` SELECT * FROM `im_friendships` WHERE `tenant_id` = 1; + +CREATE TABLE IF NOT EXISTS `im_001_messages` LIKE `im_messages`; +ALTER TABLE `im_001_messages` MODIFY `tenant_id` BIGINT NOT NULL DEFAULT 1 COMMENT '商户ID'; +INSERT IGNORE INTO `im_001_messages` SELECT * FROM `im_messages` WHERE `tenant_id` = 1; + +CREATE TABLE IF NOT EXISTS `im_002_friendships` LIKE `im_friendships`; +ALTER TABLE `im_002_friendships` MODIFY `tenant_id` BIGINT NOT NULL DEFAULT 2 COMMENT '商户ID'; +INSERT IGNORE INTO `im_002_friendships` SELECT * FROM `meeting_im`.`im_friendships`; + +CREATE TABLE IF NOT EXISTS `im_002_messages` LIKE `im_messages`; +ALTER TABLE `im_002_messages` MODIFY `tenant_id` BIGINT NOT NULL DEFAULT 2 COMMENT '商户ID'; +INSERT IGNORE INTO `im_002_messages` SELECT * FROM `meeting_im`.`im_messages`; + +-- 4. 迁移所有已存在的标准月表。过程只识别 im_group_messages_YYYYMM。 +DELIMITER $$ + +DROP PROCEDURE IF EXISTS migrate_group_month_tables$$ +CREATE PROCEDURE migrate_group_month_tables(IN source_schema VARCHAR(64), IN source_tenant BIGINT, IN tenant_code CHAR(3)) +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 = source_schema + 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_', tenant_code, '_group_messages_', month_code); + SET @create_sql = CONCAT('CREATE TABLE IF NOT EXISTS `tinode`.`', target_table, + '` LIKE `', source_schema, '`.`', source_table, '`'); + PREPARE stmt FROM @create_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; + SET @alter_sql = CONCAT('ALTER TABLE `tinode`.`', target_table, + '` MODIFY `tenant_id` BIGINT NOT NULL DEFAULT ', source_tenant, ' COMMENT ''商户ID'''); + PREPARE stmt FROM @alter_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; + SET @copy_sql = CONCAT('INSERT IGNORE INTO `tinode`.`', target_table, + '` SELECT * FROM `', source_schema, '`.`', source_table, + '` WHERE `tenant_id`=', source_tenant); + PREPARE stmt FROM @copy_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; + END LOOP; + CLOSE month_cursor; +END$$ + +DELIMITER ; + +CALL migrate_group_month_tables('tinode', 1, '001'); +CALL migrate_group_month_tables('meeting_im', 2, '002'); +DROP PROCEDURE IF EXISTS migrate_group_month_tables; + +-- 旧的非月表数据按创建月份归入 1 号商户月表(本地现有数据为 202608)。 +INSERT IGNORE INTO `im_001_group_messages_202608` +(`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,`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 `im_group_messages` +WHERE `tenant_id` = 1 AND DATE_FORMAT(`created_at`, '%Y%m') = '202608'; + +-- 5. 修正 1 号商户路由,并合入 2 号商户路由元数据。 +UPDATE `im_group_message_shards` +SET `table_name` = CONCAT('im_001_group_messages_', `shard_month`) +WHERE `tenant_id` = 1; + +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 2,`shard_month`,CONCAT('im_002_group_messages_', `shard_month`),`shard_start`,`shard_end`,`status`, + `row_count`,`min_created_at`,`max_created_at`,`created_at`,`updated_at` +FROM `meeting_im`.`im_group_message_shards` +ON DUPLICATE KEY UPDATE `table_name`=VALUES(`table_name`),`status`=VALUES(`status`); + +INSERT IGNORE INTO `im_group_message_topic_shards` SELECT * FROM `meeting_im`.`im_group_message_topic_shards`; +INSERT IGNORE INTO `im_group_message_dedup` SELECT * FROM `meeting_im`.`im_group_message_dedup`; + +-- 旧非月表迁移后补齐 Topic 路由,并按物理表实际数据校准分片统计。 +INSERT INTO `im_group_message_topic_shards` +(`tenant_id`,`topic`,`shard_month`,`min_seq`,`max_seq`,`message_count`,`min_created_at`,`max_created_at`) +SELECT 1,`topic`,'202608',MIN(`seqid`),MAX(`seqid`),COUNT(*),MIN(`created_at`),MAX(`created_at`) +FROM `im_001_group_messages_202608` +GROUP BY `topic` +ON DUPLICATE KEY UPDATE `min_seq`=VALUES(`min_seq`),`max_seq`=VALUES(`max_seq`), +`message_count`=VALUES(`message_count`),`min_created_at`=VALUES(`min_created_at`),`max_created_at`=VALUES(`max_created_at`); + +UPDATE `im_group_message_shards` AS s +SET s.`row_count`=(SELECT COUNT(*) FROM `im_001_group_messages_202608`), + s.`min_created_at`=(SELECT MIN(`created_at`) FROM `im_001_group_messages_202608`), + s.`max_created_at`=(SELECT MAX(`created_at`) FROM `im_001_group_messages_202608`) +WHERE s.`tenant_id`=1 AND s.`shard_month`='202608'; + +-- 验证结果。 +SELECT `sequence_name`,`next_id` FROM `hotgo`.`im_tenant_id_sequence`; +SELECT `table_name`,`table_rows` +FROM information_schema.tables +WHERE table_schema = 'tinode' + AND table_name REGEXP '^im_[0-9]{3}_(friendships|messages|group_messages_[0-9]{6})$' +ORDER BY table_name; diff --git a/docs/sql/IM-共库核心分表-测试预演库.sql b/docs/sql/IM-共库核心分表-测试预演库.sql new file mode 100644 index 0000000..65ce736 --- /dev/null +++ b/docs/sql/IM-共库核心分表-测试预演库.sql @@ -0,0 +1,296 @@ +-- 仅用于测试环境复制库 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;