原文:58同城沈剑的MySQL军规
一、基础规范
第一条:表存储引擎必须使用InnoDB
解读:支持事务、行级锁
第二条:使用utf8(utf8mb4)字符集
解读:
(1)通用,无乱码风险,汉字3字节,英文1字节
(2)utf8mb4是utf8的超集,存储4字节(支持表情符号)
推荐
第三条:表、字段必须加入中文注释
解读:对于表和字段创建的时候加入comment '中文注释'
第四条:禁止使用存储过程、视图、触发器、Event
解读:高并发大数据的互联网业务,架构设计思路是“
解放数据库CPU,将计算转移到服务层”,并发量大的情况下,这些功能可能将数据库拖死,业务逻辑放到服务层具备更好的扩展性,能够轻易实现“
增加业务应用层的机器来增加性能”。(数据库擅长存储与索引,CPU计算还是留给业务应用层)
第五条:禁止在数据库中存储大文件
解读:照片,可以将大文件存储在对象存储系统,数据库中存储路径
二、命名规范
第一条:只允许使用内网域名,而不是ip连接数据库
解读:虽然IP访问更快,域名访问需要内网DNS,但是对于大数据库的扩展和迁库考虑,域名更好
第二条:库名、表名、列名必须用小写,采用下划线分隔
解读:abc、Abc、ABC都是给自己埋坑
第三条:库名、表名、列名必须见名知义,长度不要超过32字符,禁止拼音英文混用
解读:见名知意,方便后续维护
第四条:表名t_xxx,非唯一索引idx_xxx,唯一索引uniq_xxx
解读:见名知意,方便后续维护
第五条:库备份必须以bak为前缀,以日期为后缀
第六条:从库必须以-s为后缀
第七条:备库必须以-ss为后缀
三、表和字段设计规范
第一条:禁止使用外键,如果要保证完整性,应由应用程式实现
解读:外键使得表之间相互耦合,影响update、delete等SQL性能,有可能造成死锁,高并发情况下容易成为数据库瓶颈。
第二条:必须把字段定义为NOT NULL并设默认值
解读:
(1)NULL的列使用索引,索引统计,值都更加复杂,MySQL更难优化
(2)NULL需要更多的存储空间
(3)NULL只能采用IS NULL 或者 IS NOT NULL,而在=/!=/in/not in时有大坑,比如:执行where name != 'tim',如果存在name为null值得记录,查询结果就不会包含name为null的记录值。
第三条:禁止使用TEXT、BLOB类型(建议将大字段,访问频度低的字段拆分到单独的表中存储,分离冷热数据)
第四条:禁止使用小数存储国币
解读:使用
“分”作为单位,这样数据库里就是整数
第五条:使用varchar(20)存储手机号,不要使用整数
解读:
(1)牵扯到国家代号,可能出现+/-/()等字符,例如+86
(2)手机号不会用来做数学运算
(3)varchar可以模糊查询,例如:like '138%'
第六条:使用TINYINT来代替ENUM
解读:ENUM增加新值要进行DDL操作
第七条:表必须有主键,推荐使用UNSIGNED整数为主键
解读:
(1)主键递增,数据行写入可以提高插入性能,可以避免page分裂,减少表碎片提升空间和内存的使用;
(2)主键要选择较短的数据类型,Innodb引擎普通索引都会保存主键的值,较短的数据类型可以有效减少索引的磁盘空间,提高索引的缓存效率;
(3)无主键的表删除,在row模式的主从架构,会导致备库挂住
第八条:使用INT UNSIGNED 存储IPv4,不要用char(15)
第九条:根据业务区分使用tinyint/int/bigint,分别会占用1/4/8字节
第十条:根据业务区分使用char/varchar
解读:
(1)字段长度固定,或者长度近似的业务场景,使用使用char,能够减少碎片,查询性能高
(2)字段长度相差较大,或者更新较少的业务场景,适合使用varchar,能够减少空间
第十一条:单实例表个数必须控制在2000个以内
第十二条:单表分表个数必须控制在1024个以内
第十三条:根据业务区分使用datetime/timestamp
解读:前者占用5个字节,后者占用4个字节(2038年),推荐使用DateTime。
四、索引设计规范
第一条:单张表索引数量建议控制在5个以内
解读:
(1)互联网高并发业务,太多索引会影响写性能
(2)生成执行计划时,如果索引太多,会降低性能,并可能导致MySQL选择不到最优索引
(3)异常复杂的查询需求,可以选择ES等更为适合的方式存储
第二条:组合索引字段数不建议超过5个
解读:如果5个字段不能极大缩小row范围,八成是设计有问题
第三条:禁止在更新十分频繁、区分度不高的属性上建立索引
解读:
(1)更新会变更B+树,更新频繁的字段建立索引回大大降低数据库性能;
(2)
“性别”这种区分不大的属性,建立索引没有什么意义,不能有效过滤数据,性能与全表扫描类似
第四条:建立组合索引,必须把区分度高的字段放在前面
解读:能够更加有效的过滤数据
五、SQL使用规范
第一条:禁止使用select *,只获取必要字段,需要显示说明列属性
解读:
(1)读取不需要的列会增加CPU、IO、NET消耗
(2)不能有效的利用覆盖索引
(3)使用SELECT * 容易在增加或者删除字段后出现程序BUG
第二条:insert必须指定字段,禁止使用insert into T values()
解读:指定字段插入,在表结构变更时,能保证对应用程序无影响
第三条:禁止使用属性隐式转换
解读:SELECT uid FROM t_user WHERE phnoe=13890392334 会导致全表扫描,而不能命中phone索引。int数据类型优先级高于varchar,改查询会把phone转换为int,因此需要把表中的phone数据转换成int,而全表扫描phone是varchar类型,SQL语句带入的是整形,故不会命中索引。
解决方法:SELECT uid FROM t_user WHERE phone='13890392334'(加个引号就好)
第四条:禁止在where条件列使用函数或者表达式
解读:SELECT uid FROM t_user WHERE from_unixtime(day)>'2017-02-15'导致全表扫描
解决方法:SELECT uid FROM t_user WHERE day>=unix_timestamp('2017-02-15 00:00:00')
导致不能命中索引,全表扫描
第五条:禁止大表使用JOIN查询,禁止大表使用子查询
解读:会产生临时表,消耗较多内存与CPU,极大影响数据库性能(大表指的是数据量在1000万以上)
第六条:禁止使用OR条件,必须改为IN查询
解读:旧版本的MySQL的OR查询不能命中索引的。而IN能够有效使用索引。
第七条:理解组合索引最左前缀原则,避免重复建索引,如果建立了(a,b,c),相当于建立了(a),(a,b),(a,b,c)
第八条:禁止使用负向查询,以及%开头的模糊查询
解读:
(1)什么是
负向查询:比如:NOT、!=、!<、!>、NOT IN、NOT LIKE等,会导致全表扫描;
(2)%开头的模糊查询,会导致全表扫描。
一般来说,WHERE过滤条件不会只带这么一个“负向查询条件”,还会有其他过滤条件
举个栗子:
查询tim已完成订单之外的订单:
SELECT oid FROM t_order WHERE uid=123 AND status
!=1;订单表5000W数据,但uid=123就会迅速的将数量过滤到很少的级别(uid建立了索引),此时再接上一个负向的查询条件就无所谓,扫描的行数就会很少。但如果要查询所有已完成订单之外的订单:SELECT odi FROM t_order WHERE status != 1;这就挂了,立马CPU100%,status索引会失效,负向查询导致全表扫描。
第九条:应用程序必须捕获SQL异常,并且有相应处理