SQL表列存储ID列表是否属于不良实践?有无更优方案?
关于在RaceDetails表用单列存储玩家ID列表的设计判断
这个设计属于明确的不良实践,不建议这么做,核心问题有这几个:
- 查询性能极差:不管你是用逗号分隔字符串、JSON数组还是其他格式存ID列表,都没法对列表里的单个用户ID建立有效索引。后续要实现“查询某玩家参与过的所有对局”“统计某玩家的参赛次数/胜率”这类需求时,只能全表扫描做模糊匹配,数据量到十万级以上就会慢到无法接受。
- 数据一致性无保障:数据库层面无法对存储在列表里的ID做外键约束,一旦出现用户账号注销、ID异常变更的情况,列表里残留的无效ID没法被自动校验清理,时间长了会积累大量脏数据。
- 扩展性极差:后续只要加和对局内玩家相关的属性,比如玩家加入大厅的时间、准备状态、对局得分、是否中途退赛、队内位置这类信息,你单存ID的列根本承载不了,到时候还是得重构表结构,迁移历史数据的成本极高。
- 并发更新风险高:玩家进出大厅时,你需要先把整列的ID列表读出来,在内存里增删ID之后再写回数据库,高并发场景下很容易出现更新覆盖,导致玩家进出记录丢失;如果加分布式锁或者行锁规避这个问题,又会大幅降低大厅的并发承载能力。
推荐的最优实现方案
用经典的一对多关联表设计就可以,完全适配玩家数量不固定的场景,长期维护成本最低:
- 保留原有的
RaceDetails表,这张表只存储对局/游戏大厅本身的固有属性,比如对局唯一ID、创建时间、游戏模式、地图参数、大厅状态(等待中/进行中/已结束)、房主ID这类仅和单场对局相关的信息,主键设为对局IDrace_id即可。 - 新建专门的关联映射表
RacePlayers,用来存储每场对局和参与玩家的对应关系,基础表结构参考:
CREATE TABLE RacePlayers ( race_id BIGINT NOT NULL COMMENT '关联的对局ID', user_id BIGINT NOT NULL COMMENT '参与玩家的用户ID', join_time DATETIME NOT NULL COMMENT '玩家加入大厅的时间', player_status TINYINT NOT NULL DEFAULT 0 COMMENT '玩家状态:0未准备 1已准备 2对局中 3中途退出 4已结算', score INT DEFAULT 0 COMMENT '玩家本局得分', PRIMARY KEY (race_id, user_id), INDEX idx_user_id (user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
这个设计的优势非常明显:
- 所有查询都可以走索引,不管是查单场对局的玩家列表,还是查单个玩家的所有参与记录,都能做到毫秒级返回。
- 可以通过外键或者业务逻辑约束保证数据一致性,不会出现无效ID的脏数据。
- 扩展灵活,后续要加任何和对局内玩家相关的属性,直接在
RacePlayers表加字段即可,不需要修改原有核心表结构。 - 并发处理简单,玩家进出大厅只需要对
RacePlayers表单条记录做增删改操作,锁粒度极小,不会出现更新覆盖的问题。
唯一的例外情况:如果你使用的是PostgreSQL这类原生支持数组类型的数据库,且100%确定后续永远不会按单个玩家维度做查询统计、也不需要记录任何和玩家在对局内的附加属性,那用数组列存ID也不是完全不可行。但99%的游戏类应用都不满足这个前提,一开始就用关联表设计是踩坑最少的选择。
内容的提问来源于stack exchange,提问作者s_jack_frost
相关产品推荐
相关产品推荐

