广告位排期冲突需用between判断区间相交或exists子查询校验,配合(slot_id,start_time,end_time)联合索引;权重须用decimal(5,3)避免浮点误差与硬编码;ad_slots表应按查询模式垂直拆分;排期时间统一用datetime存utc。

广告位排期冲突怎么查?用 BETWEEN + EXISTS 防重叠
广告位不能同时被两个活动占用,但靠应用层校验容易漏——事务间隙里照样能插进冲突数据。必须在数据库层面锁死逻辑。
典型错误是只比对开始时间:start_time ?,这会漏掉「新排期完全包住旧排期」或「旧排期完全包住新排期」的情况。
- 正确写法是用
BETWEEN判断时间区间是否相交:WHERE ? BETWEEN start_time AND end_time OR ? BETWEEN start_time AND end_time - 更稳妥的写法是用
EXISTS子查询,在插入前查是否存在重叠:EXISTS (SELECT 1 FROM ad_slots WHERE slot_id = ? AND (? = start_time)) - 务必给
(slot_id, start_time, end_time)加联合索引,否则大表上EXISTS会慢成瓶颈
权重字段为什么不能直接存整数?得用 DECIMAL(5,3)
广告位权重常用于排序、流量分配,很多人图省事用 INT 存 1/2/3,结果发现没法表达「A 是 B 的 1.5 倍」这种比例关系,后期加权策略一改就全崩。
用整数硬编码权重,等于把业务规则塞进 schema,改个策略就得改表结构、改代码、刷历史数据。
- 推荐用
DECIMAL(5,3),覆盖 0.001~999.999,足够表达相对强度和小数级衰减 - 避免用
FLOAT,浮点误差会导致排序错乱,比如ORDER BY weight DESC把 0.30000001 排在 0.3 前面 - 如果权重依赖实时计算(如点击率 × 出价),别存冗余字段,用视图或应用层算;存了就得配套维护触发器或应用层一致性逻辑
ad_slots 表要不要拆分?看查询模式再决定
广告位本身是静态配置,但排期、曝光、点击这些是高频写入数据。混在一张表里,SELECT * FROM ad_slots 会拖慢所有管理后台请求,尤其加了 JSON 字段存创意元数据之后。
常见错误是过早分表,结果关联变多、事务变复杂;或者死扛不分,等 ad_slots 超过 500 万行后,一个 UPDATE 就锁表 3 秒。
- 如果 90% 查询只读广告位基础信息(id/name/size/status),就把排期、统计、创意等拆到
ad_slot_schedules、ad_slot_stats等独立表 - 保留
ad_slots主表极简:仅id、code、weight、status、updated_at,其他全外键引用 - 别为了“看起来规范”而硬上垂直分库——MySQL 单库撑千万级广告位数据没问题,关键是索引是否打中、查询是否走覆盖
排期生效时间用 datetime 还是 timestamp?选 datetime
广告排期必须精确到秒,且不随服务器时区变更而漂移。用 timestamp 看似省空间,但一旦 DBA 调了服务器时区,所有已存排期时间全乱。
另一个坑是 PHP/Java 应用层默认把 datetime 当成本地时区解析,入库前没统一转 UTC,导致跨地区投放时间错位。
- 字段类型固定用
DATETIME,不带时区语义,存值即所见 - 所有写入前,应用层强制转成 UTC 时间(例如 Python 用
dt.astimezone(timezone.utc)) - 读取后按业务需要转本地时区展示,别让 MySQL 做
CONVERT_TZ—— 大量并发下函数开销明显
权重不是数字游戏,排期也不是日历填充。真正难的是让「可配置」不变成「可误配」,所有字段设计都要预设至少一种人为输错的路径,并提前堵死。











