[系统设计 - 数据库设计专题]3. 表结构、字段与索引实战设计
编辑表字段设计的范式与反范式
1NF 第一范式:原子性
规则:列不可再拆分,一个单元格只能存一份原子数据
2NF 第二范式:消除「部分依赖」
规则:所有非主键字段,必须完全依赖【整个复合主键】,不能只依赖主键其中某一列
前置条件:必须先满足 1NF
适用场景:复合主键(多个字段联合做主键)
3NF 第三范式:消除「传递依赖」
规则:规则:非主键字段,不能依赖另一个非主键字段。 即非主键列之间不能存在推导关系。
前置条件:满足 1NF、2NF
BCNF 巴斯 - 科德范式
规则:任何依赖关系,左边必须是候选主键。
3NF 允许主键内部存在依赖;BCNF 堵上这个漏洞。
普通业务系统,绝大多数场景用到 3NF 就足够,一般不用追求 BCNF。
字段设计
索引设计
设计索引要从业务出发,尽可能覆盖业务本身,节省重复的索引空间。
普通索引(单列索引)
适用场景
- 单字段独立查询:
where phone = ?、where status = ? - 单列范围查询、排序:
create_time > 'xxxx' order by create_time - 该字段查询频率高,无其他字段经常搭配一起查询
SQL
-- MySQL / PostgreSQL 通用
CREATE INDEX idx_user_phone ON user(phone);
-- 删除索引
DROP INDEX IF EXISTS idx_user_phone;
-- 修改索引:不能直接修改,只能先删再重建
DROP INDEX IF EXISTS idx_user_phone;
CREATE INDEX idx_user_phone ON user(phone);
优点
- 结构简单,维护成本低;支持单列等值、范围、排序查询
- 相比全表扫描,大幅减少 IO
关键注意
- 索引占用空间与字段长度正相关;超长字符串建议使用前缀索引
- 写入(insert/update/delete)会同步维护索引,索引越多写入越慢
- InnoDB 二级索引叶子节点存储主键 ID,查询命中索引后需要回表
联合索引
适用场景
- 经常多字段组合查询:
where status=1 and type=2 - 等值条件 + 范围条件组合:
where status=1 and create_time > xxx - 查询条件 + 排序字段可以放进同一个索引,避免 filesort
-- MySQL / PostgreSQL 通用
-- 顺序非常重要!等值字段放前面,范围字段放后面
CREATE INDEX idx_order_status_ctime ON order(status, create_time);
-- 删除
DROP INDEX IF EXISTS idx_order_status_ctime;
优点
- 满足最左前缀原则时,可以同时覆盖多个查询条件
- 设计合理可以实现覆盖索引,直接从索引拿到全部查询字段,消除回表
- 相比建立多个单列索引,减少索引总数,降低写入开销
核心规则 & 坑
- 字段顺序铁律:等值条件在前,范围条件在后
- 遵循最左前缀,跳过前置字段会导致索引失效
- 不要把无关字段无脑塞进联合索引,索引变长,内存占用上升、区分度下降
- 索引变更:同样不支持 ALTER 直接修改,删除重建;调整字段顺序等于新建索引
前缀索引
对字符串字段只取前 N 个字符建立索引,节省空间
CREATE INDEX idx_email on users(email(64));
GIN / GiST 索引(PostgresQL)
- GIN:数组、JSONB、全文检索,精准查找
- GiST:模糊查询、范围、地理位置、近似检索
条件索引(Filtered Index)
PostgreSQL 原生支持;SQL Server 支持;MySQL 5.7 不支持,MySQL8.0.13 之后有限支持
在建索引时指定 WHERE 条件,只有满足条件的行才会存入索引,不满足条件的数据完全不出现在索引内。
优势:
- 索引体积大幅缩小(只保留业务高频访问子集)
- 降低写入开销:新增 / 更新数据不满足条件时,不需要维护索引
- 实现条件唯一约束(普通唯一索引做不到)
CREATE INDEX idx_user_active ON user(id, name, status, mobile) WHERE status='active';
-- 命中索引, SQL 过滤条件中必须包含索引定义的条件字段
SELECT * from "user" WHERE status='active' AND mobile='1380000000';
-- 无法使用此索引
SELECT * from "user" WHERE mobile='1380000000';
关联查询
高频问题
- InnoDB 为什么默认使用是 B+Tree 而非 B 树、Hash?
- 联合索引最左前缀原理
- 回表、覆盖索引、索引下推 ICP
- 聚簇索引与非聚簇索引区别
like 'abc%'能走索引,'%abc'不能(B+Tree 有序前缀)
- 0
- 0
-
分享