feat(M11b-1b): 落库迁移 SQL 资产 --emit 产出 scripts/migrations/ 3 个迁移文件
- 20260919_m11b_01_create_pbl_runtime_event.sql (3617B): CREATE TABLE IF NOT EXISTS pbl_runtime_event + PARTITION BY RANGE COLUMNS(created_at),6 个 p<YYYYMM> + pmax - 20260919_m11b_02_pbl_runtime_event_append_only.sql (3212B): pbl_assert_append_only 过程 + trg_..._append_only_upd/del 两个 BEFORE 触发器护栏(DELIMITER $$ 包裹) - 20260919_m11b_03_pbl_runtime_event_partition_maintenance.sql (3849B): pbl_runtime_event_ensure_partitions / pbl_runtime_event_prune_partitions / ev_pbl_runtime_event_partition_maintain 三段完整正文(QC#2 截断缺陷已消除) - sql/m11b_partitions.sql 与生成器 full_script() 逐字一致(11893B, IDENTICAL=True) - 幂等:连跑两次 --emit 产物字节数不变;无硬编码连接串
This commit is contained in:
parent
907603653d
commit
cda913a694
@ -0,0 +1,50 @@
|
||||
-- 生成器: scripts/pbl_runtime_event_ddl.py --emit (M11b-1)
|
||||
-- 开发说明: projects/pbls/docs/02-develop/dev-notes-m11b1-pbl_runtime_event.md
|
||||
-- 幂等: CREATE ... IF NOT EXISTS / DROP ... IF EXISTS
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `pbl_runtime_event` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '自增ID(逻辑主键;物理主键=(id,created_at))',
|
||||
`tenant_id` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '租户ID(多租户强制打头)',
|
||||
`event_uid` VARCHAR(64) NOT NULL COMMENT '事件UID(uuid,应用层生成)',
|
||||
`event_code` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '事件编码',
|
||||
`idem_key` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '幂等键(同键重复投递只落一条)',
|
||||
`world_id` BIGINT NOT NULL DEFAULT 0 COMMENT '世界ID(引用 world 基表,只读)',
|
||||
`scene_id` BIGINT NOT NULL DEFAULT 0 COMMENT '场景/会话ID(引用 scense 基表,只读)',
|
||||
`session_id` BIGINT NOT NULL DEFAULT 0 COMMENT '游戏会话ID(M11a 语义,与 scene_id 同域)',
|
||||
`entity_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '实体ID(引用 entity 基表,只读)',
|
||||
`actor_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '触发者(服务端会话解析,客户端禁填)',
|
||||
`event_type` VARCHAR(64) NOT NULL COMMENT '事件类型',
|
||||
`payload` TEXT NULL COMMENT '事件负载JSON(任务书字段)',
|
||||
`payload_json` LONGTEXT NULL COMMENT '事件负载JSON(M11a 写路径列名,与 payload 同义留宽)',
|
||||
`causation_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '因果链:引发本事件的事件UID',
|
||||
`seq` BIGINT NOT NULL DEFAULT 0 COMMENT '会话内单调序号(轮询游标,应用层生成)',
|
||||
`seq_no` BIGINT NOT NULL DEFAULT 0 COMMENT '会话内单调序号(M11a 写路径列名)',
|
||||
`source` VARCHAR(32) NOT NULL DEFAULT 'runtime' COMMENT '来源:runtime/agent/script/client_intent',
|
||||
`state` VARCHAR(16) NOT NULL DEFAULT 'applied' COMMENT 'applied/rejected/rolled_back',
|
||||
`state_version` BIGINT NOT NULL DEFAULT 0 COMMENT '本事件推进到的服务端权威版本',
|
||||
`tx_group` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '事务组(同组同事务)',
|
||||
`broadcast` TINYINT NOT NULL DEFAULT 1 COMMENT '是否参与广播 0/1',
|
||||
`created_by` BIGINT NOT NULL DEFAULT 0 COMMENT '创建人',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(分区键,禁TIMESTAMP)',
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间戳(append-only:永不 UPDATE)',
|
||||
`occurred_at` DATETIME NULL COMMENT '兼容列(M11a 旧分区键,新写入可空)',
|
||||
PRIMARY KEY (`id`, `created_at`),
|
||||
UNIQUE KEY `uk_event_uid` (`tenant_id`, `event_uid`, `created_at`),
|
||||
UNIQUE KEY `uk_tenant_idem` (`tenant_id`, `idem_key`, `created_at`),
|
||||
KEY `ix_scene_created` (`scene_id`, `created_at`),
|
||||
KEY `ix_tenant_session_seq` (`tenant_id`, `session_id`, `seq`),
|
||||
KEY `ix_tenant_session_seqno` (`tenant_id`, `session_id`, `seq_no`),
|
||||
KEY `ix_tenant_world_type` (`tenant_id`, `world_id`, `event_type`),
|
||||
KEY `ix_tx_group` (`tx_group`),
|
||||
KEY `ix_created_at` (`created_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||||
COMMENT='运行时事件流(append-only,按月RANGE分区,保留14个月)'
|
||||
PARTITION BY RANGE COLUMNS(`created_at`) (
|
||||
PARTITION `p202609` VALUES LESS THAN (DATE '2026-10-01'),
|
||||
PARTITION `p202610` VALUES LESS THAN (DATE '2026-11-01'),
|
||||
PARTITION `p202611` VALUES LESS THAN (DATE '2026-12-01'),
|
||||
PARTITION `p202612` VALUES LESS THAN (DATE '2027-01-01'),
|
||||
PARTITION `p202701` VALUES LESS THAN (DATE '2027-02-01'),
|
||||
PARTITION `p202702` VALUES LESS THAN (DATE '2027-03-01'),
|
||||
PARTITION `pmax` VALUES LESS THAN (MAXVALUE)
|
||||
);
|
||||
@ -0,0 +1,87 @@
|
||||
-- 生成器: scripts/pbl_runtime_event_ddl.py --emit (M11b-1)
|
||||
-- 开发说明: projects/pbls/docs/02-develop/dev-notes-m11b1-pbl_runtime_event.md
|
||||
-- 幂等: CREATE ... IF NOT EXISTS / DROP ... IF EXISTS
|
||||
|
||||
DELIMITER $$
|
||||
|
||||
-- append-only 断言过程:非白名单操作一律 SIGNAL 拒绝
|
||||
DROP PROCEDURE IF EXISTS `pbl_assert_append_only`;
|
||||
|
||||
CREATE$$
|
||||
|
||||
CREATE PROCEDURE `pbl_assert_append_only`(
|
||||
IN p_table VARCHAR(64), IN p_priv VARCHAR(16), IN p_detail VARCHAR(255))
|
||||
READS SQL DATA
|
||||
BEGIN
|
||||
IF p_table <> 'pbl_runtime_event' THEN
|
||||
RETURN; -- 只守护事件表,其他表不受影响
|
||||
END IF;
|
||||
IF IFNULL(@pbl_guard_ctx, '') = 'append_only_admin' THEN
|
||||
RETURN; -- break-glass:一次性历史数据回填
|
||||
END IF;
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = 'PBL_APPEND_ONLY_DENIED',
|
||||
MYSQL_ERRNO = 45001;
|
||||
END;
|
||||
|
||||
-- BEFORE UPDATE 护栏:把违规语句原文带进错误消息,便于定位调用方
|
||||
DROP TRIGGER IF EXISTS `trg_pbl_runtime_event_append_only_upd`$$
|
||||
|
||||
CREATE$$
|
||||
|
||||
CREATE TRIGGER `trg_pbl_runtime_event_append_only_upd`
|
||||
BEFORE UPDATE ON `pbl_runtime_event` FOR EACH ROW
|
||||
BEGIN
|
||||
DECLARE v_sql TEXT;
|
||||
DECLARE v_msg VARCHAR(255);
|
||||
SET v_sql = '';
|
||||
BEGIN
|
||||
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_sql = '';
|
||||
SELECT INFO INTO v_sql FROM information_schema.processlist
|
||||
WHERE ID = CONNECTION_ID();
|
||||
END;
|
||||
IF IFNULL(@pbl_guard_ctx, '') = 'append_only_admin' THEN
|
||||
SET v_msg = CONCAT('break-glass update on ', 'pbl_runtime_event');
|
||||
ELSE
|
||||
SET v_msg = CONCAT('UPDATE denied: ', LEFT(IFNULL(v_sql, 'n/a'), 120));
|
||||
END IF;
|
||||
CALL pbl_assert_append_only('pbl_runtime_event', 'update', v_msg);
|
||||
IF IFNULL(@pbl_guard_ctx, '') <> 'append_only_admin' THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'PBL_APPEND_ONLY_DENIED_UPDATE',
|
||||
MYSQL_ERRNO = 45002;
|
||||
END IF;
|
||||
END;
|
||||
|
||||
-- BEFORE DELETE 护栏
|
||||
DROP TRIGGER IF EXISTS `trg_pbl_runtime_event_append_only_del`$$
|
||||
|
||||
CREATE$$
|
||||
|
||||
CREATE TRIGGER `trg_pbl_runtime_event_append_only_del`
|
||||
BEFORE DELETE ON `pbl_runtime_event` FOR EACH ROW
|
||||
BEGIN
|
||||
DECLARE v_sql TEXT;
|
||||
DECLARE v_msg VARCHAR(255);
|
||||
SET v_sql = '';
|
||||
BEGIN
|
||||
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_sql = '';
|
||||
SELECT INFO INTO v_sql FROM information_schema.processlist
|
||||
WHERE ID = CONNECTION_ID();
|
||||
END;
|
||||
IF IFNULL(@pbl_guard_ctx, '') = 'append_only_admin' THEN
|
||||
SET v_msg = CONCAT('break-glass delete on ', 'pbl_runtime_event');
|
||||
ELSE
|
||||
SET v_msg = CONCAT('DELETE denied: ', LEFT(IFNULL(v_sql, 'n/a'), 120));
|
||||
END IF;
|
||||
CALL pbl_assert_append_only('pbl_runtime_event', 'delete', v_msg);
|
||||
IF IFNULL(@pbl_guard_ctx, '') <> 'append_only_admin' THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'PBL_APPEND_ONLY_DENIED_DELETE',
|
||||
MYSQL_ERRNO = 45003;
|
||||
END IF;
|
||||
END;
|
||||
|
||||
-- 权限层加固(可选,需 DBA 账号执行;app 账号只保留增/查)
|
||||
-- GRANT INSERT, SELECT, UPDATE, DELETE ON `pbls`.`pbl_runtime_event` TO NULL; -- 占位,勿执行
|
||||
-- REVOKE UPDATE, DELETE ON `pbls`.`pbl_runtime_event` FROM 'pbls'@'%'$$
|
||||
|
||||
DELIMITER ;
|
||||
@ -0,0 +1,104 @@
|
||||
-- 生成器: scripts/pbl_runtime_event_ddl.py --emit (M11b-1)
|
||||
-- 开发说明: projects/pbls/docs/02-develop/dev-notes-m11b1-pbl_runtime_event.md
|
||||
-- 幂等: CREATE ... IF NOT EXISTS / DROP ... IF EXISTS
|
||||
|
||||
DELIMITER $$
|
||||
|
||||
-- 1) 幂等补建未来月分区:pmax 存在时用 REORGANIZE PARTITION 拆出新月份(不丢数据)
|
||||
DROP PROCEDURE IF EXISTS `pbl_runtime_event_ensure_partitions`;
|
||||
|
||||
CREATE$$
|
||||
|
||||
CREATE PROCEDURE `pbl_runtime_event_ensure_partitions`(IN p_months_ahead INT)
|
||||
MODIFIES SQL DATA
|
||||
BEGIN
|
||||
DECLARE v_done INT DEFAULT 0;
|
||||
DECLARE v_name VARCHAR(16);
|
||||
DECLARE v_upper DATE;
|
||||
DECLARE v_exists INT DEFAULT 0;
|
||||
DECLARE v_sql TEXT;
|
||||
DECLARE cur CURSOR FOR
|
||||
WITH RECURSIVE ms(month_start) AS (
|
||||
SELECT DATE_FORMAT(CURDATE(), '%Y-%m-01')
|
||||
UNION ALL
|
||||
SELECT DATE_ADD(month_start, INTERVAL 1 MONTH) FROM ms
|
||||
WHERE month_start < DATE_ADD(DATE_FORMAT(CURDATE(), '%Y-%m-01'),
|
||||
INTERVAL p_months_ahead MONTH)
|
||||
)
|
||||
SELECT CONCAT('p', DATE_FORMAT(month_start, '%Y%m')),
|
||||
DATE_ADD(month_start, INTERVAL 1 MONTH)
|
||||
FROM ms;
|
||||
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = 1;
|
||||
OPEN cur;
|
||||
read_loop: LOOP
|
||||
FETCH cur INTO v_name, v_upper;
|
||||
IF v_done = 1 THEN
|
||||
LEAVE read_loop;
|
||||
END IF;
|
||||
SELECT COUNT(*) INTO v_exists FROM information_schema.PARTITIONS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'pbl_runtime_event'
|
||||
AND PARTITION_NAME = v_name;
|
||||
IF v_exists = 0 THEN
|
||||
SET @pbl_part_sql = CONCAT(
|
||||
'ALTER TABLE `pbl_runtime_event` REORGANIZE PARTITION `pmax` INTO (',
|
||||
'PARTITION `', v_name, '` VALUES LESS THAN (DATE ''', v_upper, '''), ',
|
||||
'PARTITION `pmax` VALUES LESS THAN (MAXVALUE))');
|
||||
PREPARE pbl_part_st FROM @pbl_part_sql;
|
||||
EXECUTE pbl_part_st;
|
||||
DEALLOCATE PREPARE pbl_part_st;
|
||||
END IF;
|
||||
END LOOP;
|
||||
CLOSE cur;
|
||||
END;
|
||||
|
||||
-- 2) 到期归档:整月 DROP PARTITION(append-only 表唯一合规的清理手段)
|
||||
DROP PROCEDURE IF EXISTS `pbl_runtime_event_prune_partitions`$$
|
||||
|
||||
CREATE$$
|
||||
|
||||
CREATE PROCEDURE `pbl_runtime_event_prune_partitions`(IN p_keep_months INT)
|
||||
MODIFIES SQL DATA
|
||||
BEGIN
|
||||
DECLARE v_done INT DEFAULT 0;
|
||||
DECLARE v_name VARCHAR(64);
|
||||
DECLARE v_cutoff DATE;
|
||||
DECLARE cur CURSOR FOR
|
||||
SELECT PARTITION_NAME FROM information_schema.PARTITIONS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'pbl_runtime_event'
|
||||
AND PARTITION_NAME IS NOT NULL
|
||||
AND PARTITION_NAME <> 'pmax'
|
||||
AND PARTITION_NAME < CONCAT('p', DATE_FORMAT(v_cutoff, '%Y%m'));
|
||||
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = 1;
|
||||
SET v_cutoff = DATE_ADD(DATE_FORMAT(CURDATE(), '%Y-%m-01'), INTERVAL -p_keep_months MONTH);
|
||||
OPEN cur;
|
||||
prune_loop: LOOP
|
||||
FETCH cur INTO v_name;
|
||||
IF v_done = 1 THEN
|
||||
LEAVE prune_loop;
|
||||
END IF;
|
||||
SET @pbl_drop_sql = CONCAT('ALTER TABLE `pbl_runtime_event` DROP PARTITION `', v_name, '`');
|
||||
PREPARE pbl_drop_st FROM @pbl_drop_sql;
|
||||
EXECUTE pbl_drop_st;
|
||||
DEALLOCATE PREPARE pbl_drop_st;
|
||||
END LOOP;
|
||||
CLOSE cur;
|
||||
END;
|
||||
|
||||
-- 3) 每月自动补分区(需 event_scheduler=ON;否则用 cron,见开发说明)
|
||||
-- SET GLOBAL event_scheduler = ON$$
|
||||
|
||||
DROP$$
|
||||
|
||||
DROP EVENT IF EXISTS `ev_pbl_runtime_event_partition_maintain`;
|
||||
|
||||
CREATE$$
|
||||
|
||||
CREATE EVENT IF NOT EXISTS `ev_pbl_runtime_event_partition_maintain`
|
||||
ON SCHEDULE EVERY 1 MONTH
|
||||
STARTS TIMESTAMP(DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 1 MONTH), '%Y-%m-01 03:00:00'))
|
||||
ON COMPLETION PRESERVE
|
||||
ENABLE
|
||||
COMMENT '每月预建未来 6 个月分区'
|
||||
DO CALL pbl_runtime_event_ensure_partitions(6)$$
|
||||
|
||||
DELIMITER ;
|
||||
Loading…
x
Reference in New Issue
Block a user