暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MySQL 开发使用规范

原创 OnTheRoad 2022-08-30
384

1. 对象命名规范

  1. 不同类型数据库对象,以类型关键字缩写作为对象名前缀。如数据表 t_xxx_xxx、视图 v_xxx_xxx
  2. 数据库对象名称用 小写字母,并用下划线 _ 分割单词,以达到见名识意,且对象名长度不超 32 个字符;
  3. 临时库表以 tmp_ 为前缀,并以日期 _YYYYMMDD 为后缀;备份表以 bak_ 为前缀,并以日期 _YYYYMMDD 为后缀;
  4. 存储相同数据的列名和列类型需保持一致(尤其作为表连接关联列时,若关联列类型不一致会自动进行数据类型隐式转换,将会造成关联列上的索引失效,导致查询效率降低)。

2. 环境使用规范

2.1. 禁止在线上做数据库压力测试

2.2. 禁止从开发环境,测试环境直连生产环境数据库

开发环境、测试环境直连生产环境数据库,可能因误操作,而导致误删数据等无法挽回的错误。

2.3. 专库专用(连接不同的数据库使用不同的账号),禁止跨库查询

  • 为数据库迁移和分库、分表留出余地
  • 专库专用,条理清晰,降低业务耦合度
  • 避免权限过大而产生的安全风险

针对每个业务数据库,创建独立的数据库用户(通常与数据库同名),该数据库用户仅可操作与其同名的业务数据库。禁止在多个数据库之间跨库查询。

如下示例中,禁止授予 storemgr 用户访问 ordermgr 数据库。若业务需要,可通过多数据源等方式实现。

新创建的数据库用户,默认遵循最小授权原则,仅授予INSERT, DELETE, UPDATE, SELECT, CREATE权限。

业务名称 数据库名 数据库用户名 授权语句
订单管理子系统 ordermgr ordermgr GRANT INSERT, DELETE, UPDATE, SELECT, CREATE ON ordermgr.* TO ordermgr
仓储子系统 storemgr storemgr GRANT INSERT, DELETE, UPDATE, SELECT, CREATE ON ordermgr.* TO ordermgr

2.4. 用户权限遵循最小原则

默认新建的数据库用户,只分配 DML 权限,如 INSERT, DELETE, UPDATE, SELECT,不分配 DDL 权限。

在MySQL 中 truncate table 需要 DROP 权限。

3. 数据库设计规范

3.1. 所有表必须使用 InnoDB 存储引擎

在特殊要求(即 InnoDB 无法满足的功能如:列存储,存储空间数据等)的情况下,所有表必须使用 InnoDB 存储引擎(MySQL 5.5 之前默认使用 Myisam,5.6 以后默认的为 InnoDB)。InnoDB 支持事务,支持行级锁,更好的恢复性,高并发下性能更好

3.2. 统一字符集 UTF8 及排序规则

  1. 库、表字符集统一为 utf8mb4
    • utf8mb4: 使用 1-4 个字节表示一个字符的 UTF-8 编码。
    • utf8mb3: 使用 1-3 个字节表示一个字符的 UTF-8 编码。
    • utf8: 同 utf8mb3,是 utf8mb3 的别名

兼容性更好,统一字符集可以避免由于字符集转换产生的乱码,不同的字符集进行比较前,需要进行转换会造成索引失效。

  1. 统一排序规则

general_ci 更快,unicode_ci 更准确,ci 不区分大小写,cs 区分大小写

utf8mb4_general_ci 不区分大小写。排序速度略快,排序准确性略差。
utf8mb4_unicode_ci 不区分大小写。排序速度略慢,排序准确性略高。
utf8mb4_bin 区分大小写。将字符串每个字符用二进制数据编译存储,区分大小写,而且可以存二进制的内容。
utf8mb4_general_cs 区分大小写

3.3. 专业的事情交给专业的人做

避免在使用数据库函数、存储过程实现业务逻辑,以减少对数据库类型的依赖。便于日后迁移至其他数据库,如 PostgreSQL达梦 等。

4. 表设计规范

4.1. 所有表和字段都需要添加注释

使用 comment 子句添加 表和列 的备注

4.2. 控制单表数据量的大小

尽量控制单表数据量在 500W 以内,最高不超过 2000W。过多的数据量会造成修改表结构,备份,恢复都会有很大的问题;

可以用历史数据归档(应用于日志数据)、分库分表(应用于业务数据)、分区表等手段来控制单表数据量大小;

分区表中的每个分区相当于一个物理表;

4.3. 谨慎使用 MySQL 分区表

分区表在逻辑上表现为一个表,但是在物理上表现为多个文件; 谨慎选择分区字段(分区字段需要包含在 唯一键 中,即主键或唯一索引中),跨分区查询效率可能更低。推荐采用 物理分表 的方式管理大数据。

4.4. 尽量做到冷热数据分离,减小表的宽度

MySQL 限制每个表最多存储 4096 列,并且每一行数据的大小不能超过 64K,以减少磁盘IO,保证热数据的内存缓存命中率(表越宽,把表装载进内存缓冲池时所占用的内存也就越大,也会消耗更多的 IO)。
更有效的利用缓存,避免读入无用的冷数据。经常一起使用的列放到一个表中(避免更多的表连接操作)。

4.5. 禁止在表中建立预留字段

预留字段的命名很难做到见名识义,预留字段无法确认数据类型。

4.6. 禁止在数据库中存储二进制数据

图片、文件通常较大,会短时间内造成数据量快速增长。数据库在进行数据读取时,会进行大量的随机IO操作,文件很大时,IO操作很耗时。通常将图片或文件存储于文件服务器,在数据库中存储其文件地址信息。

4.7. 为每个表都要指定递增数值主键

  1. 推荐使用业务不相关的递增数值型作为表主键,可用 AUTO_INCREMENT 自增或 SHORT_UUID()(类似雪花算法的结构)。因为 InnoDB 通过聚簇索引来组织表数据的存储。主键递增,可避免数据插入的随机性,避免 page 分裂,减少表碎片提升空间和内存的使用效率。避免使用 UUID(随机字符串)作为主键。

  2. 由于表没有主键,副本库的回放效率极差,大概率会引起主从复制的延迟,继而影响业务的读取,容灾切换的时效性等各种问题。

5. 字段设计规范

5.1. 优先选择符合存储需要的最小的数据类型

  • 原因
    列的字段越大,建立索引时所需要的空间也就越大,这样一页中所能存储的索引节点的数量也就越少也越少,在遍历时所需要的IO次数也就越多, 索引的性能也就越差。
  • 方法
    • 将字符串转换成数字类型存储,如:将 IP 地址转换成整形数据。
      MySQL提供了两个方法来处理ip地址:
      inet_aton 把ip转为无符号整型(4-8位)
      inet_ntoa 把整型的ip转为地址
      插入数据前,先用 inet_aton 将 IP 地址转为整型,可以节省空间。显示数据时,使用 inet_ntoa 将整型的 IP 地址转为地址显示即可。
    • 对于非负型的数据(如自增 ID、整型 IP)来说,要优先使用无符号整型来存储。因为,无符号相对于有符号可以多出 1 倍的存储空间
      VARCHAR(N) 中的 N 代表的是字符数,而不是字节(Byte)数

使用 UTF8 存储 255 个汉字 VARCHAR(255) = 765个字节,过大的长度会消耗更多的内存

5.2. 避免使用 TEXT、BLOB 数据类型,最常见的TEXT类型可以存储64k的数据

  • 建议将 BLOB 或是 TEXT 列分离到单独的扩展表中
    MySQL 内存临时表不支持 TEXTBLOB 这样的大数据类型。如果查询中包含这样的数据,在排序等操作时,无法使用内存临时表,必须使用磁盘临时表进行,影响性能。
    而且对于这种数据,MySQL 还要进行二次回表查询,会使 SQL 性能变得很差。
    如果一定要使用 TEXTBLOB,建议将 TEXTBLOB 列分离到单独的扩展表中。查询时一定不要使用 SELECT *,而名确列出必要的列。不需要 TEXT 列的数据时,不要对该列进行查询。
  • TEXTBLOB 类型只能使用前缀索引
    因为 MySQL 对索引字段长度是有限制的,所以 TEXT 类型只能使用前缀索引,并且 TEXT列上不能有默认值。

5.3. 避免使用ENUM类型

  • 修改 ENUM 值需要使用 ALTER 语句
  • ENUM 类型的 ORDER BY 操作效率低,需要额外操作
  • 禁止使用数值作为 ENUM 的枚举值

5.4. 尽可能将列定义为 NOT NULL

原因:

  • 索引 NULL 列需要额外的空间来保存,所以要占用更多的空间;
  • 进行比较和计算时,需要对NULL值做特别的处理;
  • NULL 值的符合索引无效。

5.5. 使用DATETIME存储时间

类型 占用空间 表示范围 其他说明
DATE ‘1000-01-01’ to ‘9999-12-31’
DATETIME 8字节 ‘1000-01-01 00:00:00.000000’ to ‘9999-12-31 23:59:59.999999’
TIMESTAMP 4字节 ‘1970-01-01 00:00:01.000000’ to ‘2038-01-19 03:14:07.999999’ MySQL 将 TIMESTAMP 值从当前时区转换为 UTC 进行存储,并从 UTC 转换回当前时区进行检索。

注意
禁止用字符串存储 日期时间 类型:

  1. 无法用 日期时间函数 进行计算比较
  2. 字符串比 datetimetimestamp 占用更多的存储空间

5.6. 财务金额用 decimal 类型

  • 非精准浮点类型:floatdouble
  • 精准浮点类型:decimal
    Decimal 类型为精准浮点数,在计算时不会丢失精度。占用空间由定义的宽度决定,每 4 个字节可以存储 9 位数字,并且小数点要占用一个字节。可用于存储比 bigint 更大的整型数据。

6. 索引设计规范

6.1. 限制每张表上的索引数量,建议单张表索引不超过 5 个

索引可以增加查询效率,但同样也会降低插入和更新的效率,甚至有些情况下会降低查询效率。

因为优化器在选择如何优化查询时,会根据统计信息,对每一个可以用到的索引来进行评估,以生成出一个最好的执行计划。如果同时有很多个索引都可以用于查询,就会增加 MySQL 优化器生成执行计划的时间,同样会降低查询性能。

6.2. 禁止给表中的每一列都建立单独的索引

5.6 版本之前,一个 SQL 只能使用到一个表中的一个索引。5.6 以后,虽然有了合并索引的优化方式,但是还是远远没有使用一个联合索引的查询效率高。

6.3. 每个 Innodb 表必须有个主键

Innodb 是一种索引组织表:数据存储的逻辑顺序和索引顺序是相同的。
每个表都可以有多个索引,但是表的存储顺序只能有一种。Innodb 是按照主键索引的顺序来组织表的。
不要使用更新频繁的列作为主键;不要用多列主键(相当于联合索引); 不要使用 UUIDMD5HASH、字符串列作为主键(无法保证数据的顺序增长)。
主键建议使用自增 ID 值,如 AUTO_INCREMENTSHORT_UUID()

6.4. 常见索引列建议

  1. 出现在 SELECTUPDATEDELETE 语句的 WHERE 从句中的列
  2. 包含在 ORDER BYGROUP BYDISTINCT 中的字段
  3. 并不要将符合 12 中的字段的列都建立一个索引,通常将 12 中的字段建立联合索引效果更好
  4. 多表 join 的关联列

6.5. 如何选择索引列的顺序

建立索引的目的是:希望通过索引进行数据查找,减少随机 IO,增加查询性能。索引能过滤出越少的数据,则从磁盘中读入的数据也就越少。

  • 区分度最高的放在联合索引的最左侧(区分度=列中不同值的数量/列的总行数);
  • 尽量把字段长度小的列放在联合索引的最左侧(因为字段长度越小,一页能存储的数据量越大,IO 性能也就越好);
  • 使用最频繁的列放到联合索引的左侧(这样可以比较少的建立一些索引)。
  • 需要用范围扫描的列,放联合索引的最右侧。

6.6. 避免建立冗余索引和重复索引

因为这样会增加查询优化器生成执行计划的时间。

  • 重复索引示例:primary key(id)、index(id)、unique index(id)
  • 冗余索引示例:index(a,b,c)、index(a,b)、index(a)

6.7. 优先考虑覆盖索引

对于频繁的查询优先考虑使用覆盖索引。

覆盖索引: 就是包含了所有查询字段(WHERE,SELECT,ORDER BY,GROUP BY 包含的字段)的索引。

覆盖索引的好处:

  • 避免 Innodb 表进行索引的二次回表
    Innodb 是以聚集索引的顺序来存储的,对于 Innodb 来说,二级索引在叶子节点中所保存的是行的主键信息,
    如果是用二级索引查询数据的话,在查找到相应的键值后,还要通过主键进行二次回表才能获取我们真实所需要的数据。而在覆盖索引中,二级索引的键值中可以获取所有的数据,避免了对主键的二次回表查询 ,减少了 IO 操作,提升了查询效率。

  • 可将随机 IO 变成顺序 IO 加快查询效率
    由于覆盖索引是按键值的顺序存储的,对于 IO 密集型的范围查找来说,比随机从磁盘读取每一行的数据 IO 要少的多。因此,利用覆盖索引在访问时也可将磁盘的随机读取 IO 转变成索引查找的顺序 IO。

6.8. 尽量避免使用外键约束

  • 不建议使用外键约束(foreign key),但一定要在表与表之间的关联键上建立索引;
  • 外键可用于保证数据的参照完整性,但建议在业务端实现;
  • 外键会影响父表和子表的写操作(因为有锁)从而降低性能。

7. 数据库 SQL 开发规范

7.1. 建议使用预编译语句进行数据库操作

预编译语句可以重复使用这些计划,减少 SQL 编译所需要的时间,还可以解决动态 SQL 所带来的 SQL 注入的问题。只传参数,比传递 SQL 语句更高效。相同语句可以一次解析,多次使用,提高处理效率。

7.2. 避免数据类型的隐式转换

隐式转换会导致索引失效。如:

select name,phone from customer where id = '111';
复制

7.3. 充分利用表上已经存在的索引

  1. WHERE 条件避免使用左 %
    a like '%123%',(如果无前置 %,只有后置 %,可以用到列上的索引)
  2. 一个 SQL 只能利用到复合索引中的一列进行范围查询
    如:有 a,b,c 列的联合索引,在查询条件中有 a 列的范围查询,则在 b,c 列上的索引将不会被用到。在定义联合索引时,如果 a 列要用到范围查找的话,就要把 a 列放到联合索引的右侧。
  3. 使用 left joinnot exists 来优化 not in 操作
    因为 not in 也通常会使用索引失效。
  4. 范围查询时,尽量用 >= 替代 >;用 <= 替代 <

7.4. 禁止使用 SELECT *,必须使用 SELECT <字段列表> 查询

原因:

  • 消耗更多的 CPUIO 以网络带宽资源
  • 无法使用覆盖索引
  • 可减少表结构变更带来的影响

7.5. 禁止使用不含字段列表的 INSERT 语句

如:

INSERT INTO VALUES('a','b','c');
复制

应使用:

INSERT INTO T(c1,c2,c3) VALUES ('a','b','c');
复制

7.6. 避免使用子查询,可将子查询优化为 join 操作

通常子查询在 in 子句中,且子查询中为简单 SQL(不包含 union、group by、order by、limit 从句)时,才可将 子查询 转化为 表连接 进行优化。

子查询性能差的原因:
* 子查询的结果集无法使用索引,通常子查询的结果集会被存储到临时表中,不论是内存临时表还是磁盘临时表都不会存在索引,所以查询性能 会受到一定的影响;
* 特别是对于返回结果集比较大的子查询,其对查询性能的影响也就越大;
* 由于子查询会产生大量的临时表也没有索引,所以会消耗过多的CPU和IO资源,产生大量的慢查询。

7.7. 避免使用 JOIN 关联太多的表,建议不超 5

对于 MySQL 来说,是存在关联缓存的,缓存的大小可以由 join_buffer_size 参数进行设置。
在 MySQL 中,对于同一个 SQL 多关联(join)一个表,就会多分配一个关联缓存,如果在一个 SQL 中关联的表越多,所占用的内存也就越高。
如果程序中大量的使用了多表关联的操作,同时 join_buffer_size 设置的不高的情况下,就容易造成服务器内存溢出的情况,就会影响到服务器数据库性能的稳定性。
同时对于关联操作来说,会产生临时表操作,影响查询效率 MySQL 最多允许关联 61 个表,建议不超过 5 个。,尤其对于 OLTP 系统,建议不超过 3 个。

7.8. 减少与数据库的交互次数

数据库更适合处理批量操作,合并多个相同的操作到一起,可以提高处理效率

7.9. 使用 in 代替 or

  • 对同一列进行 or 判断时,可使用 in 代替 orin 可以更有效的利用索引,or 大多数情况下很少能利用到索引。但 in 的值不要超过 500 个。

  • 对不同字段进行 or 判断时,可将 or 改写为 union

7.10. LIMIT 高效分页

传统分页,offset 偏移量越大,速度越慢

SELECT * FROM table LIMIT <offset>,<rows>;
复制

可用如下推荐分页示例改写

SELECT * FROM table WHERE id>=23423 LIMIT 11; SELECT * FROM table WHERE id >= ( SELECT id FROM table LIMIT 10000,1 ) LIMIT 10; SELECT * FROM table INNER JOIN (SELECT id FROM table LIMIT 10000,10) USING (id) ;
复制

7.11. 禁止使用 order by rand() 进行随机排序

会把表中所有符合条件的数据装载到内存中,然后在内存中对所有数据根据随机生成的值进行排序,并且可能会对每一行都生成一个随机值。如果满足条件的数据集非常大,就会消耗大量的 CPU 和 IO 及内存资源。

7.12. WHERE 从句中禁止对列进行函数转换和计算

对列进行函数转换或计算时会导致无法使用索引。

* 不推荐: where date(create_time)='20190101' * 推荐: where create_time >= '20190101' and create_time < '20190102'
复制

7.13. 使用 UNION ALL 而不是 UNION

在明显不会有重复值时,使用 UNION ALL 而不是 UNION

  • UNION 会将两个结果集的数据放到临时表中,再进行去重操作。
  • UNION ALL 将两个结果集取并集,不会进行去重操作。

7.14. 拆分复杂的大 SQL 为多个小 SQL

大 SQL,指逻辑上比较复杂,需要占用大量 CPU 进行计算的 SQL。在 MySQL 中,一个 SQL 只能使用一个 CPU 进行计算。将大 SQL 拆分后,可以通过并行执行来提高处理效率。

8. 数据库操作规范

8.1. 超 100W 行的批量 DML 操作,要分批多次进行操作

  1. 大批量 DML 操作可能会造成严重的主从延迟
    主从环境中,大批量操作可能会造成严重的 主从延迟,大批量的 DML 操作一般都需要执行一定长的时间。而只有当主库上执行完成后,才会在其他从库上执行,所以会造成主库与从库长时间的延迟情况。

  2. binlog 日志为 ROW 格式时会产生大量的日志
    大批量 DML 操作会产生大量 binlog,特别是对于 ROW 格式的 binlog 而言,由于在 ROW 格式中会记录每一行数据的修改。我们一次修改的数据越多,产生的 binlog 日志量也就会越多。日志的传输和恢复所需要的时间也就越长,这也是造成主从延迟的一个原因。

  3. 避免产生大事务操作
    一个大事务中的大批量修改数据,会造成表中大批量数据进行锁定。从而导致大量的阻塞,阻塞会对 MySQL 的性能产生非常大的影响。
    特别是长时间的阻塞会占满所有数据库的可用连接,会使生产环境中的其他应用无法连接到数据库。因此,要注意大批量写操作要进行拆分。

8.2. 对于大表使用 pt-online-schema-change 修改表结构

  1. 避免大表修改产生的主从延迟
  2. 避免在对表字段进行修改时进行锁表
    对大表的结构的修改一定要谨慎,会造成严重的锁表操作,尤其是生产环境,是不能容忍的。
    推荐使用 percona 公司 percona-toolkit工具包的 pt-online-schema-change 进行表结构修改。修改原理:
    • 原表需有主键或唯一索引。
    • pt-online-schema-change 它会首先建立一个与原表结构相同的新表,并且在新表上进行表结构的修改
    • 然后再把原表中的数据复制到新表中,并在原表中增加一些触发器(DELETE/UPDATE/INSERT),通过原表的触发器,将原表中新增的数据也复制到新表中。
    • 在行所有数据复制完成之后,将新表命名成原表,并删除原表。将原来一个 DDL 操作,分解成多个小的操作,分批次进行。
最后修改时间:2022-09-01 08:38:28
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论

TA的专栏
MySQL Shell 8.0 中文版
收录3篇内容
MySQL 八股文
收录3篇内容
目录
  • 1. 对象命名规范
  • 2. 环境使用规范
    • 2.1. 禁止在线上做数据库压力测试
    • 2.2. 禁止从开发环境,测试环境直连生产环境数据库
    • 2.3. 专库专用(连接不同的数据库使用不同的账号),禁止跨库查询
    • 2.4. 用户权限遵循最小原则
  • 3. 数据库设计规范
    • 3.1. 所有表必须使用 InnoDB 存储引擎
    • 3.2. 统一字符集 UTF8 及排序规则
    • 3.3. 专业的事情交给专业的人做
  • 4. 表设计规范
    • 4.1. 所有表和字段都需要添加注释
    • 4.2. 控制单表数据量的大小
    • 4.3. 谨慎使用 MySQL 分区表
    • 4.4. 尽量做到冷热数据分离,减小表的宽度
    • 4.5. 禁止在表中建立预留字段
    • 4.6. 禁止在数据库中存储二进制数据
    • 4.7. 为每个表都要指定递增数值主键
  • 5. 字段设计规范
    • 5.1. 优先选择符合存储需要的最小的数据类型
    • 5.2. 避免使用 TEXT、BLOB 数据类型,最常见的TEXT类型可以存储64k的数据
    • 5.3. 避免使用ENUM类型
    • 5.4. 尽可能将列定义为 NOT NULL
    • 5.5. 使用DATETIME存储时间
    • 5.6. 财务金额用 decimal 类型
  • 6. 索引设计规范
    • 6.1. 限制每张表上的索引数量,建议单张表索引不超过 5 个
    • 6.2. 禁止给表中的每一列都建立单独的索引
    • 6.3. 每个 Innodb 表必须有个主键
    • 6.4. 常见索引列建议
    • 6.5. 如何选择索引列的顺序
    • 6.6. 避免建立冗余索引和重复索引
    • 6.7. 优先考虑覆盖索引
    • 6.8. 尽量避免使用外键约束
  • 7. 数据库 SQL 开发规范
    • 7.1. 建议使用预编译语句进行数据库操作
    • 7.2. 避免数据类型的隐式转换
    • 7.3. 充分利用表上已经存在的索引
    • 7.4. 禁止使用 SELECT *,必须使用 SELECT <字段列表> 查询
    • 7.5. 禁止使用不含字段列表的 INSERT 语句
    • 7.6. 避免使用子查询,可将子查询优化为 join 操作
    • 7.7. 避免使用 JOIN 关联太多的表,建议不超 5 个
    • 7.8. 减少与数据库的交互次数
    • 7.9. 使用 in 代替 or
    • 7.10. LIMIT 高效分页
    • 7.11. 禁止使用 order by rand() 进行随机排序
    • 7.12. WHERE 从句中禁止对列进行函数转换和计算
    • 7.13. 使用 UNION ALL 而不是 UNION
    • 7.14. 拆分复杂的大 SQL 为多个小 SQL
  • 8. 数据库操作规范
    • 8.1. 超 100W 行的批量 DML 操作,要分批多次进行操作
    • 8.2. 对于大表使用 pt-online-schema-change 修改表结构