在工作中 MySQL 的一些开发规范(干货)!

2023年 7月 19日 40.0k 0

前言

大家好,我是路由器没有路。

今天跟大家聊下关于在工作中,MySQL 的一些开发规范,如有不当的地方,欢迎指正。

数据库设计规范

【推荐】字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应遵循:

  • 不是频繁修改的字段。
  • 不是 varchar 超长字段,更不能是 text 字段。

正例: 商品类目名称使用频率高,字段长度短,名称基本一成不变,可在相关联的表中冗余存 储类目名称,避免关联查询。

【推荐】单表行数超过 600 万行或者单表容量超过 5GB,才推荐进行分库分表。

说明:如果预计 3 年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。

【推荐】一个表的字段个数控制在 50 个字段以内;如果字段超过 50 个,可考虑将字段按冷热程度分表。

说明:这样做虽然会给应用带来更多的代码开发量,但对于热表来说,这样做可以提升 buffer 利用率,减少 IO,提升查询的效率。

【强制】字段字符集与表保持一致,不单独设置字符集。

【强制】 相同含义的字段在不同表中应使用相同的名称,数据类型及长度必须保持一致。

【推荐】id 必须是主键,每个表必须有主键,且保持增长趋势的, 小型系统可以依赖于 MySQL 的自增主键,大型系统或者需要分库分表时才使用 ID 生成器,如雪花生成器等。

【推荐】id 类型没有特殊要求,必须使用 bigint unsigned,禁止使用 int,即使现在的数据量很小。id 如果是数字类型的话,必须是 8 个字节。

  • 方便对接外部系统,还有可能产生很多废数据
  • 避免废弃数据对系统 id 的影响
  • 未来分库分表,自动生成 id,一般也是 8 个字节

【推荐】字段尽量设置为 NOT NULL, 为字段提供默认值。

  • 如字符型的默认值为一个空字符值串"";
  • 数值型默认值为数值 0;
  • 逻辑型的默认值为数值 0;

【强制】每个字段和表必须提供清晰的注释。同时,如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。

【推荐】关于时间的格式,推荐统一使用 Unix 时间戳格式

  • unix 时间戳(Unix Timestamp)是从 1970 年 1 月 1 日(UTC/GMT 的午夜)开始所经过的秒数,不考虑闰秒。
  • 因为 MySQL 关于日期的类型有 Date/ Datetime/ Timestamp 三种类型。对于时间的随便选择会导致数据库开发和业务逻辑中遇到不同类型的转换时经常出现混乱或者难以察觉的
  • unix 时间戳在 MySQL 上可以直接使用 bigint unsigned 存储,避免各种时间类型的不一致。也方便后台和数据库关于时间的比较。
  • 前端业务可以根据 unix 时间戳转换成自己需要的时间格式。

【强制】表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint( 1 表示是, 0 表示否)。

  • 任何字段如果为非负数,必须是 unsigned。

正例: 表达逻辑删除的字段名 is_deleted, 1 表示删除, 0 表示未删除。

【强制】表名、字段名必须使用小写字母或数字, 禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。

正例: t_admin,rdc_config,level3_name

反例: Admin, rdcConfig, level_3_name

【推荐】表名不使用复数名词。

  • 表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于 DO 类名也是单数形式,符合表达习惯。

正例: t_user, t_config

反例: t_users, t_configs

【强制】禁用保留字,如 desc、 range、 match、 delayed 等, 请参考 MySQL 官方保留字。

【推荐】 主键索引名为 pk 字段名; 唯一索引名为 uk 字段名; 普通索引名则为 idx 字段名。

说明: pk 即 primary key; uk* 即 unique key; idx* 即 index 的简称。

【强制】小数类型为 decimal,禁止使用 float 和 double。

说明: float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不 正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。

【推荐】如果存储的字符串长度几乎相等,使用 char 定长字符串类型。

【推荐】varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。

【强制】表必备三字段:id, f_create_time, f_modify_time。

其中 ID 必为主键,类型为 bigint unsigned。 f_create_time, f_modify_time 的类型均 unix 时间戳,前者现在时表示主动创建,后者过去分词表示被动更新。

【推荐】表的命名最好是加上“业务名称_表的作用”。

正例: t_task / force_project/trade_config

【推荐】库名与应用名称尽量一致。

【推荐】所有命名必须使用全名,有默认约定的除外,如果超过 30 个字符,使用缩写,请尽量名字易懂简短。如

information --> info;

address --> addr 等

【强制】业务应用禁止有 super/root 账号的存在

super 权限很大,一般是 DBA 才会用到,会导致 read only 失效,原则上不提供给应用账号使用。

【推荐】IP 地址的存储,ipv4 尽量使用 int unsigned 来存储,而不要使用 varchar(15)来存储,可以节省 11 字节,如果包括索引,可以节省 22 字节。

【强制】不允许不同业务模块的表连接查询。

【强制】禁止在数据库中存储明文密码。

【推荐】比较重要的数据删除操作使用逻辑删除(UPDATE table SET is_deleteflag=1)代替物理删除(DELETE FROM table WHERE ..)

【强制】InnoDB 表数据量特别大的避免使用 COUNT(*)操作,推荐计数统计实时要求较强可以使用 redis,非实时统计可以使用单独统计表,定时更新。

【强制】事务语句执行完成之后必须及时提交。

事务长时间不提交,如果这时对事务相关表执行 DDL 操作,会出现等待元数据锁的提示。DDL 语句被阻塞后,其他所有表上的正常操作(DML、SELECT)都会被阻塞。

【推荐】尽量避免或者拆分执行大事务。

无论在业务程序中,还是手动数据调整中都需要尽量避免执行大事务。对于影响行数过万的记录建议 DBA 审核后通过工具或脚本分批执行。

大事务的执行会给数据库稳定性带来很多问题。例如引起从库复制延迟、导致锁等待、系统脏数据 checkpoint 写入的性能抖动。

【推荐】对于历史数据、日志数据等数据量特别大的表(通常数据量超过千万,占用空间超过 10G),需要应用根据业务特点,预估业务数据增涨速度,提前在应用代码中进行分表操作(同一 MySQL 实例下的分表操作实现不难)。

通常可以按照时间(按年、按月)进行数据分表,存在明显的数据热点,比如最近一年或者最近三个月的数据是热点数据。其他数据是历史数据。历史数据很少进行修改,一般只要提供给业务进行分析查询即可。这样可以把历史数据归档到其他实例上,提供可供实时查询的接口便可。

DBA 有成熟的实现方案,主要是减少业务库中大量历史数据造成的磁盘空间紧张、数据备份、恢复慢的运维问题。

对于大表按规则进行分表也可提升业务并发读写性能。

【强制】严格禁止单条记录超过 8K

目前我们的 DB 一个 page 大小都设置为 16K,当一条记录超过 page 的一半(8K)时,记录中的 blob/varchar 会在行外存储。存取时会有额外 IO 消耗。插入操作会锁住整个聚簇索引(X Index Lock),直到插入完成才释放。

【推荐】字段约束,对于字典类型的表,因数据量小,修改少,影响面大,应依赖数据库约束来确保数据质量。对于日志或流水型表,为了提升效率,可以释放放宽限制。

【强制】库名、表名、字段名禁止超过 32 个字符。

库名、表名、字段名支持最多 64 个字符,但为了统一规范、易于辨识以及减少传输量,禁止超过 32 个字符。

【推荐】Mysql 数据库统一使用 innodb 存储引擎

原则上,业务范畴内的表都统一使用 innodb 存储引擎;如需使用其它存储引擎,需说明原因,并征得 DBA 同意。集团标准化部署设置默认使用 innodb 引擎。

【推荐】数据库设计不建议使用这样扩展字段:attr1,attr2,attr3…,业务需要什么字段使用时再添加。

使用扩展字段,扩展字段名字和注释不好维护。

新版本的数据库新增和删除字段成本相对比较低。

【推荐】关于建模中遇到字段命名规范有疑问请参考数字办已经制定规范。

数字办已经有规范相关字段命名的工具

数据类型规范

【强制】表示状态字段(0-255)的使用 TINYINT UNSINGED,禁止使用枚举类型,注释必须清晰地说明每个枚举的含义,以及是否支持多选等。

【强制】表示 boolean 类型的都使用 TINYINT(1)

因为 MySQL 本身是没有 boolean 类型的,在自动生成代码的时候,DO 对象的字段就是 boolean 类型,例如 is_delete;其余所有时候都使用 TINYINT(4)。

TINYINT(4),这个括号里面的数值并不是表示使用多大空间存储,而是最大显示宽度,并且只有字段指定 zerofill 时有用,没有 zerofill,(m)就是无用的,例如 id BIGINT ZEROFILL NOT NULL,所以建表时就使用默认就好了,不需要加括号了,除非有特殊需求,例如 TINYINT(1)代表 boolean 类型。

TINYINT(1),TINYINT(4)都是存储一个字节,并不会因为括号里的数字改变。例如 TINYINT(4)存储 22 则会显示 0022,因为最大宽度为 4,达不到的情况下用 0 来补充。

【参考】合适的字符存储长度,不但节约数据库表空间节约索引存储,更重要的是提升检索速度。

【参考】非负的数字类型字段,都添加上 unsigned

如可以使用 INTUNSINGED 字段存 IPV4

【参考】时间字段存储首先推荐 7,如果确实要使用其他时间日期类型,不要使用字符串类型存储,日期使用 DATE 类型,年使用 YEAR 类型,日期时间使用 DATETIME.

【参考】字符串 VARCHAR(N), 其中 N 表示字符个数,请尽量减少 N 的大小,参考:code VARCHAR(32);name VARCHAR(32);memo VARCHAR(512);

【参考】Blob 和 Text 类型所存储的数据量大,删除和修改操作容易在数据表里产生大量的碎片,如果可以,应该尽量避免使用 Blob 或 Text 类型

SQL 语句规范

【强制】不要使用 count(列名)或 count(常量)来替代 count(),count()是 SQL92 定义的 标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。

count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。

【强制】count(distinct col) 计算该列除 NULL 之外的不重复行数,

count(distinct col1, col2) 如果其中一列全为 NULL,那么即使另一列有不同的值,也返回为 0。

【强制】当某一列 col 的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为 NULL,因此使用 sum()时需注意 NPE 问题。

正例:可以使用如下方式避免 sum 的 NPE 问题:SELECT IF(ISNULL(SUM(g)), 0, SUM(g)) FROM table;

【强制】使用 ISNULL()来判断是否为 NULL 值。 说明:NULL 与任何值的直接比较都为 NULL。

NULLNULL 的返回结果是 NULL,而不是 false。

NULL=NULL 的返回结果是 NULL,而不是 true。

NULL1 的返回结果是 NULL,而不是 true。

【强制】 在代码中写分页查询逻辑时,若 count 为 0 应直接返回,避免执行后面的分页语句。

【强制】不得使用外键与级联,一切外键概念必须在应用层解决。

说明:以学生和成绩的关系为例,学生表中的 studentid 是主键,那么成绩表中的 studentid 则为外键。如果更新学生表中的 studentid,同时触发成绩表中的 studentid 更新,即为 级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻 塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。

【强制】在 MySQL 中禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。

【强制】数据订正时,删除和修改记录时,要先 select,避免出现误删除,确认无误才能执行更新语句。

【推荐】逻辑运算如果能在业务层解决,尽量不在数据库做运算,尽可能简单使用 MySQL。

md5() 或 Order by Rand()或计算字段等操作不在数据库表上进行

【推荐】不建议将数据字典表进行连接查询

数据字典表可以查询到 cache 或者在内存中对相关列进行替换。

【推荐】用 UNION ALL 代替 UNION

UNION ALL 不需要对结果集再进行排序。

【推荐】in 操作能避免则避免,若实在避免不了,需要仔细评估 in 后边的集合元素数量,控制在 1000 个之内。

【推荐】减少与数据库交互次数,尽量采用批量 SQL 语句

使用下面的语句来减少和 db 的交互次数:

INSERT ... ON DUPLICATE KEY UPDATE

REPLACE INTO

INSERT IGNORE

INSERT INTO VALUES()

【推荐】SQL 为多个小 SQL,避免大事务

简单的 SQL 容易使用到 MySQL 的 QUERY CACHE;减少锁表时间特别是 MyISAM;可以使用多核 CPU。

【强制】禁止使用 HINT

HINT 给 DBA 维护带来很多的不便,MySQL 使用 CBO 优化器,会综合考虑如果执行 SQL。

【强制】禁止使用分区表

分区表对分区键有严格要求;分区表在表变大后,执行 DDL、SHARDING、单表恢复等都变得更加困难。因此禁止使用分区表,并建议业务端手动 SHARDING。

【参考】如果有全球化需要,所有的字符存储与表示,均以 utf8mb4 编码,注意字符统计函数 的区别。

SELECT LENGTH(“轻松工作”); 返回为 12

SELECT CHARACTER_LENGTH(“轻松工作”); 返回为 4

utf8mb4 字符集可以存储表情等字符。

【参考】TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但 TRUNCATE 无事务且不触发 trigger,有可能造成事故,故不建议在开发代码中使用此语句。

说明: TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同。

【推荐】不要写一个大而全的数据更新接口。

传入为 POJO 类,不管是不是自己的目标更新字 段,都进行 update table set c1=value1,c2=value2,c3=value3; 这是不对的。执行 SQL 时,不要更新无改动的字段,一是易出错;二是效率低;三是增加 binlog 存储。

【推荐】如果可以放到业务逻辑里面,避免使用 GROUP BY、DISTINCT 、ORDER BY 等语句的使用,避免联表查询和子查询,以 GROUP BY 为列:

order by 的实现有两种方式,主要就是按用没用到索引来区分,

根据索引字段排序,利用索引取出的数据已经是排好序的,直接返回给客户端;

没有用到索引,将取出的数据进行一次排序操作后返回给客户端。这时会大量耗费数据库服务器的计算性能。

【推荐】针对索引字段使用 >, >=, =, ? and b=? 那么即使 a 的区分度更高,也必须把 b 放在索引的最前列。

正例:如果 where a=? and b=? ,a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即可。

【参考】创建索引时避免有如下极端误解

  • 宁滥勿缺。认为一个查询就需要建一个索引。
  • 宁缺勿滥。认为索引会消耗空间、严重拖慢更新和新增速度。
  • 抵制惟一索引。认为业务的惟一性一律需要在应用层通过“先查后插”方式解决。

【参考】其他注意事项

  • 索引占磁盘空间,不要重复的索引,尽量短 。
  • 只给常用的查询条件加索引。
  • 过滤性高的列建索引,区分度不高的列不建索引。
  • 唯一的记录添加唯一索引。
  • 频繁更新的列不要建索引。
  • 不要对索引列运算。
  • 同样过滤效果下,保持索引长度最小。
  • 合理利用组合索引,注意索引字段先后顺序。
  • 多列组合索引,过滤性高的字段最前。
  • order by 字段建立索引,避免 filesort。
  • 组合索引,不同的排序顺序不能使用索引。
  • !=无法使用索引。

总结

以上是自己在工作中总结的关于 MySQL 的一些开发规范,主要从【数据库设计规范】、【数据类型规范】、【SQL 索引规范】、【SQL 语句规范】这几个方面来描述,如对你有帮助,可以给个赞。

另外,MySQL 的使用需要结合实际业务场景,通过优化和管理来提高其性能和稳定性,我们需要根据特定的业务场景和使用需求来选择合适的方案。

相关文章

Oracle如何使用授予和撤销权限的语法和示例
Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
社区版oceanbase安装
Oracle 导出CSV工具-sqluldr2
ETL数据集成丨快速将MySQL数据迁移至Doris数据库

发布评论