mysql使用规范【转载】

转自:https://blog.csdn.net/youbl/article/details/130861862

根据一些前人经验,以及我个人的历史经历,对MySQL的使用整理了一些规范和建议,在公司内推荐并推行。
运维同学使用开源的yearning-SQL审核工具,参照该规范进行配置并检查开发人员提交的SQL,进行限制。
yearning官网:http://next.yearning.io/

完整规范如下:

说明
MySQL目前依然是多数项目的数据存储中间件,为了提高MySQL数据库的性能,降低后期维护成本,特制定本规范。
本规范面向所有对性能有要求的OLTP事务型项目,大部分规范也适用于:OLAP分析型项目/管理后台类项目。
请大家按规范执行,有特殊情形,请经审批后,由运维协助处理。

一、基础规范
1、强制:生产环境与其它环境必须网络隔离
除了生产服务器和跳板机,其它设备均必须网络隔离
无数惨痛案例:

发布测试环境,误用生产连接,导致数据丢失、破坏。
办公环境连接生产数据,误操作数据。
2、强制:生产程序账号只赋予增删改查权限
只赋予:SELECT/UPDATE/INSERT/DELETE 权限
如果项目内全部使用【软删除】设计,技术负责人可以要求关闭delete权限

3、强制:必须使用InnoDB存储引擎
支持事务、行级锁、并发性能更好、CPU及内存缓存页优化使得资源利用率更高。
例如:

CREATE TABLE order_info (id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT ‘自增主键’,字段列表
) ENGINE=INNODB DEFAULT CHARSET= COMMENT=‘订单统计表’
4、强制:必须使用UTF8MB4字符集
1)万国码,无需转码,无乱码风险
2)UTF8字符集存储汉字占用3个字节(UTF8MB4存储低位汉字同样3个字节,高位汉字才4个字节),存储英文字符占用一个字节
3)连接客户端也要utf8,建立连接时指定charset或SET NAMES UTF8MB4
如有emoji等表情符号需求,只能使用UTF8MB4字符集。
例如:

CREATE TABLE order_info (id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT ‘自增主键’,字段列表
) ENGINE=INNODB DEFAULT CHARSET= COMMENT=‘订单统计表’
注1:Java连接串必须指定 characterEncoding=utf8,并指定初始化SQL:参考
注2:MySQL的UTF8与标准的UTF8并不兼容,是创始人为了节省空间而自定义的一个编码格式

5、强制:必须使用统一时区
国内项目,同一项目用到的所有服务器,统一使用中国时区,即东八区(UTC+8),
海外项目,统一使用UTC+0时区,展示时由前端进行时区转换,
包括K8S、数据库、Redis、MQServer、ES、Nginx等服务器。
避免时间差异导致bug。
注:对外API文档,须明显标识时区信息。
反例:Web服务器为UTC+8,数据库为UTC+0,经常出现检索不到数据的bug

6、强制:数据表、表字段必须加入注释
1)N年后谁知道这个r1,r2,r3字段是干嘛的
2)类似status 这种字段,需在注释里指明主要值的含义,如"0-离线(默认值),1-在线"
例如:

CREATE TABLE tb (id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT ‘自增主键’,domain VARCHAR(50) NOT NULL COMMENT ‘域名’,pay_status INT NOT NULL DEFAULT 0 COMMENT ‘支付状态,0-未支付,1-已支付’,create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT ‘记录新增时间’,update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘最后更新时间’,PRIMARY KEY (id)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT=‘域名申请表’
7、强制:库名、表名、字段名禁止超过32个字符,且需见名知意
为了统一和规范、易于辨识及减少数据传输量,禁止超过32个字符。
严格禁止拼音简写作为字段名,如支付状态使用: zfzt
注:不推荐预留字段,如txt1、txt2,因为改字段名或类型会锁表,可以在未来需要时,再使用扩展表处理

8、强制:禁止使用存储过程、视图、使用触发器、事件
1)存储过程:不记录日志,不便于调试,升级/维护麻烦,且在水平扩展、分库分表等优化时处理困难
2)视图:通常用于降低代码里SQL的复杂度,但为了视图的通用性会损失性能,如join太多表
3)触发器:经常造成性能和死锁问题
注:临时性工作,可以使用,需由运维人员协助处理,并在事后移除

9、强制:禁止存储大文件或者大照片
大文件和照片存储在文件系统,数据库里存URL
1)推荐:存储在aws s3、阿里云oss,并使用项目域名指向该文件
2)存本地磁盘,通过后端程序读取并转发前端,再匹配cdn应用
注:MySQL设计用于存储小型的结构化数据,读写大数据会有对单表操作甚至整个实例都会产生性能问题

10、强制:禁止缩短字段长度
在修改表结构时,如果有缩短字段长度,且存在大于新的长度的表记录时,会导致修改失败并回滚,且回滚的时间比较长,整个过程是锁表操作,从而影响正常业务。
禁止操作的例子:varchar(100) => varchar(50) 、 bigint => int
注:特殊情况下,需要如此操作时,请新建字段=>更新新字段=>删除旧字段;耗时较长

11、强制:表名、字段名、字段值以下划线风格
不使用驼峰命名,统一使用有意义的小写单词,用下划线分隔

12、建议:表名、字段名、字段值忽略大小写
方便输入和检索,如根据用户姓名检索时,大小写敏感会导致检索困难
对【密码】之类大小写敏感数据,建议根据账号查询到密码数据,读取到代码里,再进行区分大小写的比对
注:密码应当使用不可逆算法加密后存储,即使在数据库里比对,一般也不存在大小写敏感问题
13、建议:库名、表名、字段名不使用保留字
避免使用 key status name 等MySql保留字作为表名或字段名,避免bug概率。
sql中、mybatis代码中,需要使用` 转义符才能正常工作

二、表设计规范
1、强制:所有表都必须要显式指定主键
1)主键强制要求采用自增字段 或 自定义的递增字段(如雪花算法),确保新数据不在中间插入
注:考虑枚举攻击或未来分库分表需求,建议雪花算法
2)小表(万行以下) 或 增删改极少的表,可以考虑其它主键,如uuid

2、强制:禁止使用外键,如果有外键完整性约束,由应用控制
外键耦合高,性能低下,甚至会造成死锁,不适应高并发场景。
如在代码里实现该逻辑:删除部门数据前,判断是否有员工数据存在

3、强制:单实例的表数量在500张表以内
单个数据库实例内表的数量太多时,考虑存在设计问题,请找架构人员协助评审。

4、强制:单表字段数量在30个字段以内
1)单表字段数上限30以内,超出时请新建扩展表,根据冷热数据、是否常用字段或大字段进行分离
2)表字段控制少而精,可以提高IO效率,缓存容量,且alter table 也更快。

5、强制:敏感数据必须加密存储
数据库中禁止存储明文密码,必须使用不可逆加密算法加密后存储,且必须加盐处理,如:
禁止存储: 明文 或 MD5(明文)
允许存储: MD5( MD5(明文+固定盐值1) + 固定盐值2)
建议存储: MD5( MD5(明文+用户特定盐值1) + 用户特定盐值2)
注:彩虹表盛行,双层加密,可大大提升彩虹表破解成本
用户邮箱、手机号等用户隐私数据,建议使用可逆加密后存储,防止脱库导致泄露
注:加密后,将导致无法模糊查询,需结合业务考虑
6、建议:单表数据行数 控制在5000w行以内
单表含索引的空间占用,建议20GB以内
数据量太大,请考虑分库分表,或者冷热数据分离
注:本条为建议项,需参考表的字段设计、单行大小、索引设计、读写情况等综合考虑。
实际业务场景中,几十万行,也有慢查询故障案例

三、字段设计规范
1、强制:字段要求NOT NULL,并建议设置默认值
默认值建议:

数值类型使用0,字符串类型使用空串 “”,日期类型使用 “1970-01-01 00:00:00”
代码里未提供数据时,应根据业务需要,判断是否要填充默认值,如:
● 有效时间默认无效,则默认值应该是 “1970-01-01 00:00:00”
● 有效时间默认永久有效,则默认值应该是 “9999-12-31 23:59:59”
原因:
1)null列 使索引/索引统计/值比较 都更加复杂,对MySQL来说更难优化
2)null 类型MySQL内部需要特殊处理,增加处理复杂性,并导致数据库性能降低
3)null列需要单独存储空间,表和索引中的null列都需要额外的空间来标识(参考官网)
4)null只能采用is null或is not null,而不能采用=、in、<、<>、!=、not in这些操作符号
5)null值在唯一索引中,是允许重复的
6)null值在程序中需要特殊处理,在业务场景中经常造成各种异常或业务逻辑bug
2、强制:禁止使用ENUM枚举类型
1)枚举的变更,需要alter table。
2)排序效率低。
请改用tinyint类型,也可以使用VARCHAR类型存储字符串

3、强制:审计字段要求
数据表设计应当包含如下2个字段,便于排序、数据筛选、增量业务逻辑、数据中台同步等。
1)记录创建时间,此字段禁止程序插入或更新:
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’,
2)记录变更时间,此字段禁止程序插入或更新:
update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘最后修改时间’,
注:如果确认,该表只有新增,不可能更新,可以不要字段 updateTime
其它审计字段,通常在操作日志里出现,但如业务需要,可以添加:
1)创建IP
2)创建人
3)最后修改IP
4)最后修改人

4、建议:避免使用TEXT、LONGTEXT类型,禁用BLOB类型
1)会浪费更多的磁盘和内存空间,非必要的大量大字段查询会淘汰热数据,导致内存命中率急剧降低,影响性能。
2)确实需要大字段,请考虑放在扩展表内

5、建议:避免使用小数定义 float double number
1)小数精度问题容易出现金额差异;
2)整数运算比小数运算快N倍;
3)金额可以用分为单位存储,百分比、折扣等字段也使用整数,业务代码中再除以100使用
注:参考业务需求,如精确到厘,则金额*1000后入库

6、建议:类型优先级:整型>date,time>char>varchar>blob,text
列的特点分析
1)整型:定长,没有国家/地区之分,没有字符集的差异,order by排序比字符类型快。
2)date,time:定长,运算快,节省空间,考虑时区。
3)char:定长, 考虑字符集和(排序)校对集
4)varchar:不定长,要考虑字符集的转换与排序时的校对集,速度慢。
5)text/Blob:无法使用内存临时表,不建议使用,特殊时请考虑放扩展表中。
注:mysql8,可以考虑定义为 json 类型

7、建议:数据类型长度够用就行,不要慷慨
数据类型长度过大的字段浪费内存,影响速度。
能用varchar(20) 就不要用 varchar(100),太大的定义也会额外占用磁盘空间。

四、索引设计规范
1、强制:上线前检查sql索引使用情况
任何新的select,update,delete上线前都应该先经过explain review,查看索引使用情况,避免上线无索引导致生产故障。
表小或表使用不多都不是借口。
注:可以在开发、测试环境中,设置 long_query_time = 0,然后定期检查这些环境的slow query log,提前优化
上线流程应包括:上线后30分钟内的慢查询观察。

2、强制:禁止在更新十分频繁、区分度不高的属性上建立索引
1)更新会变更B+树,更新频繁的字段建立索引会大大降低数据库性能。
2)"性别"这种区分度不大的属性,建立索引没有意义,性能与全表扫描类似。
注:扫描的索引行数超过10%~30%时,会放弃使用索引,改用全表扫描

3、强制:禁止创建重复索引
禁止举例:

创建了联合索引A,含字段:name, state,
再创建索引B,含字段name
例外项:

联合索引A有比较多使用场景,
但是存在另一个场景是 where name=xxx order by id,该查询使用后面这个索引B更高效,使用索引A效率较差
4、建议:索引个数限制
1)单表索引建议控制在5个以内,索引过多会导致insert/update效率下降。
2)单索引包含的字段不允许超过5个。

5、建议:合理建立组合索引
1)不推荐超过4个字段的组合索引
2)区分度高的字段放前面,能够更加有效的过滤数据
如:联合索引:user_id、state
3)必要时,可以创建覆盖索引,即索引包含sql用到的所有字段

6、建议:添加 update_time 列的索引
该字段跟随记录更新而更新,能够有效的排查增量记录;
方便运维进行过期数据清理
3)表的数据较小,或未来不存在数据清理的需要时,可以不创建
7、建议:等值查询创建Hash索引
对于只会使用等值查询,且返回结果集数量较小的场景,建议创建Hash索引。
查询效率高且占用内存小
注:如果需要范围查询、或需要部分查询、或返回结果数较多时,不能使用。

8、建议:大字段创建前缀索引
如果字段的前n位已经拥有足够的区分度,建议创建前缀索引,而不是整个字段创建索引,以节约空间。
必须时,可以创建计算列,用于索引

五、SQL使用规范
1、强制:禁止不带字段插入,必须显式指定插入的列属性
不允许使用类似语句: INSERT INTO xxx VALUES(11,22),而使用 INSERT INTO xxx(num1,num2) VALUES(11,22)
1)容易在增加或者删除字段后,字段与值出现错位引发出现程序BUG、垃圾数据。

2、强制:禁止在WHERE条件的属性上使用函数或者表达式
条件检索里,在字段上使用函数或表达式,会导致无法应用索引。

如:SELECT uid FROM t_user WHERE from_unixtime(day)>=‘2017-05-12’ 会导致全表扫描
正确写法:SELECT uid FROM t_user WHERE day>= unix_timestamp(‘2017-05-12 00:00:00’)
如 SELECT uid FROM t_user WHERE age +10>40 会导致全表扫描
3、强制:禁止在字段上使用类型隐式转换
查询时,对字段的值进行类型隐式转换,会导致全表扫描,不能命中索引
如:手机号phone字段为varchar(20),查询时条件要带上单引号
SELECT uid FROM t_user WHERE phone=13812345678 会导致全表扫描
因为MySQL会把phone的值,隐式转换为数值后,再比较
注1:无法确认时,条件值允许统一加上单引号,因为MySQL会优先转换为数值,比如 where id=‘123’ 只会隐式转换’123’为123
注2:反例故障参考

4、强制:禁止加载全部数据内存中分页的行为
影响性能,数据量特别大时会内存溢出,导致程序崩溃。
反例:前深圳团队内存中过滤和排序,3个月后所有报表无法导出

5、强制:只读取一行数据,必须加上limit 1
如果不加limit 1,可能导致全表扫描或全索引一直检索到末尾

6、强制:禁止使用随机函数排序,如 order by rand()
会导致全表扫描
注:一定需要随机排序时,优化方法参考

7、强制:批量更新,降低一次性更新的数量
分多批次执行,如每次更新10000行,更新10次,而不是一次更新10万行
太大的更新,会导致更长时间的锁表,以及更大的事务操作,死锁概率很高,还会影响主从同步的性能。
包括插入、删除的大批量操作,都应当分多批次执行。

8、建议:避免使用SELECT *,只获取必要的字段
1)读取不需要的列会增加CPU、IO、NET消耗
2)不能有效的利用覆盖索引
3)使用SELECT *容易在增加或者删除字段后出现程序BUG

9、建议:避免负向查询,以及%开头的模糊查询
1)负向查询条件:NOT、!=、<>、!<、!>、NOT IN、NOT LIKE等,会导致全表扫描
备注:事实上,这个要具体问题具体分析,看数据的分布,并不是负向查询都会导致索引不可用
2)%开头的模糊查询,会导致全表扫描

10、建议:避免JOIN连接大表,避免对大表使用子查询
1)会产生临时表,消耗较多内存与CPU,极大影响数据库性能
2)子查询会导致全表扫描,参考故障案例

11、建议:使用参数化查询
1)防SQL注入问题;
2)开启查询缓存时,可以有效命中
注:不推荐开启查询缓存,且MySQL8.0开始,已经默认关闭了查询缓存。
现在基本都使用ORM框架,要避免人工手写/拼接SQL

12、建议:避免JOIN连接超过3个表
可能产生临时表,导致性能问题;
可能导致索引选择错误,影响查询性能;
注:每个表只会使用到一个索引;
太多的JOIN不建议在OLTP系统上使用,而是由应用发起多次查询,在应用里进行组合。

13、建议:sql语句尽可能简单;一条sql只能在一个cpu运算
一条大sql,可能导致整个数据库堵死,影响所有业务;
拆分为多条小语句执行,减小加锁时长,增加数据库吞吐量。
14、建议:使用limit高效分页
limit越大,效率越低,如避免limit直接访问第1000页,优化参考:
select id,name from t where xxx limit 10000, 10;
=>
select id,name from t where xxx and id > 10000 limit 10;

15、建议:使用union all替代union
union会对结果数据进行distinct去重处理,有一定开销,非必要不使用

六、行为规范
1、强制:未经公司相关领导或负责人审批,禁止直接导出生产数据库数据进行分发。
2、强制:禁止绕开运维/DBA直接修改生产数据库中的表结构。
3、强制:数据库个人账号专人专用,禁止共用或借用行为。
4、建议:需要查询大表或已知的慢查询时,请使用备库查询,或在业务低峰时处理,避免影响生产环境。
5、建议:大批量数据修改操作,应避开业务高峰期,提交审批给运维/DBA处理。
6、建议:及时处理已下线业务的SQL或业务表。
7、建议:重要项目的数据库方案选型和设计需运维/DBA参与评审。

七、建表案例
CREATE TABLE apm_orgtest (id int NOT NULL AUTO_INCREMENT COMMENT ‘主键’,org_name varchar(200) NOT NULL DEFAULT ‘’ COMMENT ‘组织名称’,is_valid int NOT NULL DEFAULT ‘1’ COMMENT ‘是否有效,0无效,1有效’,create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT ‘记录入库时间’,update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘最后更新时间’,PRIMARY KEY (id),KEY idx_name (org_name),KEY idx_update_time (update_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘apm应用数据收集表’;

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