弹幕表主键不宜用自增ID,因会导致物理存储与时间顺序错位、引发B+树页分裂;应采用(video_id, ctime)联合主键,ctime为INT UNSIGNED秒级时间戳,确保写入有序、查询高效。

弹幕表主键为什么不能用自增 id?
因为弹幕是严格按时间顺序读写的,id 自增会导致物理存储和查询顺序错位:新弹幕写在表尾,但查询时要按时间范围扫全表或索引,I/O 效率极低。MySQL 的 B+ 树索引在时间递增写入时容易引发页分裂,尤其高并发下 INSERT 性能明显下降。
实操建议:
- 主键用
(video_id, ctime)联合主键(ctime是 UNIX 时间戳整数,非DATETIME),确保物理存储与播放时间一致 -
video_id放前,利于按视频分片或归档;ctime放后,保证同一视频内弹幕按时间局部有序 - 避免用 UUID 或雪花 ID 作主键——它们打乱写入顺序,对时间轴查询毫无帮助
如何让「按时间范围查弹幕」不走全表扫描?
核心是让查询条件能命中联合主键最左前缀。比如查 video_id = 123 且 ctime BETWEEN 1715000000 AND 1715003600,只有 video_id 在联合主键第一位,才能用上索引。
常见错误现象:
- 只查
ctime BETWEEN ...(没带video_id)→ 必然全表扫描 - 用
DATE(ctime)或FROM_UNIXTIME(ctime)包裹字段 → 索引失效 - 把
ctime设为DATETIME类型再建单独索引 → 类型转换 + 函数索引兼容性差,MySQL 5.7 不支持函数索引
实操建议:
-
ctime必须是INT UNSIGNED,存秒级时间戳(毫秒级会溢出,且弹幕精度到秒足够) - 查询必须带上
video_id条件,哪怕前端只传了时间范围,后端也得补个默认值或拒绝请求 - 如果真要跨视频查(如运营后台),单独建覆盖索引:
INDEX idx_ctime (ctime, video_id, content),但别用于线上播放逻辑
单条弹幕记录该存多少字段?
高频写入场景下,每多一个字段都增加磁盘 I/O 和网络传输开销。弹幕不是聊天记录,不需要完整用户上下文。
最小必要字段组合(已在线上验证过千万级/日写入):
-
video_id:BIGINT UNSIGNED,关联视频表 -
ctime:INT UNSIGNED,秒级时间戳(播放器送来的原始时间点) -
content:TEXT或VARCHAR(200),UTF8MB4 编码,限制长度防恶意刷屏 -
user_hash:CHAR(32),用户标识哈希(如 MD5(uid+salt)),不存明文user_id防隐私泄露和关联攻击 -
position:TINYINT UNSIGNED,弹幕位置(0=滚动,1=顶部,2=底部),业务强相关就保留,否则可删
坚决不要的字段:
-
created_at:和ctime冗余,且入库时间 ≠ 播放时间 -
ip、ua、device_id:这些应由网关层记录,不在弹幕正表里存 -
like_count、report_count:实时统计走 Redis,这里只存原始事实
数据量大了以后怎么不锁表做归档?
弹幕是典型的“写多读少、冷热分明”数据:95% 查询集中在最近 2 小时,3 天前的数据基本只用于审计或离线分析。硬删或 DELETE 会导致长事务、锁表、binlog 膨胀。
实操路径:
- 按
video_id+ctime分区:MySQL 5.7+ 支持RANGE COLUMNS(video_id, ctime),但实际效果一般;更稳的是按ctime做RANGE分区(如每月一个分区) - 归档不用
DELETE,改用ALTER TABLE ... DROP PARTITION,秒级完成,无锁 - 归档前先用
CREATE TABLE ... SELECT把旧分区数据导出到历史库(字段可精简,比如去掉user_hash只留脱敏摘要) - 注意:分区字段必须是主键一部分,所以联合主键
(video_id, ctime)是前置条件
容易被忽略的一点:分区不是银弹。如果 video_id 分布极度不均(比如头部视频占 80% 流量),单个分区可能依然巨大,这时得配合应用层分库,或者对热门 video_id 单独切分。










