数据库基本设计规范
所有的字符存储与表示,均以 utf-8 或者 utf8mb4 编码,表和字段需要有注释信息。
尽量避免使用大事务,建议使用短事务、轻操作以及顺序访问,即事务持续时间尽量短、避免在事务中进行远程 RPC 访问,事务批量梳理的数据尽量少、 事务更新业务表的顺序需要保持一致,让事务走相同的加锁顺序。避免无索引的 FOR UPDATE,极易导致全表范围锁。
说明:
例如在一个事务里进行多个 SELECT 或 UPDATE 语句,如果是高频事务,会严重影响并发能力,因为事务持有的锁等资源只在事务 ROLLBACK/COMMIT 时才能释放。但同时也要评估数据写入的一致性。
库表规范
TDSQL Boundless 支持普通表、分区表和同步表,在建表时必须明确表类型,三种类型不可互转(除单表→分区表外)。
说明:
TDSQL Boundless 支持的表类型如下:
表类型 | 适用场景 | 特点 |
普通表(单表) | 数据量小、无分布需求 | 所有数据在一个复制组内 |
分区表 | 大数据量、需水平扩展 | 数据按规则分布到多个服务组内 |
同步表 | 系统配置、维度表、参数表、读多写少小表 | 全节点强同步副本,若频繁写入可能出现写入卡顿,如 Follower 故障时卡一个 Lease |
创建分区表时,主键和所有唯一索引必须包含分区键,同时分区数建议为预期最大节点数 × 2,分区数过少会导致扩容时数据分布不均,过多增加管理开销,且会引入一定的性能损失。
说明:
TDSQL Boundless 支持的分区类型同 MySQL 8.0,具体如下,包括一级分区与二级分区(二级分区仅支持 HASH 和 KEY 模式):
策略 | 适用场景 | 键类型 |
HASH | 均匀散列,整数键 | 整数类型 |
KEY | 均匀散列,字符串键 | 任意类型 |
RANGE | 时间范围、ID 区间查询 | 整数/日期 |
LIST | 枚举值分类(如地区) | 整数 |
单表可在线更改为分区表,但运行时修改分区定义可能需要做拷表 DDL,DDL 时长和 IO 开销随数据量增加。
ALTER TABLE t PARTITION BY HASH(id) PARTITIONS 16;不建议为频繁更新写入的表创建为同步表。
说明:
同步表写入时需强同步到所有有效 Follower 副本才返回,每个副本可提供强一致性读,因此不适合大量写入的场景,尤其是副本数量多时写性能线性下降,谨慎在大集群中使用。当任意 Follower 节点故障时,写请求会卡顿一个租约时间(lease 时间,约秒级)。
同步表不支持转换为普通表与分区表。
广播同步日志流一旦创建不会被销毁,即使删除所有同步表。
创建同步表的 SQL 语法中
SYNC_LEVEL = NODE(ALL) 和 DISTRIBUTION = NODE(ALL) 目前只支持 ALL,不能指定部分节点。数据表必须显式定义主键,推荐使用复合主键或带随机散列前缀的主键。
说明:
无主键表容易导致写入热点。
不推荐使用自增列或单调递增列(例如,时间字段)作为主键,由于单调递增会造成写入热点。
若业务必须保留自增列,则推荐自增列作为唯一索引,否则可能出现值重复。自增值只保证全局唯一,不保证连续。
说明:
自增列默认存在分片缓存机制,
tdsql_auto_increment_batch_size 默认100。若业务要求连续自增,需设置
tdsql_auto_increment_batch_size=1,连续自增可能导致热点以及性能下降。单表列数建议不超过60。
单行数据建议不超过64KB。
表名建议控制在32字符以内。
小数类型需使用 decimal 类型来定义,禁止使用 float 和 double。
说明:
float 和 double 在存储的时候,存在精度损失的问题,很可能在值比较的时候得到的结果有误。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。
禁用保留字,如 desc、range、match、delayed 等,请参考 MySQL 官方保留字。此外,除 MySQL 保留字外,TDSQL Boundless 本身新增为:CLUSTERING,COROUTINE,DISTRIBUTION,POLICY,SEQUENCE_TABLE,FLASHBACK,TDSQL_AWR,UNLOAD。
创建表时需为表中的字段设置默认值,并且将字段设置为 NOT NULL,以避免在插入数据时出现空值或缺失值的情况,数字类型默认值推荐给0,VARCHAR 等字符类型的默认值推荐空字符串,如''。
建议表包含两个字段:create_time,update_time,且均为 datetime 类型。
说明:
在数据库出现意外时可以判断数据进入数据库和修改的时间,在极端情况可以帮助数据恢复的判断。
如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
字段允许适当跨表冗余,以避免关联查询,提高查询性能,但必须考虑数据一致性。
说明:
冗余字段应遵循:
不是频繁修改的字段。
不是 varchar 超长字段和 text 字段。
设置合适的存储长度(不建议使用 LONG TEXT,BLOB 等长类型字段),不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。
数据库 SQL 查询规范
当使用 ORDER BY .. LIMIT 查询时,优先考虑通过索引优化查询语句,提高执行效率。
使用 ORDER BY、GROUP BY、DISTINCT 执行查询时,WHERE 条件过滤出来的结果集请保持在1000行以内,否则会降低查询效率。
使用 ORDER BY、GROUP BY、DISTINCT 语句时,优先利用索引检索排序好的数据。如 WHERE a=1 ORDER BY b 可以利用 key(a,b)。
使用 JOIN 连接查询时,WHERE 条件尽量充分利用同一表上的索引。
说明:
例如,select t1.a, t2.b from t1,t2 where t1.a=t2.a and t1.b=123 and t2.c=4。
如果 t1.c 与 t2.c 字段相同,那么 t1上的索引 (b,c) 就只用到 b。此时如果把 WHERE 条件中的 t2.c=4 改成 t1.c=4,那么可以用到完整的索引。这种情况可能会在字段冗余设计(反范式)时出现。
推荐使用 UNION ALL,减少使用 UNION,需要考虑是否需要对数据进行去重。使用 UNION ALL 不对数据去重,由于少了排序操作,速度快于使用 UNION,如果业务没有去重的需求,优先使用 UNION ALL。
在代码中实现分页查询逻辑时,若 COUNT 为0应直接返回,避免执行后面的分页语句。
避免频繁对表进行 COUNT 操作。对大数据量表进行 COUNT 操作耗时会较长,一般都是秒级响应速度。如果有频繁对表进行 COUNT 操作的需求,请引入专门的计数表解决。
确定返回结果只有一条时,使用 LIMIT 1。在保证数据无误的前提下,可以确定结果集数量时,尽量使用 limit 查询,尽可能快速返回结果。
评估 DELETE 和 UPDATE 语句效率时,可以将语句改成 SELECT 后执行 EXPLAIN(explain 命令可以帮助我们分析 SQL 查询语句的执行计划和性能瓶颈)。但需注意,如果频繁执行 SELECT 语句会导致数据库性能慢,因此在使用 explain 命令分析 SQL 查询语句时,需尽量减少 SELECT 语句的执行次数,在分析 SQL 查询语句时,需要综合考虑查询效率和数据库性能,权衡利弊,选择最优的方案。
TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和日志资源少,如果进行全表删除,建议使用 TRUNCATE TABLE。
说明:
TRUNCATE TABLE 不会把删除的数据写到日志文件中。
TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同。
TRUNCATE TABLE 不能和其它 DML 写在同一个事务里。
尽量不要使用负向查询,避免全表扫描。
说明:
使用负向查询是指使用负向运算符,如:NOT, !=, <>, NOT EXISTS, NOT IN 以及 NOT LIKE 等。如果使用负向查询,无法利用索引结构做二分查找,只能做全表扫描。
避免对三个表以上执行 JOIN 连接。需要 JOIN 的字段,数据类型必须保持一致。
多表关联查询时,保证被关联的字段需要有索引;在多表 JOIN 中,尽量选取结果集较小的表作为驱动表,用来 JOIN 其他表。即使双表 JOIN 也要关注表索引、SQL 性能情况。
数据库 SQL 开发规范
建议分区表查询时在 WHERE 条件中携带分区键,避免全分区扫描。
不更新分区表的分区键。
说明:
不要通过 UPDATE 修改分区键的值,会触发跨分区数据迁移,性能差且可能失败。
避免
SELECT *,明确列出所需字段。对于简单 SQL,优先考虑拆分。
说明:
如 OR 条件:f_phone='10000' or f_mobile='10000',两个字段各自有索引,但只能用到其中一个。可以拆分成2个 SQL,或者使用 union all。
需要在 SQL 中进行复杂的运算或业务逻辑时,优先考虑在业务层实现。
大范围查询(如全表扫描)建议添加
LIMIT 保护。使用合理的分页方式以提高分页效率,大页情况下不使用跳跃式分页。
说明:
例如有类似下面分页语句:
SELECT * FROM table1 ORDER BY ftime DESC LIMIT 10000,10;这种分页方式会导致大量的 IO。
推荐分页方式:即传入上一次分页的界值。
SELECT * FROM table1 WHERE ftime < last_time ORDER BY ftime DESC LIMIT 10;减少使用 in 操作,in 后的集合元素数量不超过500个。
为了减少与数据库交互的次数,可以适度采用批量 SQL 语句。例如:INSERT INTO … VALUES (XX),(XX),(XX)....(XX); 这里 XX 的个数建议100个以内。更大批量的数据加载,建议使用 Bulk Load 模式加载(
tdstore_bulk_load 参数),性能可提升数倍,详细请参见 关于数据导入(Bulk Load 模式)。避免使用隐式类型转换。
说明:
类型转换规则具体如下:
1. 两个参数至少有一个是 NULL 时,比较的结果也是 NULL,特殊情况是使用 <=> 对两个 NULL 做比较时会返回 1,这两种情况都不需要做类型转换。
2. 两个参数都是字符串,会按照字符串来比较,不做类型转换。
3. 两个参数都是整数,按照整数来比较,不做类型转换。
4. 十六进制的值和非数字做比较时,会被当做二进制串。
5. 参数是 TIMESTAMP 或 DATETIME,并且另外一个参数是常量,常量会被转换为 timestamp。
6. 有一个参数是 decimal 类型,如果另外一个参数是 decimal 或者整数,会将整数转换为 decimal 后进行比较,如果另外一个参数是浮点数,则会把 decimal 转换为浮点数进行比较。
7. 其他情况下,两个参数都会被转换为浮点数再进行比较。
8. 如果一个索引建立在 string 类型上,如果这个字段和一个 int 类型的值比较,符合上述第7条。如 f_phone 定义的类型是 varchar,但 WHERE 语句中使用 f_phone in (098890),两个参数都会被当成浮点型。这种情况下 string 转换后的 float,导致 MySQL 无法使用索引,导致出现性能问题。如果是 f_user_id ='1234567' 的情况,符合上述第2条,直接把数字当字符串比较。
业务允许的情况下,事务里包含 SQL 语句越少越好,尽量不超过5个。因为过长的事务会导致锁数据较久、MVCC 版本积压、事务等待、连接消耗过多等问题。
业务允许的情况下,避免超大事务,单事务不超过1GB,超大批量删除操作推荐通过 BATCH LIMIT 拆分。
避免无索引的 FOR UPDATE:极易导致全表范围锁。
避免使用自然连接(natural join)。
说明:
自然连接没有显式定义连接列,而是隐含,会出现难以理解及无法移植问题。
TDSQL Boundless 支持 Read Committed (RC) 和 Repeatable Read (RR) 两个隔离级别,默认 RR 隔离级别。
说明:
RR 实为快照隔离(Snapshot Isolation),不会出现幻读,但可能出现写偏斜(Write Skew)。
RC 级别下与 MySQL 差异:TDSQL Boundless 在 RC 下仍可能使用范围锁(MySQL RC 不加间隙锁)。
事务数据在提交前完全缓存在内存中,大事务落盘功能尚在开发中。
支持三类事务开启方式。
BEGIN;START TRANSACTION;SET autocommit = 0;说明:
读快照(ReadView)在第一条读写 SQL 执行时分配,不是 BEGIN 时。
支持
SAVEPOINT 部分回滚。支持悲观事务:
SELECT ... FOR UPDATE(排他锁)/ SELECT ... LOCK IN SHARE MODE(共享锁)无索引的悲观锁会升级为范围锁,可能锁整表,务必确保查询条件有索引。
说明:
TDSQL Boundless 的锁基于逻辑键值而非物理存储,与 InnoDB 行为有差异。
记录不存在时行为与 MySQL 不同,不会升级为间隙锁。
数据库索引设计规范
根据实际业务需求,减少使用无法利用索引优化的 order by 查询语句。ORDER BY、GROUP BY、DISTINCT 这些语句较为耗费 CPU 资源。
涉及到复杂 SQL 语句时,优先参考已有索引进行设计,通过执行
EXPLAIN,查看执行计划,利用索引,增加更多查询限制条件以及确认分区裁剪(partition pruning)是否生效。使用新的 SELECT、UPDATE、DELETE 语句时,都需要通过 EXPLAIN 查看执行计划中的索引使用情况,尽量避免 extra 列出现:Using File Sort,Using Temporary。当执行计划中扫描的行数超过1000时,需要评估是否允许上线。需每日进行慢日志统计分析,处理慢日志语句。
说明:
EXPLAIN 解读:
type:ALL, index, range, ref, eq_ref, const, system, NULL(从左到右,性能从差到好)。
possible_keys:指出 MySQL 能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用。
key:表示 MySQL 实际决定使用的键(索引),如果没有选择索引,键是 NULL。要想强制 MySQL 使用或忽视 possible_keys 列中的索引,在查询中使用 FORCE INDEX、USE INDEX 或者 IGNORE INDEX。
ref:哪些列或常量被用于查找索引列上的值。
rows:根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数。
Extra:
Using temporary:表示 MySQL 需要使用临时表来存储结果集,常见于排序和分组查询。
Using filesort:MySQL 中无法利用索引完成的排序操作称为“文件排序”。
Using index:表示使用索引,如果只有 Using index,说明没有查询到数据表,只用索引表即完成了这个查询,这种情况为覆盖索引。如果同时出现 Using where,代表使用索引来查找读取记录,也是可以用到索引的,但是需要查询到数据表。
Using where:表示条件查询,如果不读取表的所有数据,或者不仅仅通过索引就可以获取所有需要的数据,则会出现 Using where。如果 type 列是 ALL 或 index,而没有出现该信息,则您有可能在执行错误的查询,返回了所有的数据。
在 WHERE 条件列上使用函数,会导致索引失效。
说明:
如 WHERE left(name, 5) = 'zhang',left 函数会导致 name 上的索引失效。可在业务侧修改该条件,不使用函数。当返回结果集较小时,业务侧过滤满足条件的行。
数据库权限规范
所有 DDL(例如:创建表,更改表结构等)只有通过评审后,由 DBA 通过数据库管理工具(DMC)执行,在业务低峰期操作上线。大表加索引时,默认使用 Online DDL,即 ALGORITHM=INPLACE(默认),不阻塞业务读写,建议监控 INFORMATION_SCHEMA.DDL_JOBS 检查 DDL 执行进度。
说明:
1. 执行 DDL 前必须检查:
1.1 确认存储空间充足(INPLACE DDL 需要临时空间)
1.2 检查是否有慢查询或长事务
1.3 查看数据分布是否均衡(严重倾斜时降低并行度)
1.4 确认当前 CPU/IO 负载在合理范围
2. 数据倾斜严重时,降低 DDL 执行的并行度(默认 8)
SET max_parallel_ddl_degree = 4; -- 或 23. 执行过程中建议监控 DDL 进度
SELECT * FROM information_schema.ddl_jobs WHERE state != 'done';不建议修改表的主键,建议在建表时设计好主键。
说明:
不支持 Online 方式添加/删除主键,需要 COPY 方式,会锁表。
当前不支持 Online DDL 的操作,即需要锁表与 Copy 数据的操作,建议降低 DDL 执行的并行度。
说明:
所有主键相关 DDL(Add/Drop Primary Key)
EXCHANGE PARTITIONANALYZE/CHECK PARTITION修改字符集(某些情况)
修改列类型(某些情况)
说明:
与 MySQL 8.0 差异:
TDSQL Boundless 在
PARTITION BY、Converting charset、Changing column type 上支持 DML 并发,MySQL 不支持。权限需要进行细粒度控制,读写权限分开,运维和开发权限要分开。
DDL 操作保留操作日志。