表设计应拒绝大宽表,按业务变更频率拆分;外键需显式声明但避免跨服务强依赖;枚举字段须建独立表并加CHECK约束;视图仅用于简单过滤,复杂聚合用物化视图;索引须匹配真实查询模式,复合索引顺序关键;迁移脚本必须可重入且带验证。

表设计阶段就要拒绝“大宽表”
宽表在初期看似省事,但很快会成为查询性能、权限管理、变更风险的源头。字段超过 30 列的表,大概率存在职责混杂——比如把用户基本信息、订单统计、风控标签全塞进 users 表里。
- 拆分依据不是“能不能 JOIN”,而是“业务变更频率是否一致”:用户资料每月改一次,而登录日志每秒写入,必须分离到
users和user_login_events - 外键约束宁可显式声明,也不要靠应用层“保证一致性”;但也要避免跨微服务强外键,此时用逻辑关联 + 定期校验更实际
- 枚举类字段(如
status)别用字符串硬编码,建独立status_types表并加CHECK约束,否则后续改名或扩值域时全库UPDATE风险极高
视图和物化视图的使用边界
视图不是银弹。普通 VIEW 只是保存 SQL 文本,嵌套三层后,SELECT * 可能触发全表扫描+重复计算,而你根本没意识到。
- 简单查询封装(如过滤掉已删除记录)用
VIEW没问题:CREATE VIEW active_orders AS SELECT * FROM orders WHERE deleted_at IS NULL - 涉及多表聚合、窗口函数、或高频访问的报表逻辑,优先考虑物化视图(PostgreSQL 9.3+ 的
MATERIALIZED VIEW,或 MySQL 用定时任务+汇总表) - 物化视图更新策略必须明确:是
REFRESH CONCURRENTLY(PG),还是容忍几分钟延迟?千万别在事务里调REFRESH MATERIALIZED VIEW,它会锁整个视图
索引不是越多越好,而是要匹配查询模式
加索引前先看慢查询日志里真实出现的 WHERE、JOIN、ORDER BY 组合,而不是凭感觉给所有 id 和 created_at 加索引。
NetShop软件特点介绍: 1、使用ASP.Net(c#)2.0、多层结构开发 2、前台设计不采用任何.NET内置控件读取数据,完全标签化模板处理,加快读取速度3、安全的数据添加删除读取操作,利用存储过程模式彻底防制SQL注入式攻击4、前台架构DIV+CSS兼容IE6,IE7,FF等,有利于搜索引挚收录5、后台内置强大的功能,整合多家网店系统的功能,加以优化。6、支持三种类型的数据库:Acces
- 复合索引顺序至关重要:
WHERE a = ? AND b > ? ORDER BY c最佳索引是(a, b, c),反过来就失效 -
LIKE '%abc'无法走 B-tree 索引,这类搜索要么用全文索引(tsvector),要么前置生成反向字符串索引 - 删除长期未被
pg_stat_all_indexes.idx_scan记录使用的索引,它们只拖慢写入、增大备份体积
迁移脚本必须可重入且带验证
ALTER TABLE ADD COLUMN 看似安全,但如果脚本重复执行,可能报错“column already exists”,导致自动化发布中断。
- 所有 DDL 脚本开头加判断:
DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_attribute WHERE attrelid = 'orders'::regclass AND attname = 'paid_at') THEN ALTER TABLE orders ADD COLUMN paid_at TIMESTAMPTZ; END IF; END $$; - 变更后立刻跟验证语句,比如加了非空约束,就跑
SELECT COUNT(*) FROM orders WHERE new_column IS NULL,不为 0 就中止后续步骤 - 生产环境禁止直接
DROP COLUMN,先设为NULL、删默认值、应用层停写、确认无读取后再真正删除
复杂度控制真正的难点不在技术选型,而在团队对“什么算合理拆分”的共识。一个字段该不该进这张表,往往卡在产品、后端、DBA 对同一业务术语的理解偏差上。这时候,比 SQL 更重要的是那张贴在白板上的实体关系草图,以及每次评审时敢问一句:“这个字段下次改的时候,会影响多少个服务?”









