2.1.4 索引设计
- 【强制】InnoDB表必须主键为
id int/bigint auto_increment
,且主键值禁止被更新。 - 【建议】主键的名称以“
pk_
”开头,唯一键以“uk_
”或“uq_
”开头,普通索引以“idx_
”开头,一律使用小写格式,以表名/字段的名称或缩写作为后缀。 - 【强制】InnoDB和MyISAM存储引擎表,索引类型必须为
BTREE
;MEMORY表可以根据需要选择HASH
或者BTREE
类型索引。 - 【强制】单个索引中每个索引记录的长度不能超过64KB。
- 【建议】单个表上的索引个数不能超过7个。
- 【建议】在建立索引时,多考虑建立联合索引,并把区分度最高的字段放在最前面。如列
userid
的区分度可由select count(distinct userid)
计算出来。 - 【建议】在多表join的SQL里,保证被驱动表的连接列上有索引,这样join执行效率最高。
- 【建议】建表或加索引时,保证表里互相不存在冗余索引。对于MySQL来说,如果表里已经存在
key(a,b)
,则key(a)
为冗余索引,需要删除。
2.1.5 分库分表、分区表
- 【强制】分区表的分区字段(
partition-key
)必须有索引,或者是组合索引的首列。 - 【强制】单个分区表中的分区(包括子分区)个数不能超过1024。
- 【强制】上线前RD或者DBA必须指定分区表的创建、清理策略。
- 【强制】访问分区表的SQL必须包含分区键。
- 【建议】单个分区文件不超过2G,总大小不超过50G。建议总分区数不超过20个。
- 【强制】对于分区表执行
alter table
操作,必须在业务低峰期执行。 - 【强制】采用分库策略的,库的数量不能超过1024
- 【强制】采用分表策略的,表的数量不能超过4096
- 【建议】单个分表不超过500W行,ibd文件大小不超过2G,这样才能让数据分布式变得性能更佳。
- 【建议】水平分表尽量用取模方式,日志、报表类数据建议采用日期进行分表。
2.1.6 字符集
- 【强制】数据库本身库、表、列所有字符集必须保持一致,为
utf8
或utf8mb4
。 - 【强制】前端程序字符集或者环境变量中的字符集,与数据库、表的字符集必须一致,统一为
utf8
。
2.1.7 程序层DAO设计建议
- 【建议】新的代码不要用model,推荐使用手动拼SQL+绑定变量传入参数的方式。因为model虽然可以使用面向对象的方式操作db,但是其使用不当很容易造成生成的SQL非常复杂,且model层自己做的强制类型转换性能较差,最终导致数据库性能下降。
- 【建议】前端程序连接MySQL或者redis,必须要有连接超时和失败重连机制,且失败重试必须有间隔时间。
- 【建议】前端程序报错里尽量能够提示MySQL或redis原生态的报错信息,便于排查错误。
- 【建议】对于有连接池的前端程序,必须根据业务需要配置初始、最小、最大连接数,超时时间以及连接回收机制,否则会耗尽数据库连接资源,造成线上事故。
- 【建议】对于log或history类型的表,随时间增长容易越来越大,因此上线前RD或者DBA必须建立表数据清理或归档方案。
- 【建议】在应用程序设计阶段,RD必须考虑并规避数据库中主从延迟对于业务的影响。尽量避免从库短时延迟(20秒以内)对业务造成影响,建议强制一致性的读开启事务走主库,或更新后过一段时间再去读从库。
- 【建议】多个并发业务逻辑访问同一块数据(innodb表)时,会在数据库端产生行锁甚至表锁导致并发下降,因此建议更新类SQL尽量基于主键去更新。
- 【建议】业务逻辑之间加锁顺序尽量保持一致,否则会导致死锁。
- 【建议】对于单表读写比大于10:1的数据行或单个列,可以将热点数据放在缓存里(如mecache或redis),加快访问速度,降低MySQL压力。
2.1.8 一个规范的建表语句示例
一个较为规范的建表语句为:
CREATE TABLE user ( `id` bigint(11) NOT NULL AUTO_INCREMENT, `user_id` bigint(11) NOT NULL COMMENT ‘用户id’ `username` varchar(45) NOT NULL COMMENT '真实姓名', `email` varchar(30) NOT NULL COMMENT ‘用户邮箱’, `nickname` varchar(45) NOT NULL COMMENT '昵称', `avatar` int(11) NOT NULL COMMENT '头像', `birthday` date NOT NULL COMMENT '生日', `sex` tinyint(4) DEFAULT '0' COMMENT '性别', `short_introduce` varchar(150) DEFAULT NULL COMMENT '一句话介绍自己,最多50个汉字', `user_resume` varchar(300) NOT NULL COMMENT '用户提交的简历存放地址', `user_register_ip` int NOT NULL COMMENT ‘用户注册时的源ip’, `create_time` timestamp NOT NULL COMMENT ‘用户记录创建的时间’, `update_time` timestamp NOT NULL COMMENT ‘用户资料修改的时间’, `user_review_status` tinyint NOT NULL COMMENT ‘用户资料审核状态,1为通过,2为审核中,3为未通过,4为还未提交审核’, PRIMARY KEY (`id`), UNIQUE KEY `idx_user_id` (`user_id`), KEY `idx_username`(`username`), KEY `idx_create_time`(`create_time`,`user_review_status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='网站用户基本信息';
2.2 SQL编写
2.2.1 DML语句
- 【强制】SELECT语句必须指定具体字段名称,禁止写成
*
。因为select *
会将不该读的数据也从MySQL里读出来,造成网卡压力。且表字段一旦更新,但model层没有来得及更新的话,系统会报错。 - 【强制】insert语句指定具体字段名称,不要写成
insert into t1 values(…)
,道理同上。 - 【建议】
insert into…values(XX),(XX),(XX)…
。这里XX的值不要超过5000个。值过多虽然上线很很快,但会引起主从同步延迟。 - 【建议】SELECT语句不要使用
UNION
,推荐使用UNION ALL
,并且UNION
子句个数限制在5个以内。因为union all
不需要去重,节省数据库资源,提高性能。 - 【建议】in值列表限制在500以内。例如
select… where userid in(….500个以内…)
,这么做是为了减少底层扫描,减轻数据库压力从而加速查询。 - 【建议】事务里批量更新数据需要控制数量,进行必要的sleep,做到少量多次。
- 【强制】事务涉及的表必须全部是innodb表。否则一旦失败不会全部回滚,且易造成主从库同步终端。
- 【强制】写入和事务发往主库,只读SQL发往从库。
- 【强制】除静态表或小表(100行以内),DML语句必须有where条件,且使用索引查找。
- 【强制】生产环境禁止使用
hint
,如sql_no_cache
,force index
,ignore key
,straight join
等。因为hint
是用来强制SQL按照某个执行计划来执行,但随着数据量变化我们无法保证自己当初的预判是正确的,因此我们要相信MySQL优化器! - 【强制】where条件里等号左右字段类型必须一致,否则无法利用索引。
- 【建议】
SELECT|UPDATE|DELETE|REPLACE
要有WHERE子句,且WHERE子句的条件必需使用索引查找。 - 【强制】生产数据库中强烈不推荐大表上发生全表扫描,但对于100行以下的静态表可以全表扫描。查询数据量不要超过表行数的25%,否则不会利用索引。
- 【强制】WHERE 子句中禁止只使用全模糊的LIKE条件进行查找,必须有其他等值或范围查询条件,否则无法利用索引。
- 【建议】索引列不要使用函数或表达式,否则无法利用索引。如
where length(name)='Admin'
或where user_id+2=10023
。 - 【建议】减少使用or语句,可将or语句优化为union,然后在各个where条件上建立索引。如
where a=1 or b=2
优化为where a=1… union …where b=2, key(a),key(b)
。 - 【建议】分页查询,当limit起点较高时,可先用过滤条件进行过滤。如
select a,b,c from t1 limit 10000,20;
优化为:select a,b,c from t1 where id>10000 limit 20;
。