外观
OceanBase 表索引设计规范
表设计规范
1. 表命名规范
- 表名:使用小写字母、数字和下划线组合,以字母开头,不超过 64 个字符
- 分区表:在表名后添加
_pt后缀,如order_pt - 历史表:在表名后添加
_history后缀,如order_history - 临时表:在表名前添加
tmp_前缀,如tmp_order
2. 字段命名规范
- 字段名:使用小写字母、数字和下划线组合,以字母开头,不超过 64 个字符
- 主键字段:使用
id或表名缩写 +_id,如order_id - 外键字段:使用关联表名缩写 +
_id,如user_id - 时间字段:
- 创建时间:
gmt_create - 修改时间:
gmt_modified - 业务时间:根据业务含义命名,如
order_time
- 创建时间:
3. 字段类型选择
| 业务场景 | 推荐类型 | 不推荐类型 |
|---|---|---|
| 整数 ID | BIGINT UNSIGNED | INT(可能溢出) |
| 字符串 ID | VARCHAR(64) | CHAR(空间浪费) |
| 枚举类型 | VARCHAR(32) | ENUM(修改困难) |
| 时间字段 | DATETIME(3) | TIMESTAMP(时区问题) |
| 金额字段 | DECIMAL(18,4) | FLOAT、DOUBLE(精度问题) |
| 状态字段 | TINYINT | INT(空间浪费) |
| 大文本 | LOB | VARCHAR(65535)(性能问题) |
4. 表结构设计原则
- 避免宽表:单表字段数建议不超过 50 个
- 避免冗余字段:尽量通过关联查询获取数据
- 合理设置默认值:为非空字段设置合理默认值
- 使用 NOT NULL:尽量避免使用 NULL,除非必要
- 合理设置 AUTO_INCREMENT:仅用于自增主键,避免用于业务字段
索引设计基础
1. 索引类型
| 索引类型 | 适用场景 | 优缺点 |
|---|---|---|
| B+ Tree 索引 | 范围查询、排序、分组 | 支持范围查询,查询效率高 |
| 哈希索引 | 等值查询 | 等值查询效率高,不支持范围查询 |
| 位图索引 | 低基数列,如性别、状态 | 空间效率高,适合位图运算 |
| 全文索引 | 文本搜索 | 支持全文检索,适合文本字段 |
2. 索引存储结构
- 聚簇索引:索引和数据存储在一起,主键默认为聚簇索引
- 二级索引:索引和数据分开存储,需要回表查询
- 联合索引:多个字段组成的索引,遵循最左前缀原则
3. 索引设计原则
- 选择性原则:选择选择性高的字段创建索引
- 最左前缀原则:联合索引的查询条件需包含最左字段
- 覆盖索引原则:查询字段全部包含在索引中,避免回表
- 最小化原则:索引字段数尽可能少
- 避免重复索引:避免创建重复或冗余索引
索引设计规范
1. 主键设计
- 推荐使用 BIGINT UNSIGNED:避免使用 VARCHAR 作为主键
- 自增主键:优先使用自增主键,避免主键值频繁更新
- 复合主键:仅在必要时使用,如多对多关系表
- 不使用业务字段:避免使用经常变化的业务字段作为主键
2. 唯一索引设计
- 唯一约束:对于需要唯一约束的字段,使用唯一索引
- 联合唯一索引:多个字段组合唯一时,使用联合唯一索引
- 避免过多唯一索引:唯一索引会增加写入开销
3. 普通索引设计
- 查询频繁的字段:为 WHERE、JOIN、ORDER BY、GROUP BY 中频繁使用的字段创建索引
- 联合索引顺序:将选择性高的字段放在前面
- 覆盖查询:设计联合索引覆盖常用查询字段
- 避免索引膨胀:定期清理无用索引
4. 索引创建规范
sql
-- 推荐:单列索引
CREATE INDEX idx_user_name ON user(name);
-- 推荐:联合索引,选择性高的字段在前
CREATE INDEX idx_order_user_time ON order(user_id, order_time);
-- 推荐:唯一索引
CREATE UNIQUE INDEX uk_user_email ON user(email);
-- 不推荐:冗余索引
CREATE INDEX idx_user_id ON order(user_id);
CREATE INDEX idx_user_id_time ON order(user_id, order_time); -- 冗余索引,因为 idx_user_id 已存在分区表设计规范
1. 分区类型选择
| 分区类型 | 适用场景 | 优缺点 |
|---|---|---|
| 范围分区 | 时间序列数据 | 便于数据归档和查询 |
| 哈希分区 | 随机分布数据 | 数据分布均匀,查询性能稳定 |
| 列表分区 | 枚举值分区 | 便于按枚举值管理数据 |
| 复合分区 | 复杂业务场景 | 结合多种分区优势 |
2. 分区键选择
- 范围分区:选择时间字段或自增 ID 作为分区键
- 哈希分区:选择选择性高的字段作为分区键
- 列表分区:选择枚举类型字段作为分区键
3. 分区表创建规范
sql
-- 推荐:按时间范围分区
CREATE TABLE order_pt (
order_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
order_time DATETIME(3) NOT NULL,
amount DECIMAL(18,4) NOT NULL,
PRIMARY KEY (order_id, order_time)
) PARTITION BY RANGE COLUMNS(order_time) (
PARTITION p202301 VALUES LESS THAN ('2023-02-01'),
PARTITION p202302 VALUES LESS THAN ('2023-03-01'),
PARTITION p202303 VALUES LESS THAN ('2023-04-01'),
PARTITION p202304 VALUES LESS THAN ('2023-05-01')
);
-- 推荐:按哈希分区
CREATE TABLE user_pt (
user_id BIGINT UNSIGNED NOT NULL,
name VARCHAR(64) NOT NULL,
PRIMARY KEY (user_id)
) PARTITION BY HASH(user_id) PARTITIONS 16;索引优化规范
1. 索引维护
- 定期分析表:使用
ANALYZE TABLE分析表统计信息 - 定期检查索引使用情况:通过
V$INDEX_USAGE视图查看索引使用情况 - 删除无用索引:删除长期未使用的索引
- 重建索引:定期重建碎片化严重的索引
2. 索引使用优化
sql
-- 推荐:使用索引覆盖查询
SELECT user_id, order_time FROM order WHERE user_id = 123;
-- 不推荐:使用 SELECT *,可能导致回表
SELECT * FROM order WHERE user_id = 123;
-- 推荐:使用最左前缀原则
SELECT * FROM order WHERE user_id = 123 AND order_time > '2023-01-01';
-- 不推荐:违反最左前缀原则
SELECT * FROM order WHERE order_time > '2023-01-01';
-- 推荐:使用索引列作为排序字段
SELECT * FROM order WHERE user_id = 123 ORDER BY order_time;
-- 不推荐:使用非索引列排序
SELECT * FROM order WHERE user_id = 123 ORDER BY amount;3. 索引性能监控
sql
-- 查看索引使用情况
SELECT * FROM V$INDEX_USAGE WHERE table_name = 'order';
-- 查看索引大小
SELECT index_name, ROUND(bytes/1024/1024, 2) AS size_mb FROM V$INDEX_SIZE WHERE table_name = 'order';
-- 查看慢查询中未使用索引的语句
SELECT * FROM V$SLOW_QUERY WHERE is_index_used = 0;常见索引设计错误
1. 过多索引
问题:创建过多索引会增加写入开销,降低性能 解决方案:
- 只创建必要的索引
- 定期清理无用索引
- 优先使用联合索引覆盖多个查询场景
2. 不当的联合索引顺序
问题:联合索引字段顺序不当,导致索引使用率低 解决方案:
- 将选择性高的字段放在前面
- 考虑查询频率和场景
- 遵循最左前缀原则
3. 索引字段类型不匹配
问题:查询条件字段类型与索引字段类型不匹配,导致索引失效 解决方案:
- 确保查询条件字段类型与索引字段类型一致
- 避免在索引字段上使用函数
4. 忽略覆盖索引
问题:未充分利用覆盖索引,导致回表查询 解决方案:
- 设计索引时考虑常用查询字段
- 使用
EXPLAIN分析查询计划 - 调整索引覆盖常用查询
5. 主键设计不当
问题:使用 VARCHAR 或频繁更新的字段作为主键 解决方案:
- 优先使用 BIGINT UNSIGNED 自增主键
- 避免使用业务字段作为主键
- 复合主键仅在必要时使用
索引设计最佳实践
1. 小表(< 100 万行)
- 主键索引:必须创建
- 唯一索引:根据业务需求创建
- 普通索引:根据查询需求创建,建议不超过 5 个
2. 中表(100 万 - 1000 万行)
- 主键索引:必须创建
- 唯一索引:根据业务需求创建
- 普通索引:精心设计,建议不超过 8 个
- 考虑使用覆盖索引减少回表
3. 大表(> 1000 万行)
- 主键索引:必须创建
- 唯一索引:根据业务需求创建
- 普通索引:严格控制,建议不超过 10 个
- 必须使用分区表
- 考虑使用二级分区
- 定期维护索引,清理无用索引
4. 热点表
- 使用分区表分散热点
- 考虑使用局部索引
- 避免在热点字段上创建过多索引
- 定期监控索引使用情况
索引设计案例
1. 订单表设计
sql
-- 表结构
CREATE TABLE `order` (
`order_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` BIGINT UNSIGNED NOT NULL,
`shop_id` BIGINT UNSIGNED NOT NULL,
`order_time` DATETIME(3) NOT NULL COMMENT '下单时间',
`pay_time` DATETIME(3) DEFAULT NULL COMMENT '支付时间',
`amount` DECIMAL(18,4) NOT NULL COMMENT '订单金额',
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '订单状态',
`gmt_create` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`gmt_modified` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`order_id`),
KEY `idx_user_time` (`user_id`, `order_time`),
KEY `idx_shop_time` (`shop_id`, `order_time`),
KEY `idx_status_time` (`status`, `order_time`),
KEY `idx_pay_time` (`pay_time`)
) COMMENT '订单表';2. 用户表设计
sql
-- 表结构
CREATE TABLE `user` (
`user_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`username` VARCHAR(64) NOT NULL COMMENT '用户名',
`email` VARCHAR(128) NOT NULL COMMENT '邮箱',
`mobile` VARCHAR(20) NOT NULL COMMENT '手机号',
`gender` TINYINT NOT NULL DEFAULT 0 COMMENT '性别',
`age` TINYINT UNSIGNED DEFAULT NULL COMMENT '年龄',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态',
`gmt_create` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`gmt_modified` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`user_id`),
UNIQUE KEY `uk_username` (`username`),
UNIQUE KEY `uk_email` (`email`),
UNIQUE KEY `uk_mobile` (`mobile`),
KEY `idx_gender_age` (`gender`, `age`)
) COMMENT '用户表';索引设计工具
1. OceanBase 内置工具
EXPLAIN 命令
sql
-- 查看查询计划
EXPLAIN SELECT * FROM order WHERE user_id = 123 AND order_time > '2023-01-01';
-- 查看详细执行计划
EXPLAIN EXTENDED SELECT * FROM order WHERE user_id = 123 AND order_time > '2023-01-01';索引建议工具
sql
-- 查看索引建议
SELECT * FROM V$INDEX_ADVICE WHERE table_name = 'order';2. 第三方工具
- OceanBase Developer Center (ODC):提供可视化的索引设计和优化建议
- SQL Advisor:自动分析 SQL 并提供索引优化建议
- 性能监控工具:Prometheus + Grafana 监控索引使用情况
常见问题(FAQ)
Q1: 如何确定一个字段是否适合创建索引?
A1: 可以通过以下方法判断:
- 计算字段的选择性:选择性 = 不同值数量 / 总行数,选择性越高越适合创建索引
- 分析查询频率:频繁作为查询条件的字段适合创建索引
- 考虑查询类型:范围查询、排序、分组操作需要索引支持
Q2: 联合索引的字段顺序如何确定?
A2: 联合索引的字段顺序应考虑以下因素:
- 字段的选择性:选择性高的字段放在前面
- 查询频率:频繁作为查询条件的字段放在前面
- 查询场景:覆盖更多查询场景
- 排序和分组需求:用于排序和分组的字段放在后面
Q3: 什么时候需要使用覆盖索引?
A3: 当查询的所有字段都包含在索引中时,可以使用覆盖索引,避免回表查询。适合以下场景:
- 频繁执行的查询,且查询字段较少
- 大表查询,回表开销较大
- 内存资源有限,需要减少 I/O 操作
Q4: 如何处理大表的索引维护问题?
A4: 处理大表索引维护的方法:
- 使用分区表,减少索引维护范围
- 在业务低峰期进行索引维护操作
- 使用
ALTER TABLE ... REBUILD INDEX进行在线索引重建 - 定期分析表统计信息,确保优化器选择正确的索引
Q5: 如何监控索引的使用情况?
A5: 可以通过以下方式监控索引使用情况:
- 查询
V$INDEX_USAGE视图查看索引使用次数和频率 - 使用慢查询日志分析未使用索引的查询
- 监控索引大小变化,及时清理无用索引
- 使用 OceanBase 监控工具查看索引相关指标
Q6: 主键和唯一索引有什么区别?
A6: 主键和唯一索引的主要区别:
- 主键是表的唯一标识,一个表只能有一个主键
- 主键字段不允许为 NULL,唯一索引字段允许为 NULL(最多一个)
- 主键默认是聚簇索引,唯一索引默认是二级索引
- 主键用于唯一标识记录,唯一索引用于保证字段唯一性
Q7: 如何优化频繁更新的索引字段?
A7: 优化频繁更新索引字段的方法:
- 避免在频繁更新的字段上创建索引
- 考虑使用局部索引
- 优化更新逻辑,减少更新频率
- 考虑使用其他方式保证数据一致性
Q8: 索引越多性能越好吗?
A8: 不是。索引越多,写入性能越差,因为每次写入都需要更新所有相关索引。索引设计应遵循以下原则:
- 只创建必要的索引
- 优先使用联合索引覆盖多个查询场景
- 定期清理无用索引
- 控制索引数量,大表建议不超过 10 个索引
Q9: 如何处理热点数据的索引问题?
A9: 处理热点数据索引问题的方法:
- 使用分区表分散热点
- 考虑使用局部索引
- 避免在热点字段上创建过多索引
- 使用读写分离,将查询流量分散到只读节点
Q10: 如何选择分区表的分区键?
A10: 选择分区表分区键的原则:
- 数据分布均匀
- 符合业务查询模式
- 便于数据管理和维护
- 考虑数据生命周期管理
常见的分区键选择:
- 时间字段:适合时间序列数据
- 哈希值:适合随机分布数据
- 业务标识:适合按业务维度分区
