-- thirdparty 支付任务事实表与执行尝试表。 -- Quartz 是唯一周期触发入口;任务表只保存可恢复事实、租约和幂等状态。 CREATE TABLE IF NOT EXISTS `third_payment_task` ( `id` bigint NOT NULL COMMENT '任务主键', `tenant_id` bigint NOT NULL COMMENT '租户ID', `task_type` varchar(48) NOT NULL COMMENT '任务类型编码', `task_key` varchar(192) NOT NULL COMMENT '租户内稳定幂等任务键', `pay_order_id` bigint DEFAULT NULL COMMENT 'third_pay_order主键', `fz_order_id` bigint DEFAULT NULL COMMENT 'third_fz_order主键', `aggregate_session_id` bigint DEFAULT NULL COMMENT 'third_aggregate_pay_session主键', `pay_no` varchar(128) DEFAULT NULL COMMENT '平台支付单号', `refund_no` varchar(128) DEFAULT NULL COMMENT '平台退款单号', `fz_no` varchar(128) DEFAULT NULL COMMENT '平台分账单号', `aggregate_no` varchar(128) DEFAULT NULL COMMENT '聚合会话单号', `status` varchar(16) NOT NULL COMMENT '任务状态:READY/PROCESSING/RETRY/SUCCESS/FAILED/ALERT/CANCELLED', `next_run_time` datetime NOT NULL COMMENT '下一次可执行时间', `priority` int NOT NULL DEFAULT 0 COMMENT '任务优先级,数值越大越优先', `lease_owner` varchar(128) DEFAULT NULL COMMENT 'Worker实例标识', `lease_token` varchar(64) DEFAULT NULL COMMENT '本次执行租约令牌', `lease_expire_time` datetime DEFAULT NULL COMMENT '租约失效时间', `attempt_count` int NOT NULL DEFAULT 0 COMMENT '累计执行次数', `last_external_status` varchar(64) DEFAULT NULL COMMENT '最近一次渠道状态', `last_error_code` varchar(128) DEFAULT NULL COMMENT '最近一次错误码', `last_error_message` varchar(1024) DEFAULT NULL COMMENT '最近一次错误摘要', `finished_time` datetime DEFAULT NULL COMMENT '任务终态时间', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `create_time` datetime NOT NULL COMMENT '创建时间', `update_by` varchar(64) DEFAULT NULL COMMENT '修改人', `update_time` datetime NOT NULL COMMENT '更新时间', `del_flag` char(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除标记', PRIMARY KEY (`id`), UNIQUE KEY `uk_third_payment_task_tenant_key` (`tenant_id`, `task_key`), KEY `idx_third_payment_task_due` (`task_type`, `status`, `next_run_time`, `priority`, `id`), KEY `idx_third_payment_task_lease` (`task_type`, `status`, `lease_expire_time`), KEY `idx_third_payment_task_pay_no` (`tenant_id`, `pay_no`), KEY `idx_third_payment_task_fz_no` (`tenant_id`, `fz_no`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '第三方支付任务事实'; CREATE TABLE IF NOT EXISTS `third_payment_task_attempt` ( `id` bigint NOT NULL COMMENT '尝试记录主键', `task_id` bigint NOT NULL COMMENT '任务主键', `tenant_id` bigint NOT NULL COMMENT '租户ID', `attempt_no` int NOT NULL COMMENT '任务内尝试序号', `trigger_source` varchar(32) NOT NULL COMMENT '触发来源:QUARTZ/CALLBACK/SYNC/MANUAL', `worker_id` varchar(128) DEFAULT NULL COMMENT 'Worker实例标识', `lease_token` varchar(64) DEFAULT NULL COMMENT '执行租约令牌', `request_snapshot` text DEFAULT NULL COMMENT '脱敏请求快照', `response_snapshot` text DEFAULT NULL COMMENT '脱敏响应快照', `external_status` varchar(64) DEFAULT NULL COMMENT '渠道状态', `error_code` varchar(128) DEFAULT NULL COMMENT '错误码', `error_message` varchar(1024) DEFAULT NULL COMMENT '错误摘要', `started_time` datetime NOT NULL COMMENT '开始时间', `finished_time` datetime DEFAULT NULL COMMENT '结束时间', PRIMARY KEY (`id`), KEY `idx_third_payment_attempt_task` (`task_id`, `attempt_no`), KEY `idx_third_payment_attempt_tenant_time` (`tenant_id`, `started_time`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '第三方支付任务执行尝试'; -- Quartz 会按业务事实持续物化任务。百万级订单量下,如果业务表只有主键,每个固定触发器都会退化为全表扫描。 -- 以下索引只覆盖实际扫描谓词和租户内业务号关联;使用 information_schema 判断保证迁移可以重复执行。 SET @index_ddl = IF( EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'third_pay_order' AND index_name = 'idx_third_pay_order_task_scan'), 'SELECT ''idx_third_pay_order_task_scan already exists'' AS message', 'ALTER TABLE `third_pay_order` ADD INDEX `idx_third_pay_order_task_scan` (`status`, `del_flag`, `pay_platform_type`, `update_time`, `id`)' ); PREPARE index_stmt FROM @index_ddl; EXECUTE index_stmt; DEALLOCATE PREPARE index_stmt; SET @index_ddl = IF( EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'third_pay_order' AND index_name = 'idx_third_pay_order_fz_scan'), 'SELECT ''idx_third_pay_order_fz_scan already exists'' AS message', 'ALTER TABLE `third_pay_order` ADD INDEX `idx_third_pay_order_fz_scan` (`status`, `enable_fz`, `del_flag`, `pay_time`, `id`)' ); PREPARE index_stmt FROM @index_ddl; EXECUTE index_stmt; DEALLOCATE PREPARE index_stmt; SET @index_ddl = IF( EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'third_pay_order' AND index_name = 'idx_third_pay_order_callback_scan'), 'SELECT ''idx_third_pay_order_callback_scan already exists'' AS message', 'ALTER TABLE `third_pay_order` ADD INDEX `idx_third_pay_order_callback_scan` (`status`, `is_biz_callback`, `del_flag`, `update_time`, `id`)' ); PREPARE index_stmt FROM @index_ddl; EXECUTE index_stmt; DEALLOCATE PREPARE index_stmt; SET @index_ddl = IF( EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'third_pay_order' AND index_name = 'idx_third_pay_order_close_scan'), 'SELECT ''idx_third_pay_order_close_scan already exists'' AS message', 'ALTER TABLE `third_pay_order` ADD INDEX `idx_third_pay_order_close_scan` (`status`, `biz_type`, `del_flag`, `create_time`, `id`)' ); PREPARE index_stmt FROM @index_ddl; EXECUTE index_stmt; DEALLOCATE PREPARE index_stmt; SET @index_ddl = IF( EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'third_pay_order' AND index_name = 'idx_third_pay_order_tenant_pay_no'), 'SELECT ''idx_third_pay_order_tenant_pay_no already exists'' AS message', 'ALTER TABLE `third_pay_order` ADD INDEX `idx_third_pay_order_tenant_pay_no` (`tenant_id`, `pay_no`, `del_flag`)' ); PREPARE index_stmt FROM @index_ddl; EXECUTE index_stmt; DEALLOCATE PREPARE index_stmt; SET @index_ddl = IF( EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'third_fz_order' AND index_name = 'idx_third_fz_order_task_scan'), 'SELECT ''idx_third_fz_order_task_scan already exists'' AS message', 'ALTER TABLE `third_fz_order` ADD INDEX `idx_third_fz_order_task_scan` (`status`, `del_flag`, `update_time`, `id`)' ); PREPARE index_stmt FROM @index_ddl; EXECUTE index_stmt; DEALLOCATE PREPARE index_stmt; SET @index_ddl = IF( EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'third_fz_order' AND index_name = 'idx_third_fz_order_tenant_pay_no'), 'SELECT ''idx_third_fz_order_tenant_pay_no already exists'' AS message', 'ALTER TABLE `third_fz_order` ADD INDEX `idx_third_fz_order_tenant_pay_no` (`tenant_id`, `pay_no`, `del_flag`)' ); PREPARE index_stmt FROM @index_ddl; EXECUTE index_stmt; DEALLOCATE PREPARE index_stmt; SET @index_ddl = IF( EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'third_aggregate_pay_session' AND index_name = 'idx_third_aggregate_task_scan'), 'SELECT ''idx_third_aggregate_task_scan already exists'' AS message', 'ALTER TABLE `third_aggregate_pay_session` ADD INDEX `idx_third_aggregate_task_scan` (`status`, `del_flag`, `update_time`, `id`)' ); PREPARE index_stmt FROM @index_ddl; EXECUTE index_stmt; DEALLOCATE PREPARE index_stmt; SET @index_ddl = IF( EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'third_aggregate_pay_session' AND index_name = 'idx_third_aggregate_close_scan'), 'SELECT ''idx_third_aggregate_close_scan already exists'' AS message', 'ALTER TABLE `third_aggregate_pay_session` ADD INDEX `idx_third_aggregate_close_scan` (`status`, `del_flag`, `create_time`, `id`)' ); PREPARE index_stmt FROM @index_ddl; EXECUTE index_stmt; DEALLOCATE PREPARE index_stmt; -- 生产执行建议:低峰期使用在线 DDL 能力建索引,完成后 ANALYZE TABLE 并用真实状态分布执行 EXPLAIN ANALYZE。