AzerothCore学习笔记·数据库05:模板表设计——核心字段演化逻辑
数据库设计课上有一条铁律:宽表是坏味道。
字段多到几十上百行,不同类型的实体挤在同一张表里——这是"反范式",是设计缺陷,是迟早要还的技术债。
然后你去看 AzerothCore 的item_template:接近两百个字段;creature_template:item_entry表有 50+ 字段,外加creature_template本身 100+ 字段。两张核心表,每张都宽到离谱。
为什么?
答案要从 WoW 里到底有多少种东西说起。
一个表要描述多少种东西
AzerothCore 里有 25000+ 种怪物,20000+ 种物品。这 25000 种怪物差异极大:有的会施法,有的不会;有的是人形,有的是亡灵,有的是机械;有的掉金币,有的不掉。
如果严格按范式设计,应该拆成creature_template_base+creature_template_spells+creature_template_loot+creature_template_ai…… 拆十几张表,每种怪物只填充其中几张。
问题是:查询时要 JOIN 十几张表。怪物数据在游戏里是高频读取的——每只怪刷新时都要读模板。JOIN 十几张表,在 MMO 这种高并发场景下,性能代价是不可接受的。
物品的情况更极端。武器有伤害和速度,消耗品有使用效果和冷却,任务物品要关联任务ID,配方有技能要求——每种物品类型的字段集合完全不同。如果按类型拆表,item_template_weapon+item_template_armor+item_template_consumable…… 光是物品类型就有几十种,查询时要不 JOIN 很多表,要不写复杂的 UNION。
creature_template 的字段分类
宽表不是乱放。两百多个字段拆开看,分几大类:
基础属性:entry(模板ID)、name(名称)、faction(阵营)、npcflag(NPC功能标志)
战斗属性:mindmg/maxdmg(伤害范围)、attackpower、health/mana、armor
模型相关:modelid1~4(模型ID,最多4个,用于随机选模型)、HealthModifier/ManaModifier
AI相关:AIName(AI类型:NullAI、ReactorAI、PassiveAI、AggressorAI……)、ScriptName
掉落相关:lootid(掉落表ID)、pickpocketloot(偷窃掉落)、skinloot(剥皮掉落)
其他:spell(施放的法术列表)、movement(巡逻路径类型)、种族/职业限制(主要对玩家生效的NPC)
大部分字段对大部分怪物来说是 NULL 或默认值,但它们都在同一张表里。
拿一个具体例子:一只普通森林狼,modelid1=17550,mindmg=9,maxdmg=13,spell=0,AIName=NullAI——其余字段全是 NULL。但查这只狼时,一次 SELECT * 搞定,不用 JOIN 五张表。
宽表的取舍:空间换时间
AzerothCore 的选择:一个表,所有字段,用 NULL 表示"这个字段对我无效"。
好处很明显:一次 SELECT * 就能拿到这只怪物的全部数据,不需要 JOIN。磁盘空间便宜,CPU 和延迟贵——对于 MMO 服务端,这是正确的取舍。
字段是怎么"长"出来的
item_template和creature_template的字段不是一次性设计好的,而是随着 WoW 的每一次资料片逐步加出来的。
原版(2004):基础字段——名字、等级、属性
燃烧的远征(2007):加了韧性、法术穿透
巫妖王之怒(2008):加了成就相关字段
大地的裂变(2010):加新种族/职业兼容
后续资料片:持续加新字段……
如果去看creature_template的字段列表,会发现一些"奇怪"的字段——只对某个资料片的某种怪物有意义,但字段留在表里没人删。删字段是破坏性操作,所有已有的 template 数据都要迁移,成本远高于留着几个 NULL 字段。
这不是设计懒惰,而是演化式架构的必然结果:系统活着,需求在变,表结构只能加,很难减。
dbdocs:字段级的文档系统
字段越来越多,新人看表结构会完全懵掉——这个字段是干嘛的?默认值是多少?
AzerothCore 在data/sql/dbdocs/目录下给 World 库每一张表的每一个字段写说明。如果你想知道creature_template里AIName字段取什么值、是什么意思,直接查 dbdocs,不用翻代码或猜。
数据库 schema 本身不包含业务语义。AIName的值是字符串(“NullAI”、“PassiveAI”、“AggressorAI”……),这些字符串的含义只能在文档里说清楚。dbdocs 把这件事系统化了:每个字段有中文说明、取值范围、默认值、联动关系。对于 World 库这种"配置驱动"的库来说,这比写再多代码注释都管用。
为什么 Characters 库的表反而窄
回过头看,characters主表大概 50 个字段,character_inventory只有 10 个左右。
原因很简单:Characters 库里的每张表描述的是一个明确、稳定的概念。"角色"这个概念不会突然多出十几个新维度;"背包里有个物品"这个关系也很稳定。
World 库的模板表描述的是"游戏内容"——这是会持续膨胀的。每一次资料片加入新机制,都可能在模板表里加字段。
这就是「配置」和「状态」在表设计上的根本差异:配置表跟着游戏内容走,只会越来越多;状态表跟着玩家行为走,结构是稳定的。
回到开头的悖论:数据库课说宽表是坏味道,MMO 场景说宽表是正确选择。两者都对——因为场景不同,取舍就不同。AzerothCore 的模板表设计,本质上是对"读多写少、配置驱动、容忍 NULL、拒绝 JOIN"这四个原则的正确执行。这套取舍同样体现在 AzerothCore 的条件系统里——下篇聊的conditions表,是 World 库里最精巧的一张表。
