SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; USE hyapp_lucky_gift; -- AB 实验事实表:两组各钉住一个不可变 rule_version,运行时按 bucket_salt + user 哈希分流。 -- active 生成列只在未完成状态为 1,配合唯一键保证同一奖池同时最多一个未完成实验; -- completed 后 active 为 NULL,不占用唯一键,历史实验可以保留任意多条。 CREATE TABLE IF NOT EXISTS lucky_gift_experiments ( app_code VARCHAR(32) NOT NULL COMMENT '应用编码', experiment_id VARCHAR(64) NOT NULL COMMENT '实验 ID', pool_id VARCHAR(96) NOT NULL COMMENT '幸运礼物奖池 ID', name VARCHAR(128) NOT NULL COMMENT '实验名称', control_rule_version BIGINT NOT NULL COMMENT '对照组钉住的规则版本', treatment_rule_version BIGINT NOT NULL COMMENT '实验组钉住的规则版本', treatment_traffic_ppm BIGINT NOT NULL DEFAULT 0 COMMENT '实验组流量比例 ppm,0..1000000', bucket_salt VARCHAR(64) NOT NULL COMMENT '分桶盐;实验生命周期内不可变,保证同一用户分组稳定', status VARCHAR(16) NOT NULL DEFAULT 'running' COMMENT 'running/paused/completed', active TINYINT AS (IF(status IN ('running', 'paused'), 1, NULL)) STORED COMMENT '未完成实验为 1,completed 为 NULL', winner_arm VARCHAR(16) NOT NULL DEFAULT '' COMMENT '结束实验时选择的获胜组 control/treatment', winner_rule_version BIGINT NOT NULL DEFAULT 0 COMMENT '获胜组钉住的规则版本', final_rule_version BIGINT NOT NULL DEFAULT 0 COMMENT '结束时重发布的收敛版本,全量流量由它承接', created_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '创建管理员 ID', started_at_ms BIGINT NOT NULL COMMENT '实验开始时间,UTC epoch ms', ended_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '实验结束时间,UTC epoch ms', created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', PRIMARY KEY (app_code, experiment_id), UNIQUE KEY uk_lucky_gift_experiments_active_pool (app_code, pool_id, active), KEY idx_lucky_gift_experiments_pool (app_code, pool_id, started_at_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='幸运礼物 AB 实验'; -- 白名单 override:把指定用户强制固定到某个组,用于放量前验证。 -- user_key:内部用户为十进制 user_id;外部用户为 "ext:" + external_user_id。 CREATE TABLE IF NOT EXISTS lucky_gift_experiment_overrides ( app_code VARCHAR(32) NOT NULL COMMENT '应用编码', experiment_id VARCHAR(64) NOT NULL COMMENT '实验 ID', -- user_key 必须字节精确(binary collation):FNV 分桶按字节计算,ci 排序规则会让大小写不同的外部用户在白名单上碰撞/静默失配。 user_key VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '用户标识:内部十进制 user_id 或 ext:external_user_id', arm VARCHAR(16) NOT NULL COMMENT '强制组 control/treatment', created_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '操作管理员 ID', created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', PRIMARY KEY (app_code, experiment_id, user_key) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='幸运礼物 AB 实验用户白名单'; -- 实验分组统计按 (app, pool, rule_version, 时间窗) 聚合;没有含 rule_version 的索引时热池会退化为大范围回表扫描。 SET @ddl := IF((SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'lucky_draw_records' AND INDEX_NAME = 'idx_lucky_draw_pool_version') = 0, 'ALTER TABLE lucky_draw_records ADD KEY idx_lucky_draw_pool_version (app_code, pool_id, rule_version, created_at_ms)', 'SELECT 1'); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- 兜底:006 首版曾以表默认 ci 排序规则建过 overrides 表;重复执行时归一到 binary。 SET @ddl := IF((SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'lucky_gift_experiment_overrides' AND COLUMN_NAME = 'user_key' AND COLLATION_NAME <> 'utf8mb4_bin') = 1, 'ALTER TABLE lucky_gift_experiment_overrides MODIFY user_key VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT ''用户标识:内部十进制 user_id 或 ext:external_user_id''', 'SELECT 1'); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;