高性能Mysql——Mysql开发规范

文章目录

  • 一. 数据库基本设计规范
    • 1.1 所有表必须使用 Innodb 存储引擎
    • 1.2 数据库和表的字符集统一使用 UTF8
    • 1.3 所有表和字段都需要添加注释
    • 1.4 谨慎使用 MySQL 分区表
    • 1.5 尽量做到冷热数据分离,减小表的宽度
    • 1.6 禁止在表中建立预留字段
    • 1.7 禁止在数据库中存储图片,文件等大的二进制数据
    • 1.8 禁止在线上做数据库压力测试
    • 1.9 禁止从开发环境,测试环境直接连接生产环境数据库
  • 二. 数据库字段设计规范
    • 2.1 优先选择符合存储需要的最小数据类型
    • 2.2 避免使用TEXT、BLOB数据类型
    • 2.3 避免使用ENUM数据类型
    • 2.4 尽可能把所有列定义为NOT NULL
    • 2.5 不要用字符串存储日期类型的数据
    • 2.6 同财务相关的金额类数据必须使用 decimal 类型
  • 三. 索引设计规范
    • 3.1 主键
    • 3.2 外键和级联
    • 3.3 限制每张表上的索引数量,建议单张表索引不超过 5 个
    • 3.4 常见索引列建议
    • 3.5 如何选择索引顺序
    • 3.6 禁止给表中的每一列都建立单独的索引
    • 3.7 避免建立冗余索引和重复索引
  • 四. 数据库 SQL 开发规范
    • 4.1 建议使用预编译语句进行数据库操作
    • 4.2 避免数据类型的隐式转换
    • 4.3 合理利用存在索引,而不是盲目增加索引
    • 4.4 程序连接不同的数据库使用不同的账号,禁止跨库查询
    • 4.5 禁止使用`select *`进行查询
    • 4.6 禁止使用不含字段列表的INSERT语句
    • 4.7 避免使用子查询,可以把子查询优化为join操作
    • 4.8 避免使用JOIN关联太多的表
    • 4.9 减少同数据库的交互次数
    • 4.10 使用in代替or,很多时候用 exists 代替 in 是一个好的选择
    • 4.11 禁止使用orderer by rand() 进行随机排序
    • 4.12 Where从句中禁止对列进行函数转换和计算
    • 4.13 在明显不会有重复值的时候,使用UNION ALL而不是使用UNION
    • 4.14 拆分复杂的大sql为多个小sql
    • 4.15 用Where子句替换HAVING 子句
    • 4.16 应尽量避免在 where 子句中使用!=或<>操作符
    • 4.17 应尽量避免在 where 子句中对字段进行 null 值判断
  • 五. 数据库操作行为规范
    • 5.1 超100万行的写操作,最好分批多次进行操作
    • 5.2 对于大表使用pt-online-schema-change修改表结构
    • 5.3 禁止为程序使用的账号赋予super权限
    • 5.4 对于程序连接数据库账号,遵循权限最小原则

一. 数据库基本设计规范

1.1 所有表必须使用 Innodb 存储引擎

没有特殊要求(即 Innodb 无法满足的功能如:列存储,存储空间数据等)的情况下,所有表必须使用 Innodb 存储引擎(MySQL5.5 之前默认使用 Myisam,5.6 以后默认的为 Innodb)。

Innodb 支持事务,支持行级锁,更好的恢复性,高并发下性能更好。

1.2 数据库和表的字符集统一使用 UTF8

兼容性更好,统一字符集可以避免由于字符集转换产生的乱码,不同的字符集进行比较前需要进行转换会造成索引失效,如果数据库中有存储 emoji 表情的需要,字符集需要采用 utf8mb4 字符集。

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

使用 comment 从句添加表和列的备注,从一开始就进行数据字典的维护

  1. 尽量控制单表数据量的大小,建议控制在 500 万以内。
    500 万并不是 MySQL 数据库的限制,过大会造成修改表结构,备份,恢复都会有很大的问题。

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

1.4 谨慎使用 MySQL 分区表

分区表在物理上表现为多个文件,在逻辑上表现为一个表;

谨慎选择分区键,跨分区查询效率可能更低;

建议采用物理分表的方式管理大数据。

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

MySQL 限制每个表最多存储 4096 列,并且每一行数据的大小不能超过 65535 字节。

减少磁盘 IO,保证热数据的内存缓存命中率(表越宽,把表装载进内存缓冲池时所占用的内存也就越大,也会消耗更多的 IO);

更有效的利用缓存,避免读入无用的冷数据;

经常一起使用的列放到一个表中(避免更多的关联操作)。

1.6 禁止在表中建立预留字段

预留字段的命名很难做到见名识义。

预留字段无法确认存储的数据类型,所以无法选择合适的类型。

对预留字段类型的修改,会对表进行锁定。

1.7 禁止在数据库中存储图片,文件等大的二进制数据

通常文件很大,会短时间内造成数据量快速增长,数据库进行数据库读取时,通常会进行大量的随机 IO 操作,文件很大时,IO 操作很耗时。

通常存储于文件服务器,数据库只存储文件地址信息

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

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

二. 数据库字段设计规范

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

比如将字符串转化为数字类型存储(mysql提供了如下两个函数):

INET_ATON('255.255.255.255')=4294967295
INET_NTOA(4294967295)='255.255.255.255'

可以看出,如果使用字符串来存储的话至少需要15个字节。而转换为Int类型后,则只需要4个字节。

a.将字符串转换成数字类型存储,如:将 IP 地址转换成整形数据

MySQL 提供了两个方法来处理 ip 地址

inet_aton 把 ip 转为无符号整型 (4-8 位)
inet_ntoa 把整型的 ip 转为地址
插入数据前,先用 inet_aton 把 ip 地址转为整型,可以节省空间,显示数据时,使用 inet_ntoa 把整型的 ip 地址转为地址显示即可。

b.对于非负型的数据 (如自增 ID,整型 IP) 来说,要优先使用无符号整型来存储

另外需要注意的是:
Varchar(N)中的N代表的是字符数,而不是字节数。比如使用UTF8存储汉字Varchar(255)=765个字节,可以存储255个汉字但是需要765个字节。
过大的长度会消耗更多的内存,虽然Varchar类是可变长度的字符类,虽然存储的时候是按照实际长度存储的,但是读取到内存的时候,为了提高效率是按照字段定义的长度来分配内存的。

2.2 避免使用TEXT、BLOB数据类型

以最常见的TEXT类型为例,可以存储64k的数据,而很少会存储到64k,所以对于这类数据来说,一般使用varchar类型就足够了。

另外由于mysql的内存表是不支持TEXT和BLOB这种大的数据类型的。如果我们查询中包括这样的数据类型的话,进行排序操作的时候就不能使用内存表,而是使用磁盘表。而且对于这类数据mysql在读取的时候会进行二次查询,所以会对sql性能变得很差。

如果一定使用这两种类型,不要使用select *。

2.3 避免使用ENUM数据类型

对枚举类型进行修改的时候,需要使用ALTER语句,而且会使用元数据锁,存在一定的操作风险

而且枚举类型的ORDER BY操作效率低,需要转化为字符串类型,然后才进行排序,这样是无法使用索引的。

最后,要禁止使用数值作为ENUM的枚举值,因为枚举在数据库内部存储实质就是整数,容易造成逻辑上的混淆。如果枚举值是整型的话,直接使用tyny int之类的数据类型就好了。

2.4 尽可能把所有列定义为NOT NULL

对于可能为空的列,在这样的列上增加索引NULL列需要额外的空间来保存(来存储列是否为空或者非空),所以要占用更多的空间。前面说过,对于索引占用空间越少越好。

另外,进行计算和比较的时候,NULL值会做特别的处理,这也可能使得索引失效。
比如:应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null

2.5 不要用字符串存储日期类型的数据

使用 TIMESTAMP(4 个字节) 或 DATETIME 类型 (8 个字节) 存储时间。

使用字符串类型:

  • 其一是无法用日期函数进行计算和比较。
  • 其二是用字符串存储日期要占用更多的空间。

2.6 同财务相关的金额类数据必须使用 decimal 类型

非精准的浮点包括float和double类型。
精准的浮点包括decimal类型。

一般财务相关的金额类数据,必须使用decimal类型,decimal类型占用空间由其定义的宽度决定,每4个字节可以存储9位数字,并且小数点也会占用一个字节。

另外decimal类型可以用于存储比bigint更大的整数数据,比使用varchar类型来存储更加的高效。

三. 索引设计规范

3.1 主键

使用Innodb表的时候,它是按照主键来指导索引的。一般来说,每个Innodb表必须要有一个主键,如果没有明确指定主键的话,Innodb表会优先选择表中的第一个非空唯一索引来作为主键,如果连非空唯一索引都没有,Innodb会自动生成一个6个字节的主键,但是其性能并不好

对于Innodb表,有如下建议:

  • 不使用更新频繁的列作为主键不使用多列主键。频繁改变主键,会有大量的IO,和CPU的使用,会降低服务器的整体性能(比如索引的调整)。
  • 不使用UUID,MD5,HASH,字符串作为主键。这类数据不是自然增长的,后面插入的数据不一定笔前面的数据大,所以会有大量的插入操作,也会降低服务器的整体性能。

3.2 外键和级联

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

说明:以学生和成绩的关系为例,学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。

外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风 险;外键影响数据库的插入速度。

  1. 增加了复杂性: a.每次做DELETE 或者UPDATE都必须考虑外键约束,会导致开发的时候很痛苦,测试数据极为不方便;b.外键的主从关系是定的,假如那天需求有变化,数据库中的这个字段根本不需要和其他表有关联的话就会增加很多麻烦。
  2. 增加了额外工作: 数据库需要增加维护外键的工作,比如当我们做一些涉及外键字段的增,删,更新操作之后,需要触发相关操作去检查,保证数据的的一致性和正确性,这样会不得不消耗资源;(个人觉得这个不是不用外键的原因,因为即使你不使用外键,你在应用层面也还是要保证的。所以,我觉得这个影响可以忽略不计。)
  3. 外键还会因为需要请求对其他表内部加锁而容易出现死锁情况;
  4. 对分库分表不友好 :因为分库分表下外键是无法生效的。

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

索引并不是越多越好!索引可以提高效率同样可以降低效率。

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

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

3.4 常见索引列建议

  • SELECT、UPDATE、DELETE语句的WHERE从句中的列。
  • 包含在ORDER BY、GROUP BY、DISTINCT中的字段。
  • 多表JOIN的关联列。

另外,应当避免建立冗余索引和重复索引。对于频繁的查询优先考虑使用覆盖索引。

3.5 如何选择索引顺序

之所以要设计索引,也就是为了通过索引来进行查找,减少磁盘的随机IO,增加查询的性能。

所以应该把区分度最高的列放在联合索引的最左侧。
其次,应该把字段长度小的列放在联合索引的左侧。
最后,使用最频繁的列放在联合索引的左侧。

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

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

3.7 避免建立冗余索引和重复索引

(增加了查询优化器生成执行计划的时间)
重复索引示例:primary key(id)、index(id)、unique index(id)
冗余索引示例:index(a,b,c)、index(a,b)、index(a)

四. 数据库 SQL 开发规范

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

高性能Mysql——Mysql开发规范_第1张图片
预编译语句只传递参数,比传递SQL语句更加高效
相同语句可以一次解析,多次使用,能够提高处理效率。

4.2 避免数据类型的隐式转换

隐式转换主要出现在where从句中,当参数类型和列类型不一致的时候,就有可能产生隐式转换。比如下面这条SQL语句。隐式转换会导致索引失效。
在这里插入图片描述

4.3 合理利用存在索引,而不是盲目增加索引

比如,避免使用双%号或者前置%的查询条件,如a like '%123%',或者a like '123%'(后置是可以用到索引的)。

一个 SQL 只能利用到复合索引中的一列进行范围查询。如:有 a,b,c 列的联合索引,在查询条件中有 a 列的范围查询,则在 b,c 列上的索引将不会被用到。

再比如,使用left join 或者not exists来优化not in 操作。因为not in也会使得索引失效。

4.4 程序连接不同的数据库使用不同的账号,禁止跨库查询

为数据库迁移和分库分表留出余地
降低业务耦合度
避免权限过大而产生的安全风险

4.5 禁止使用select *进行查询

原因:

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

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

主要的目的也是为了减少表结构变更带来的影响。

4.7 避免使用子查询,可以把子查询优化为join操作

  1. 子查询的结果是无法使用到索引的。
  2. 子查询会产生临时表操作,如果子查询的数据量过大则会严重影响效率。这些临时表是没有索引的,从而也会消耗过多的CPU和IO资源,从而产生慢查询。另外临时表的创建和销毁也需要时间。

对于分库分表后,join也不友好了。对此,可以使用两次查询,第二次使用in的方式拿到数据。

4.8 避免使用JOIN关联太多的表

每Join一个表会占用一部分内存(join_buffer_size)。
会产生临时表操作,影响查询效率。
Mysql最多允许61个表关联,但是建议不超过5个。

4.9 减少同数据库的交互次数

比如进行分页操作的时候,如果可以一次提取100行的话,就不要分100次做。

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

4.10 使用in代替or,很多时候用 exists 代替 in 是一个好的选择

in操作可以有效的利用索引,但是in的值最好不要超过500个。

4.11 禁止使用orderer by rand() 进行随机排序

这种方式会对mysql的性能产生很大的影响,因为它会把表中所有符合条件的数据都装载到内存中进行排序,这样会消耗大量的CPU和IO及内存资源。

推荐使用在程序中获取一个随机值,然后在数据库中获取数据的方式。

4.12 Where从句中禁止对列进行函数转换和计算

对列进行函数转换和计算会导致无法使用索引。
比如下面的语句,第一条进行了函数转换无法使用到索引,所以推荐使用第二条的方式。
高性能Mysql——Mysql开发规范_第2张图片

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

因为UNION操作会把所有数据放到临时表中后再进行去重操作。如果结果集太大,会消耗大量的资源。

4.14 拆分复杂的大sql为多个小sql

mysql中,一个sql只能使用一个cpu进行计算,并不会进行并行处理。

如果大sql拆分为多个小sql,可以进行sql的并行处理。

4.15 用Where子句替换HAVING 子句

因为HAVING 只会在检索出所有记录之后才对结果集进行过滤

4.16 应尽量避免在 where 子句中使用!=或<>操作符

否则将引擎放弃使用索引而进行全表扫描。

4.17 应尽量避免在 where 子句中对字段进行 null 值判断

否则将导致引擎放弃使用索引而进行全表扫描

五. 数据库操作行为规范

5.1 超100万行的写操作,最好分批多次进行操作

注意这里是写操作并不是查询操作,虽然数据库仍然是适合批处理的,但是大批量的写操作会造成如下的问题:
造成严重的主从延迟。在主库写完后,才会与从库进行数据同步,所以如果写操作耗时过长,会造成主从延迟过长。
binlog日志为row格式时会产生大量的日志。binlog会记录每一行数据的修改,所以我们修改的数据越多,产生的日志数据量也就会越多,日志传输和恢复的时间也就越长,这就是造成主从延迟的主要原因。
会造成严重的锁表操作,生产环境中可能导致其他应用无法连接到数据库。

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

同样修改表结构也会产生锁表操作,生产环境中可能导致其他应用无法连接到数据库。
使用pt-online-schema-change这个工具,能够避免修改大表产生的主从延迟,避免对表字段进行修改时进行锁表。

pt-online-schema-change实际上是复制了一个原来的表,然后将原本的表删除掉,复制的表改为原来的表名。它能够分多个批次进行。

5.3 禁止为程序使用的账号赋予super权限

super权限主要是进行数据库维护时使用的。
当达到最大连接数限制时,还允许1个有super权限的用户进行连接。所以,super权限只能留给DBA处理问题的账号使用。

5.4 对于程序连接数据库账号,遵循权限最小原则

比如,程序使用账号不允许跨库,原则上不准有drop权限。

你可能感兴趣的:(数据库)