大型跨境电商网站部署在Debian的香港服务器中,如何用MariaDB分区表顶住大促订单暴增
技术教程 2025-09-16 09:12 213


晚上23:20,我站在香港柴湾机房 C 排 12 柜,Slack 上“War Room”频道不断闪烁:运营在确认库存池,风控在调阈值,SRE 在播报上云 WAF 的规则命中。我盯着一个 Grafana 大屏:Threads_running 还在两位数,innodb_log_waits 稳定为 0,iostat -x 的 await 一直压在个位数。我知道,真正的考验是 00:00——东南亚几个站点同时开闸,订单会像雨点砸下来。

我把笔记本放到机柜托盘上,翻开去年的复盘:那次黑五,我们的订单表还是“整块大表”,商家后台一个 DATE(created_at) 的列表把半年的数据都扫了一遍,Binlog 队列高位徘徊,主从延迟拉到了 9 秒。那晚我们靠限流和熬人把坑补过去,但我给自己记了三条红线:不在业务侧临时改代码、不牺牲数据一致性、必须把热数据和冷数据切开。

这次大促前,我决定在 Debian 12 + MariaDB 10.11 的栈上做一件“看似小、实则关键”的事:用生成列做分区键,按月 RANGE 分区。不动业务代码,不改主键发号,只让优化器能“裁剪”到最近几个月。为了不锁表,我先在影子环境把 orders_new 按目标结构建好,双写验证 30 分钟后,窗口期 RENAME TABLE 切换,再把最老月份 EXCHANGE PARTITION 到归档表。那一刻我对同事说:今晚如果顶住,靠的不是神奇的参数,而是我们把“查询命中正确分区”这件事做到了骨子里。

把小扳手插回口袋,我又确认了几件“临门一脚”的细节:event_scheduler=ON、未来 12 个月的分区齐备、ProxySQL 的读规则优先命中本地只读库、innodb_flush_log_at_trx_commit=1 坚守,“必要时带签字降级”。我看了一眼维港方向的夜色,屏幕上时钟跳到 23:59:20——离闸前 40 秒,我们所有人都在呼吸。

写在前面:这不是一篇“理论+摘抄”的教程,而是我在香港柴湾机房、穿着羽绒背心守着一排 NVMe 机柜,亲手把生产库从“可能被冲垮”救到“稳如老狗”的完整过程。你会看到真实的硬件型号、配置片段、踩坑与复盘。我尽量把每一步写到“新手可以照做、老手可以挑毛病”的程度。

背景与现场

业务形态:跨境电商,站点主要面向东南亚、港澳台与中东;大促活动(双 11、黑五、年终清仓)会出现分钟级突增订单。

目标:在不改写业务代码的前提下,通过 MariaDB 分区表 + 一些内核/存储/复制层优化,把峰值写入从 8 万 TPS 提升到 12~15 万 TPS,并把关键查询(商家后台的订单列表、风控审单)P95 延迟从 450 ms 压到 120 ms 以内。

版本矩阵:

  • 操作系统:Debian 12 (Bookworm),内核 6.1 LTS。
  • 数据库:MariaDB 10.11(Debian 默认 LTS)。
  • 文件系统:ext4(稳定保守),底层 mdadm RAID10。
  • 连接池:ProxySQL 2.6(读写分离+连接复用)。
  • 监控:Prometheus + Grafana + mysqld_exporter,辅以 pt-query-digest。

机房小记:凌晨两点的冷风顺着上送风地板往脸上刮,耳边是 1U 机的风扇尖啸。运营在 Slack 催问“能顶住吗?”我盯着 SHOW GLOBAL STATUS 的 Threads_running 和磁盘队列长度,不敢眨眼。下面是我们最终落地的方案。

1. 硬件与网络拓扑(为什么选香港)

层级 规格 选择理由
计算 2×Intel Xeon Gold 6338N(32C)/节点,128 vCPU;内存 256 GB DDR4 ECC 单机足够大的 Buffer Pool;NUMA 影响可控
存储 8×3.84TB NVMe SSD,RAID10(mdadm),写缓存关、掉电保护开 高写入吞吐 + 低尾延迟;RAID10 便于并行随机写
网络 双上联 BGP,2×25GbE;ToR 里配 ECN+PFC 区域内访问延迟低,丢包率小;避免瞬时微拥塞
角色 db-primary-2c-256g-nvme-01/02 主库高可用,db-replica-sg-01 新加坡只读,db-analytics-hk-01 报表 读写分离、跨区就近读、分析解耦

注:如果你的业务对一致性极致敏感,主库坚持 innodb_flush_log_at_trx_commit=1,别轻易妥协到 2。我们后面会说明大促期间如何“临时降级”以及风控线条款。

2. 系统准备(Debian 实操)

2.1 分区与文件系统

# 1) 组 RAID10(举例 8 块 NVMe),元数据1.2
mdadm --create /dev/md0 --level=10 --raid-devices=8 /dev/nvme{0..7}n1

# 2) 调整读写条带,提高并发
mdadm --detail --scan >> /etc/mdadm/mdadm.conf
update-initramfs -u

# 3) ext4:journal 校准,目录项哈希
mkfs.ext4 -E stride=128,stripe-width=256 -O dir_index,extent,has_journal /dev/md0
mkdir -p /var/lib/mysql && mount -o noatime,nodiratime,discard /dev/md0 /var/lib/mysql

# 4) fstab 固化
UUID=$(blkid -s UUID -o value /dev/md0)
echo "UUID=$UUID /var/lib/mysql ext4 noatime,nodiratime,discard 0 2" >> /etc/fstab

2.2 内核与系统参数

/etc/sysctl.d/99-mariadb-tuning.conf

fs.file-max = 4000000
vm.swappiness = 1
vm.dirty_ratio = 10
vm.dirty_background_ratio = 5
net.core.somaxconn = 4096
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_max_syn_backlog = 4096

/etc/systemd/system/mariadb.service.d/override.conf

[Service]
LimitNOFILE=1000000
TasksMax=infinity
# 让 mariadbd 亲和 NUMA 节点,按需调整:
# ExecStartPre=/usr/bin/numactl --cpunodebind=0 --membind=0 /usr/sbin/mariadbd ...

坑 1:别在数据库数据盘上启用透明大页(THP);在 Debian 12 可通过 GRUB_CMDLINE_LINUX="transparent_hugepage=never" 关闭。

2.3 安装 MariaDB

Debian 12 默认仓库即可:

apt update && apt install -y mariadb-server mariadb-client
systemctl enable mariadb --now

3. MariaDB 核心配置(10.11)

/etc/mysql/mariadb.conf.d/50-server.cnf

[mysqld]
# 基础
bind-address = 0.0.0.0
user = mysql
skip_name_resolve = ON
max_connections = 4000
back_log = 1024

# InnoDB
innodb_buffer_pool_size = 160G         # 256G 内存机器,给 60~65%
innodb_buffer_pool_instances = 8
innodb_log_file_size = 4096M
innodb_log_files_in_group = 2
innodb_flush_method = O_DIRECT
innodb_io_capacity = 4000
innodb_io_capacity_max = 8000
innodb_flush_log_at_trx_commit = 1     # 主库坚守 1;大促应急见下
innodb_autoinc_lock_mode = 2

# 线程池(MariaDB 社区版可用)
thread_handling = pool-of-threads
thread_pool_max_threads = 256

# Binlog/复制
server_id = 1001
log_bin = mariadb-bin
binlog_format = ROW
binlog_expire_logs_seconds = 604800     # 7 天
log_slave_updates = ON
binlog_row_image = full

# 事件调度(用于分区自动维护)
event_scheduler = ON

# 时区统一
default_time_zone = "+00:00"          # DB 内一律用 UTC

# 性能观测
performance_schema = ON

# 表定义
sql_mode = STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

坑 2:分区表 所有唯一索引 必须包含分区键,否则建表报错 ER_UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF。下面的表结构会避开这个坑。

4. 订单表的分区设计与建表

4.1 为什么选“按月 RANGE 分区 + 生成列”

  • 大促期间,写入与查询都强依赖时间维度(最近 30~60 天最热)。
  • 我不想把主键从自增改成“按时间打散”的复杂方案,于是用持久化生成列 p_month 做分区键:
  • p_month = YEAR(created_at)*100 + MONTH(created_at)
  • 主键设计为 (id, p_month),满足“唯一索引包含分区键”的要求,同时对写入热点友好(自增 id 聚簇)。

4.2 建表示例(生产可直接用)

CREATE DATABASE IF NOT EXISTS shopdb DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE shopdb;

CREATE TABLE IF NOT EXISTS orders (
  id              BIGINT UNSIGNED NOT NULL COMMENT '订单ID,自增',
  created_at      DATETIME       NOT NULL COMMENT '下单时间,UTC',
  p_month         INT            NOT NULL AS (YEAR(created_at)*100 + MONTH(created_at)) PERSISTENT,
  buyer_id        BIGINT UNSIGNED NOT NULL,
  seller_id       BIGINT UNSIGNED NOT NULL,
  status          TINYINT        NOT NULL COMMENT '0:待支付 1:已支付 2:已发货 3:完成 4:取消 5:售后',
  total_amount    DECIMAL(12,2)  NOT NULL,
  currency        CHAR(3)        NOT NULL,
  region_code     CHAR(2)        NOT NULL,
  pay_channel     VARCHAR(32)    NOT NULL,
  ext_json        JSON           NULL,
  PRIMARY KEY (id, p_month),                             -- 注意:包含分区键
  KEY idx_created (created_at),
  KEY idx_seller_created (seller_id, created_at),
  KEY idx_buyer_created (buyer_id, created_at),
  KEY idx_region_status_created (region_code, status, created_at)
)
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY RANGE COLUMNS (p_month) (
  PARTITION p202407 VALUES LESS THAN (202407),
  PARTITION p202408 VALUES LESS THAN (202408),
  PARTITION p202409 VALUES LESS THAN (202409),
  PARTITION pMAX     VALUES LESS THAN (MAXVALUE)
);

说明:预先放几个历史月 + 一个 pMAX 兜底,后续用事件自动“滚动创建下一月分区,同时收缩最老的分区”。

4.3 分区自动化(存储过程 + 事件)

DELIMITER //
CREATE PROCEDURE ensure_order_partitions(
  IN keep_months INT  -- 保留最近 N 个月,过期直接丢分区
)
BEGIN
  DECLARE i INT DEFAULT 0;
  DECLARE tgt INT;
  DECLARE pm INT;
  # 1) 未来 12 个月分区存在性检查
  WHILE i < 12 DO
    SET tgt = DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL i MONTH), '%Y%m');
    SET pm = CAST(tgt AS UNSIGNED);
    IF NOT EXISTS (
      SELECT 1 FROM information_schema.PARTITIONS
      WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'orders'
        AND PARTITION_NAME = CONCAT('p', pm)
    ) THEN
      SET @sql = CONCAT('ALTER TABLE orders DROP PARTITION pMAX, ADD PARTITION (PARTITION p', pm,
                         ' VALUES LESS THAN (', pm, ')), ADD PARTITION (PARTITION pMAX VALUES LESS THAN (MAXVALUE))');
      PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
    END IF;
    SET i = i + 1;
  END WHILE;

  # 2) 清理过期分区(保留 keep_months)
  SET i = keep_months;  # 从 N 月开始算过期
  SET tgt = DATE_FORMAT(DATE_SUB(CURDATE(), INTERVAL i MONTH), '%Y%m');
  SET pm = CAST(tgt AS UNSIGNED);
  # 删除所有 < pm 的分区
  FOR rec IN (
    SELECT PARTITION_NAME FROM information_schema.PARTITIONS
    WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'orders'
      AND PARTITION_NAME REGEXP '^p[0-9]{6}$'
      AND CAST(SUBSTR(PARTITION_NAME, 2) AS UNSIGNED) < pm
  ) DO
    SET @sql = CONCAT('ALTER TABLE orders DROP PARTITION ', rec.PARTITION_NAME);
    PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
  END FOR;
END //
DELIMITER ;

-- 每晚 02:10 触发,保留 18 个月
CREATE EVENT IF NOT EXISTS evt_ensure_orders_part
ON SCHEDULE EVERY 1 DAY STARTS CURRENT_DATE + INTERVAL 2 HOUR + INTERVAL 10 MINUTE
DO CALL ensure_order_partitions(18);

坑 3:ALTER TABLE ... DROP/ADD PARTITION 会有元数据锁,若后台有 DDL/长事务可能阻塞。建议在低峰时段运行(上例是 02:10)。

5. 查询写法与分区裁剪(Pruning)

要让分区生效,WHERE 条件里必须出现可以推导出分区键的常量,例如:

-- ✅ 能裁剪:直接命中 2024-09 ~ 2024-10 两个月分区
SELECT * FROM orders
WHERE created_at >= '2024-09-01' AND created_at < '2024-11-01'
  AND seller_id = 12345
ORDER BY created_at DESC LIMIT 50;

-- ✅ 能裁剪:使用生成列 p_month
SELECT COUNT(*) FROM orders WHERE p_month BETWEEN 202409 AND 202410;

-- ❌ 不能裁剪:函数包裹导致优化器无法提前计算常量
SELECT * FROM orders WHERE DATE(created_at) = CURDATE();

-- ✅ 改写方式
SELECT * FROM orders WHERE created_at >= CURRENT_DATE()
                      AND created_at <  CURRENT_DATE() + INTERVAL 1 DAY;

建议在代码层统一用 闭区间/开区间 的时间写法,避免边界错误。

6. 大促前压测与结果(节选)

6.1 压测场景

写入:模拟订单创建(单行 INSERT + 轻量二级索引),峰值 120k TPS。

查询:商家后台列表(seller_id + created_at 范围)、风控抽样(最近 24 小时)、买家订单列表(buyer_id + 最近半年)。

对照组:非分区大表 vs. 按月 RANGE 分区表。

6.2 指标对比

指标 非分区 分区(按月) 提升
写入 TPS(峰值) 83,000 128,000 +54%
商家列表 P95 452 ms 118 ms -74%
风控抽样 P95 380 ms 102 ms -73%
磁盘读 IOPS(峰) 180k 95k -47%
Buffer Pool 命中 96.1% 98.7% +2.6 pp

解释:分区显著减少了无关分区扫描与冷数据 IO,索引更“短”,Buffer Pool 命中变高,尾延迟下降明显。

7. 复制与读写分离(ProxySQL + 半同步可选)

7.1 复制拓扑

  • 主库(香港)→ 只读副本(香港)→ 只读副本(新加坡)
  • binlog_format=ROW,log_slave_updates=ON,开启 gtid_strict_mode=ON(10.11 默认兼容)。

从库配置(要点):

read_only = ON
skip_replica_start = OFF

7.2 ProxySQL 路由规则(示例)

INSERT INTO mysql_servers(hostgroup_id, hostname, port, max_connections) VALUES
 (10,'10.0.0.10',3306,2000),   -- 主
 (20,'10.0.0.11',3306,1500),   -- 香港只读
 (30,'172.16.0.12',3306,1500); -- 新加坡只读

-- 读写分离:明确只把时间范围查询路由到本地只读,跨区读用于用户端最近数据
INSERT INTO mysql_query_rules (rule_id,active,match_pattern,apply, destination_hostgroup)
VALUES
 (100,1,'^SELECT.*FROM orders .*created_at',1,20),
 (110,1,'^SELECT',1,30),
 (200,1,'^INSERT|^UPDATE|^DELETE|^REPLACE',1,10);
LOAD MYSQL SERVERS TO RUNTIME; SAVE MYSQL SERVERS TO DISK;
LOAD MYSQL QUERY RULES TO RUNTIME; SAVE MYSQL QUERY RULES TO DISK;

坑 4:读到旧分区。跨区只读库延迟若超过 3 秒,商家后台会错过“刚下单的记录”。对于强一致的后台页面,强制路由到本地只读或主库。

8. 备份、归档与在线冷热分层

8.1 物理备份(mariabackup)

apt install -y mariadb-backup
mariabackup --backup --target-dir=/backup/full-$(date +%F) --user=backup --password=***

8.2 利用分区做“零拷贝归档”(EXCHANGE PARTITION)

将 18 个月外的分区换出到独立归档表,然后压缩存储:

CREATE TABLE orders_202301 LIKE orders;  -- 结构相同但无分区
ALTER TABLE orders EXCHANGE PARTITION p202301 WITH TABLE orders_202301 WITHOUT VALIDATION;
-- 现在 p202301 的数据已瞬时移到 orders_202301,可单独打包/下线

注:WITHOUT VALIDATION 要确保表结构与约束完全一致。归档表可转储到对象存储,或导入 ClickHouse 之类做离线分析。

9. 大促当天应急手册(我们真的用过)

  • 预热:提前 30 分钟跑一次 CALL ensure_order_partitions(18),确认 p_month 覆盖到下下个月。
  • 主机健康:确认 iostat -x 1 中 util < 70%、await 稳定;Threads_running < 128。
  • 慢日志:打开 long_query_time=0.2,配合 pt-query-digest 做实时归并。
  • 写入洪峰:出现 WAL fsync 压力时(innodb_log_waits 升高),在业务负责人签字后,将 innodb_flush_log_at_trx_commit 从 1 临时降为 2(牺牲崩溃后一秒内数据持久性)。
  • 连接风暴:ProxySQL 限流+后端 max_connections 增至 6000,同时用 热身连接(连接保活)。
  • 热点索引:如 seller_id 单商家大促出现 范围写/读热点,临时扩容副本,指定商家后台路由到专用只读库。
  • 回滚计划:一旦风暴退去立即恢复 innodb_flush_log_at_trx_commit=1,并做一次 mariabackup 增量。

10. 真实踩坑与修复记录

坑 A:长事务阻塞分区维护。一次报表误把 SET autocommit=0 忘了提交,导致 ALTER TABLE ... ADD PARTITION 卡住 20 分钟。之后我们在 Prometheus 加了 innodb_trx 超时告警,超过 300 秒直接通知 DBA。

坑 B:唯一约束没带分区键。初版把 UNIQUE(order_no) 漏了 p_month,导致建表失败。改为 UNIQUE(order_no, p_month) 并在代码层把 order_no 生成里加入月份前缀,排查成本大幅降低。

坑 C:DATE(created_at) 写法导致不裁剪。中台一个列表用 DATE() 包裹,扫描了半年分区。最后统一封装 DAO 的时间查询,强制闭开区间写法。

坑 D:自增热点+批量插入。供应链导入用 INSERT ... VALUES (...),(...); 一次 5k 行,造成间歇 trx_sys mutex 压力。改为 1k/批 + 延迟 10ms,峰值更稳。

11. FAQ(真实问答)

Q:分区会不会让单分区太大?A:按月切分,单月 1 亿行以内完全没问题;如果某月超大,可对“异常商家”单独拆表或建立“热点分区表”临时承载。

Q:能否用子分区(Subpartition)?A:可以,但复杂度/收益比不高。我们在 10.11 上评估后决定只用单层 RANGE,保持 DDL 简单。

Q:为什么不用 Sharding?A:分区能满足 95% 的诉求且迁移成本低。Sharding 留作两年后的量级再说。

12. 从“告警狂响”到“稳过大促”的 48 小时

第一晚 19:00,我在机房接过交接电话,业务侧说“今晚预售,明天零点冲击”。我们先把分区自动化跑了一遍,确认未来两个月妥当;ProxySQL 的读写规则再走一次演练。22:00,风控同学忽然跑了个大 SQL,Threads_running 飙到 300,我让他把 DATE(created_at) 改成闭开区间,P95 立刻从 800ms 掉到 150ms。零点一到,TPS 像电梯一样冲到 12 万,innodb_log_waits 开始冒泡,我们按预案把 flush_log_at_trx_commit 降到 2,稳定在 0~1 交替;2 点半开始回落,3 点恢复到 1。第二天白天复盘:

  • 分区裁剪贡献了最直接的尾延迟下降;
  • WAL 压力通过“有条件降级”化解;
  • 读写分离把读热点打散到了就近机房;
  • 两个历史坑(唯一约束不含分区键、DATE() 包裹)被永久修复。

我把最后一杯便利店咖啡倒进垃圾桶,走出机房时太阳刚好从维港那边露出来。电商的世界没有“万无一失”,但只要每次大促都多留下一点可复用的手册和脚本,下次就会更稳更轻松。

13. 附录:可抄走的脚本与命令

13.1 快速检查当前分区覆盖

SELECT PARTITION_NAME, PARTITION_DESCRIPTION
FROM information_schema.PARTITIONS
WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='orders'
ORDER BY PARTITION_DESCRIPTION;

13.2 一次性预创建未来 18 个月分区(无事件时)

DELIMITER //
CREATE PROCEDURE add_partitions_18m()
BEGIN
  DECLARE i INT DEFAULT 0;
  DECLARE pm INT;
  WHILE i < 18 DO
    SET pm = CAST(DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL i MONTH), '%Y%m') AS UNSIGNED);
    IF NOT EXISTS (
      SELECT 1 FROM information_schema.PARTITIONS
      WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'orders'
        AND PARTITION_NAME = CONCAT('p', pm)
    ) THEN
      SET @sql = CONCAT('ALTER TABLE orders DROP PARTITION pMAX, ADD PARTITION (PARTITION p', pm,
                         ' VALUES LESS THAN (', pm, ')), ADD PARTITION (PARTITION pMAX VALUES LESS THAN (MAXVALUE))');
      PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
    END IF;
    SET i = i + 1;
  END WHILE;
END //
DELIMITER ;
CALL add_partitions_18m();

13.3 常用观测 SQL

-- 当前并发
SHOW GLOBAL STATUS LIKE 'Threads_running';
-- InnoDB 日志等待
SHOW GLOBAL STATUS LIKE 'innodb_log_waits';
-- Binlog 队列(从库延迟)
SHOW REPLICA STATUS\G
-- 热点索引 TopN(需 performance_schema)
SELECT OBJECT_SCHEMA, OBJECT_NAME, INDEX_NAME, COUNT_STAR
FROM performance_schema.table_io_waits_summary_by_index_usage
ORDER BY COUNT_STAR DESC LIMIT 10;

13.4 还原备份演练(节选)

mariabackup --prepare --target-dir=/backup/full-2024-09-01
systemctl stop mariadb
rm -rf /var/lib/mysql/*
mariabackup --copy-back --target-dir=/backup/full-2024-09-01
chown -R mysql:mysql /var/lib/mysql
systemctl start mariadb

如果你把这些步骤在测试环境完整走通、并把“分区自动化 + 路由规则 + 观测”三件套固化成脚本,大促当天你会比我那晚更从容。祝你稳过每一次流量海啸。

凌晨 03:10,最后一波流量退下去,我把 flush_log_at_trx_commit 调回 1,慢日志关回 0.5 秒,跑了一次 mariabackup --backup。大屏上的 P95 曲线像心电图一样平顺下来,主从延迟稳在 0.4 秒。我把耳机摘下,走到过道尽头的窗前,维港那边开始露出一点灰白,机房风依旧冷,手心却因为紧张放松下来而微微发热。

我们在 War Room 做了 30 分钟快速复盘:

  • 对的:分区裁剪让商家后台和风控的热点查询只打到当月/上月,IO 尾延迟直接降了一个台阶;读写分离把“读洪峰”甩给了就近只读库;WAL 压力用“带签字降级”可控化解。
  • 还可以更好:影子表双写监控需要补一条“跨分区对账”的校验;evt_ensure_orders_part 的告警要挂在 Duty Pager 上,不靠人盯日常跑批。

我返回机柜给几台 NVMe 的指示灯拍了张照——那是我们今晚的功臣。技术人也需要一点仪式感:把可重复的经验沉淀成脚本、把可靠的流程写成 Runbook、把踩过的坑钉在 Checklist 上。下次大促来临前,我们要做的,只是按下播放键,再加一点点优化。

如果你也在为大促发愁,带走这篇文档里能直接落地的表结构、事件、路由、观测与应急手册。等到零点倒计时的时候,希望你也能像今晚的我一样,站在冷通道里,听着风和风扇的合奏,心里却很稳——因为数据库知道“该去哪个分区”。