统一团队规范,避免后续维护灾难,核心原则: 科技新闻。
另外,我会要求每个表带上元数据四件套:id、create_time、update_time、is_deleted。这样做逻辑删除、追踪时间都很方便,配合 MyBatis-Plus 爽得一批。”
同时适当反范式,冗余一点常用字段,减少关联。
My背景与起因
就是我们上面用的虚拟列索引。把 JSON 里的 $.brand 提取出来建成虚拟列,再对这个虚拟列建索引,查询性能从全表扫变成了索引查找,零侵入业务代码。
KEY idx_user_status_time (user_id, status, create_time) 这个索引,如果只查 status = 1 或者只按 create_time 排序,索引竟然失效!全表扫描。
“这些难点全部是真实生产级的,看得出来你不是只停留在建表语法,而是把背后的性能、扩展性、容错都考虑到了。这轮回答非常扎实 👍。”
My事件经过
前期把扩展信息直接扔进 JSON 列,后来运营要求“查所有买了某品牌商品的订单”,瞬间傻眼,只能全表扫描。
几千万行的表,执行 SELECT COUNT(*) FROM t_order WHERE user_id = ? AND status = 0,耗时几秒,并发一高 CPU 直接打满。
order_no 是唯一索引,用户取消订单后逻辑删除(is_deleted=1)。当他再次下单生成相同订单号时,唯一约束报错。
My各方回应
对于扩展属性,我会用 JSON 类型,灵活。如果需要检索 JSON 里的某个字段,就建个虚拟列索引。
“你在项目里负责过不少数据库设计,那直接聊聊,MySQL建表的时候你最关注哪些点? 别背八股,说实际经验就行。”
“不错。刚才你提到时段用 DATETIME,为什么不用 TIMESTAMP?还有字符集和引擎一般怎么定?”
My影响分析
“SQL 写得很规范,虚拟列索引用得很秀。那实际开发中,这种设计遇到过哪些技术难点,又是怎么解决的?”
“TIMESTAMP 有时区转换问题,而且范围只到 2038 年,DATETIME 范围大又省心。
还有几个血泪避坑 🩸:绝不用外键,不留 ext1,ext2 这种预留字段,单表字段控制在 20 个以内。数据量预估到千万级别,就提前规划分库分表或分区。”
这是最容易被忽视但影响最大的部分,核心原则:用最小的数据类型满足业务需求
“没问题,我直接给一个近期电商订单表的设计,里面包含了我们刚聊到的大部分要点,还有一些进阶用法。”
“非常清晰。你刚才提的点里,能用一个表结构图直观展示一下你的设计习惯吗?”
字符集必须是 utf8mb4 + utf8mb4_general_ci,能存表情包 😂,千万别用那个假三字节的 utf8。这些通常在建库时就锁死,防止建表遗漏。”
“没问题,比如一个用户表模块,典型设计就是这样的:”
索引是 MySQL 性能的灵魂,建表时就要规划好,核心原则:少而精,按需创建
以下是我在实际项目中使用的、经过千万级数据验证的标准建表 SQL,所有技术亮点都在注释中标注:
联合索引必须从最左列开始匹配,跳过 user_id 直接查后面字段,索引无法利用。
InnoDB 没有存全表行数的计数器,每次都需要遍历索引或表。
“大字段拆表。文章内容单独放 t_article_content,和主表一对一靠主键关联,避免主表查询拖一堆 TEXT 字段。
这个案例就是 SQL 里那段 product_snapshot 的来源,完美兼顾了灵活性和性能。
引擎闭眼选 InnoDB,事务、行锁、崩溃恢复都靠它。
“这绝对是重灾区。我要求团队:
逻辑删除只是标记字段,物理记录还在,唯一索引无法识别“已删除不算重复”。
“理论很扎实。光说不练假把式,能不能把你平时建表的 SQL 贴一段出来,最好有亮点,能体现技术深度的。”
“痛点还真不少,我挑几个最有代表性的说。”
“嗯,规范很细。那索引呢?很多人建表就顺手 INDEX(user_id)、INDEX(create_time),你怎么看?”
“好,我从项目落地角度总结过 7个必抓的点,先讲最基础的:
“好,再深入一点。如果表里需要存大文本,比如文章内容,或者用户扩展属性经常变,你怎么设计?”
📌 主表放高频小字段,详情拆出大字段和 JSON,用 user_id 关联但不设物理外键。所有必备字段到位,既规矩又灵活。
“谢谢,我们项目确实因为这些设计吃过不少苦,所以现在建表时都会把这些预防针打在前头 😄。”