上次改表结构的时候被卡住,才沉下心琢磨mysql外键怎么设置,之前一直凭着印象随便写语句,报出来的错误提示翻来覆去看不懂,改一处又冒出来别的报错,耗了快一个下午。
一开始直接在已有的两张表上加外键约束,主表是存放分类信息,从表用来存对应的条目,字段名都是cid,以为字段名一样就能绑上。执行语句之后,返回的报错信息一闪而过,只看明白约束创建失败,反复复制同一段sql来回跑,结果当然还是一样。那时候没往存储引擎上面想,默认建表的时候没指定,有一张表停留在myisam,这种引擎本身就不支持外键,不管sql写得多规整,都没法生成外键关系。
后来才反应过来,两张表都得换成innodb才行,alter改引擎的语句跑通之后,再往下走,又撞上别的问题。两个cid,一个设置成int,另一个定义成varchar,类型对不上,外键依旧建立不起来。不光是数据类型,长度、有无符号、是否允许为空这些属性也得保持一致,差一点都不行。
建外键的语句,我当时是这么写的,给从表增加约束,指定关联的主表和主表里面对应的字段,顺带写上删除和更新的动作。选的是ondeleterestrict,主表记录被引用的时候,不让直接删掉,避免从表出现孤立的数据。也试过ondeletecascade,主表删了,关联的子记录跟着一起清理,只是这个方式风险偏高,随便删一条主数据,连带一堆子数据消失,没确认清楚业务逻辑前不敢随便用。还有setnull,删完主记录,子表的外键字段置空,前提是这个字段本身允许null值。
建表的时候同步定义外键,和后期alter追加约束,两种写法都试过。新建表的时候直接写在字段后面,语句紧凑一点,适合提前规划好关联关系的场景;已经上线有数据的表,只能用altertableaddconstraint的方式添加,这个场景下还要额外检查表里面已有的脏数据,如果从表里面存在主表找不到匹配id的记录,添加约束的动作会直接终止,得先把这些异常数据清理干净。
同事那边习惯不在数据库层面设置外键,所有关联校验全部挪到后端代码里面处理,说数据库外键会拖累写入性能,大批量导入数据的时候容易锁表,迁移数据也麻烦。当时听完两边对比,一时拿不准哪种更合适。业务简单、数据量不大,希望靠数据库兜底数据一致性,加外键还算省心;高并发频繁写入的场景,很多人选择放弃数据库外键,靠程序控制关联逻辑。
这只是我的个人感受,不是标准答案。我只在几个内部小项目里这么试过,没有在超大流量的生产环境长期跑过,不同团队的开发规范不一样,选择也会跟着变。
还有一个容易忽略的小点,外键的索引。mysql会自动给从表上的外键字段生成索引,不用手动额外新建,但主表关联的字段,本身必须是主键或者有唯一索引,不然约束创建同样失败。之前有一回主表cid没设主键,只加了普通索引,也是一直报错,排查了好久才找到这个缘由。
测试的时候,建好外键之后试着在从表插入一条主表不存在的id,直接拦截,没法写入。去主表删除一条正在被从表引用的记录,操作被拒绝,和设置的restrict行为匹配。这些验证做完,才算确认这次的外键配置生效了。
到现在也不会每一张关联表都加上外键,遇到业务频繁迭代、数据经常批量同步的场景,大多会选择不在数据库层绑定。只是如果想依靠数据库本身守住基础的数据关联规则,就得把引擎、字段属性、已有脏数据、索引这几件事挨个核对一遍,少一步都容易卡住。
那天调试完,关掉数据库客户端,顺手把那段能跑通的sql存进了本地的笔记文档里,没有整理成可以复用的模板。