数据库设计规范章节,依旧以《阿里巴巴Java开发手册》为原型进行修正和完善。
MySQL规约
(一) 建表规约
(二) 索引规约
(三) SQL规约
(四) ORM规约
(一) 建表规约
1.
【强制】
表达是与否概念的字段,必须使用is_xxx的方式命名,数据类型是
unsignedtinyint(
1表示是,0表示否),此规则同样适用于odps建表。
说明:任何字段如果为非负数,必须是unsigned。
2.
【强制】
表名、字段名必须使用小写字母或数字;禁止出现数字开头,禁止两个下划
线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名
称需要慎重考虑。
正例:
getter_admin
,
task_config
,
level3_name
反例:
GetterAdmin
,
taskConfig
,
level_3_name
3.
【强制】
表名不使用复数名词。
说明:表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于DO类名
也是单数形式,符合表达习惯。
4.
【强制】
禁用保留字,如desc、range、match、delayed等,请参考MySQL官方保留
字。
5.
【强制】
唯一索引名为uk
字段名;普通索引名则为idx
字段名。
说明:uk
即 unique key;idx
即index的简称。
6.
【强制】
小数类型为decimal,禁止使用float和double。
说明:float和double在存储的时候,存在精度损失的问题,很可能在值的比较时,得
到不正确的结果。如果存储的数据范围超过decimal的范围,建议将数据拆成整数和
小数分开存储。
7.
【强制】
如果存储的字符串长度几乎相等,使用char定长字符串类型。
8.
【强制】
varchar是可变长字符串,不预先分配存储空间,长度不要超过5000,如果
存储长度大于此值,定义字段类型为text,独立出来一张表,用主键来对应,避免影响
其它字段索引效率。
9.
【强制】
表必备字段:id,
creation_time,creator,modified_time,modifier,valid。
说明:其中id必为主键,类型为unsigned bigint、单表时自增、步长为1。
gmt_create,gmt_modified的类型均为date_time类型。
10.
【强制⭐】
所有表必须有主键,主键必须保证有序。Mysql的锁,锁的是主键索引树,没主
键会导致所有操作都是锁全表。而主键无序,会导致频繁页分裂,大大降低更新速度。
11.
【推荐】
表的命名最好是加上“业务名称_表的作用”。
正例:
tiger_task / tiger_reader / mpp_config
12.
【推荐】
库名与应用名称尽量一致。
13.
【推荐】
如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。
14.
【推荐】
字段允许适当冗余,以提高性能,但是必须考虑数据同步的情况。冗余字段
应遵循:
不是频繁修改的字段。
不是varchar超长字段,更不能是text字段。
正例:商品类目名称使用频率高,字段长度短,名称基本一成不变,可在相关联的表中冗余存储
类目名称,避免关联查询。
15.
【推荐】一般情况下,
单表行数超过500万行或者单表容量超过2GB,才推荐进行分
库分表。
如果某张表字段特别多,或者字段比较大,则需要提前进行。按照以前的经验,
110个字段的表,在数据量超过100万时,已经会比较严重地拖慢整体性能了。
说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分
表。
16.【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是
提升检索速度。
1
正例:人的年龄用
unsigned tinyint
(表示范围
0-255
,人的寿命不会超过
255
岁);海龟就
必须是
smallint
,但如果是太阳的年龄,就必须是
int
;如果是所有恒星的年龄都加起来,那么
就必须使用
bigint
。
(二) 索引规约
1.
【强制】
业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。
说明:不要以为唯一索引影响了insert速度,这个速度损耗可以忽略,但提高查找速
度是明显的;另外,即使在应用层做了非常完善的校验和控制,只要没有唯一索引,
根据墨菲定律,必然有脏数据产生。
2.
【强制】
超过三个表禁止join。需要join的字段,数据类型保持绝对一致;多表关联查
询时,保证被关联的字段需要有索引。
说明:即使双表join也要注意表索引、SQL性能。
3.
【强制】
在varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索
引,根据实际文本区分度(离散度)决定索引长度。
说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为20的索
引,区分度会高达90%以上,可以使用count(distinct left(列名,索引长度))/count(*)
的区分度来确定。
4.
【强制】
页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
说明:索引文件具有B-Tree的最左前缀匹配特性,如果左边的值未确定,那么无法
使用此索引。
5.
【强制】
对于联合索引左边第一个能够覆盖到的索引列,不要再单独添加索引。
6.【强制】创建索引的列,不要有Null值,查询Null值无法走索引。
7.
【推荐】
如果有order by的场景,请注意利用索引的有序性。order by最后的字段是
组合索引的一部分,并且放在索引组合顺序的最后,避免出现file_sort的情况,影响查
询性能。
1
正例:
where a=? and b=? order by c;
索引:
a_b_c
2
反例:索引中有范围查找,那么索引有序性无法利用,如:
WHERE a>10 ORDER BY b;
索引
a_
b
无法排序。
8.
【推荐】
利用覆盖索引来进行查询操作,来避免回表操作。
说明:如果一本书需要知道第11章是什么标题,会翻开第11章对应的那一页吗?目
录浏览一下就好,这个目录就是起到覆盖索引的作用。
1
正例:能够建立索引的种类:主键索引、唯一索引、普通索引,而覆盖索引是一种查询的一种效
果,用
explain
的结果,
extra
列会出现:
using index
。
9.
【推荐】
利用延迟关联或者子查询优化超多分页场景。
说明:MySQL并不是跳过offset行,而是取offset+N行,然后返回放弃前offset行,
返回N行,那当offset特别大的时候,效率就非常的低下,要么控制返回的总页数,
要么对超过特定阈值的页数进行SQL改写。
10.
【推荐】
SQL性能优化的目标:至少要达到 range级别,要求是ref级别,如果可以
是consts最好。
说明:
1)consts单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取
到数据。
2)ref指的是使用普通的索引(
normal index)。
3)range对索引进行范围检索。
11.
【推荐】
建组合索引的时候,区分度最高的在最左边(最左匹配原则)。
12.【参考】创建索引时避免有如下极端误解:
误认为一个查询就需要建一个索引。
误认为索引会消耗空间、严重拖慢更新和新增速度。
误认为唯一索引一律需要在应用层通过“先查后插”方式解决。
误认为索引建了就一定会走索引。
误认为索引一定能提高查询效率。
(三) SQL规约
1.
【强制】
不要使用count(列名)或count(常量)来替代count(
*)
,count(
*)
就是
SQL92定义的标准统计行数的语法,跟数据库无关,跟NULL和非NULL无关。
说明:count(*)会统计值为NULL的行,而count(列名)不会统计此列为NULL值的
行。
2.
【强制】
count(distinct column)计算该列除NULL之外的不重复数量。注意
count(distinct column1, column2)如果其中一列全为NULL,那么即使另一列有不同的
值,也返回为0。
1
正例:先快速定位需要获取的
id
段,然后再关联:
SELECT a.* FROM
表
1 a, (select id from
表
1 where
条件
LIMIT 100000,20 ) b wh
ere a.id=b.id
2
反例:
explain
表的结果,
type=index
,索引物理文件全扫描,速度非常慢,这个
index
级别
比较
range
还低,与全表扫描是小巫见大巫。
1
1
正例:如果
where a=? and b=?
,
a
列的几乎接近于唯一值,那么只需要单建
idx_a
索引即可。
说明:存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如:
wherea
>?and b=?
那么即使
a
的区分度更高,也必须把
b
放在索引的最前列。
2
3.
【强制】
当某一列的值全是NULL时,count(column)的返回结果为0,但
sum(column)的返回结果为NULL,因此使用sum(column)时需注意NPE问题。
1
正例:可以使用如下方式来避免
sum
的
NPE
问题:
SELECT IF(ISNULL(SUM(g)),0,SUM(g))
FROM table;
4.
【强制】
使用ISNULL()来判断是否为NULL值。注意:NULL与任何值的直接比较都为
NULL。
说明:
1) NULL<>NULL的返回结果是NULL,而不是false。
2) NULL=NULL的返回结果是NULL,而不是true。
3) NULL<>1的返回结果是NULL,而不是true。
5.
【强制】
在代码中写分页查询逻辑时,若count为0应直接返回,避免执行后面的分页
语句。
6.【强制⭐】不得使用外键与级联,一切外键概念必须在应用层解决。
说明:(概念解释)学生表中的student_id是主键,那么成绩表中的student_id则为
外键。如果更新学生表中的student_id,同时触发成绩表中的student_id更新,则为
级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更
新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
7.
【强制】
禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
8.
【强制】
数据订正时,删除和修改记录时,要先select,避免出现误删除,确认无误
才能执行更新语句。
9.
【强制】
删除数据时,禁止“delete from Table where Condition”。必须先查询出待删除的
数据,然后根据数据ID删除。程序如果没有传入Condition的条件,最终结果不是不删除数据,
而是删除所有数据,必须避免。
10.
【推荐】
in操作能避免则避免,若实在避免不了,需要仔细评估in后边的集合元素数
量,控制在1000个之内。
11.【参考】如果有全球化需要,所有的字符存储与表示,均以utf-8编码,那么字符计
数方法
注意:
说明:
SELECT LENGTH("轻松工作");返回为12
SELECT CHARACTER_LENGTH("轻松工作");返回为4
如果要使用表情,那么使用utfmb4来进行存储,注意它与utf-8编码的区别。
12.【参考】TRUNCATETABLE比 DELETE速度快,且使用的系统和事务日志资源少,
但TRUNCATE无事务且不触发trigger,有可能造成事故,故不建议在开发代码中使用此
语句。
说明:TRUNCATE TABLE在功能上与不带 WHERE子句的 DELETE语句相同。
(四) ORM规约
1.
【强制】
在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确
写明。
说明:
1)增加查询分析器解析成本。
2)增减字段容易与resultMap配置不一致。
2.
【强制】
POJO类的boolean属性不能加is,而数据库字段必须加is_,要求在
resultMap中进行字段与属性之间的映射。
说明:参见定义POJO类以及数据库字段定义规定,在sql.xml增加映射,是必须的。
3.
【强制】
不要用resultClass当返回参数,即使所有类属性名与数据库字段一一对应,
也需要定义;反过来,每一个表也必然有一个与之对应。
说明:配置映射关系,使字段与DO类解耦,方便维护。
4.
【强制】
xml配置中参数注意使用:#{},#param#不要使用${}此种方式容易出现SQL
注入。
5.
【强制】
iBATIS自带的queryForList(String statementName,int start,int size)不推荐
使用(它是基于内存的分页方式,存在深分页问题)。
说明:其实现方式是在数据库取到statementName对应的SQL语句的所有记录,再
通过subList取start,size的子集合,线上因为这个原因曾经出现过OOM。
1
正例:在
sqlmap.xml
中引入
#start#, #size#
2
Map<String, Object> map = new HashMap<String, Object>();
3
map.put("start", start);
4
map.put("size", size);
6.
【强制】
不允许直接拿HashMap与Hashtable作为查询结果集的输出。
7.
【强制】
更新数据表记录时,必须同时更新记录对应的更新人与更新时间。对于拿不
到更新人的场景,可以统一使用保留字”system“,不要留空。
8.
【推荐】
不要写一个大而全的数据更新接口,传入为POJO类,不管是不是自己的目
标更新字段,都进行update table set c1=value1,c2=value2,c3=value3;这是不对的。
执行SQL时,尽量不要更新无改动的字段,一是易出错;二是效率低;三是binlog增加
存储。
9.【推荐】编写查询语句”select column1,column2,... from table where XXX“时,字
段的个数如果比较少,可以适当冗余。
试想一个场景:代码中同时存在针对某个实体的查询方法,它们的入参和返回值完
全一样,只有条件不同,这很容易被误解两个方法的返回值包含的属性值也一致。但如
果底层的实现中,select 的列名不一样,则很容易导致空指针。
思考:只查询需要的字段,数据库只需要返回用得到的字段即可,但方法可能有歧
义导致误用。查询全量字段,会要求数据库返回更多的信息,不过方法含义明确,不会
因为漏了字段值而导致问题。
10.【参考】@Transactional事务不要滥用。事务会影响数据库的QPS,另外使用事务
的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修
正等。
11.【参考】中的compareValue是与属性值对比的常量,一般是数字,表示相等时带上
此条件;表示不为空且不为null时执行;表示不为null值时执行。
12.【参考】作为更新场景的补充,为了不全量更新,一般会先判断传入的值是否为空,不为空
则更新。此时如果确实想设置某些字段的值为空,则无法实现。这种情况下,建议单独写一个设
置空值的方法,而不是通过一个全量更新的方法实现。
数据库层的调优
这里主要是一些与数据库调优相关的内容,包括数据库服务器,SQL分析等。
数据库服务器的性能
服务器端的分析集中在这几个方向上:硬件资源。数据库架构,以及数据库实例参数优化。
硬件资源分析
1,使用top命令查看CPU负载。
2,使用free命令查看内存使用情况。
3,使用iostat工具查看磁盘I/O使用情况。
4,使用vmstat命令查看系统的负载情况。
5,使用perf top命令查看系统热点情况。
6,使用nmon工具监控系统一段时间的整体情况。
使用腾讯云数据库时,也可以用腾讯云提供的相关工具。 如果发现数据库主机的CPU、
I/O、内存等使用率很高,那么原因有两种:
1,数据库实例存在性能瓶颈。
2,实例所在的机器硬件本身存在问题(可能性比较小,也容易排除) 。
数据库架构分析
目前公司主要是云数据库架构,依托腾讯云本身的能力,这一块的架构分析可以略过或找腾
讯云的售后进行支持。
服务器调优
数据库服务器调优。可以结合业务场景对数据库参数进行优化。以下是一个优化示例。
例如:有一个密集交易型数据库服务器配置如下:
CPU:4路8核。
内存:256G。
磁盘阵列:1T。
那么,推荐的参数设置如下:
识别慢SQL
目前使用腾讯云数据库,它们提供了数据库监控以及慢日志监控等功能,可以直接拿到慢
SQL。接下来按并发度对这些SQL进行分类:
1,并发非常高。SQL特征:SQL的条数很少(按5%统计),但是执行频率非常高,甚至达到
每秒上百次,只要一慢,系统很可能瘫痪。
优化级别:最优先处理。
优化方向:
对SQL本身进行优化,调到最优。
对发起SQL请求的应用进行优化,减少执行次数。包括使用缓存,多次请求合并为一次
等等。
2,并发一般 。SQL特征:占大多数(按80%统计),如果有慢的,对系统整体稳定性影响不
大,但是会造成局部的某些操作慢。
优化级别:次优先处理。
优化方向:对SQL本身进行优化,综合考虑索引,SQL改写等方式。
3,并发很少特别慢。SQL特征:数量少(按15%统计),往往是很复杂的查询,可能一天就
执行几次,对系统整体影响不大,但是优化难度很大。
优化级别:最后处理 。
优化方向:对SQL本身进行优化,同时可以对这类SQL有一定容忍度。
阻塞与死锁分析
死锁与阻塞是最为常见的两个性能杀手。可以用一些SQL来观测SQL的执行情况:
【show processlist】:此命令可以查看服务端线程状态。利用这些信息,可以找到耗时比
较长的环节并对其进行优化。还可以对阻塞的线程进行kill。使用【show processlist】会列出一
系列信息,其含义如下:
1,id。这是线程的唯一标记,【
kill {id}】可以终止该线程。
2,user。这个字段表示启动该线程的用户。
3,host。这个字段表示发起该连接的host。
4,db。这个字段表示操作的数据库是哪个。
5,command。表示操作的命令,包括SQL语句也是一种命令。
6,time。表示该操作持续了多久。
7,state。表示该线程当前处于什么状态。
8,info。包含前100个字符的Sql语句。如果想查看完整SQL,可以使用【show full processlist】。
【show status】:此命令可以查看Mysql服务器的运行状态,重启后状态信息会被清空。
还可以带上作用域,比如【
show global status】表示查看全局状态。
【show engine】:此命令用于查看存储引擎的状态。包括事务持有的表锁、行锁信息;事
务的锁等待情况;线程信号量等待;文件IO 请求;buffer pool 统计信息等。
死锁的特征:
1,死锁是相互堵塞。
2,数据库自动识别死锁并解锁。
3,SQL日志中会记录死锁信息。
避免数据库死锁需要采取以下措施:
1,减少事务的数量:减少并发事务的数目是避免死锁的首要步骤。
2,统一事务处理:尽量将对同一数据库表的更新操作放在同一个事务中,以减少出现死锁
的机会。
3,按照相同的顺序访问资源: 所有事务都按照相同的顺序来访问资源,以避免出现交叉等
待的情况。
4,使用低级别锁:尽可能使用低级别锁,例如行锁而不是表锁,以减少出现死锁的机会。
5,缩短锁定时间:尽量缩短锁定时间,并在完成后立即释放锁,以便其他事务可以及时访
问相关资源。
6,定期清理无用的锁:通过定期清理不再使用的锁,可以避免死锁的产生。
7,使用数据库提供的工具检测死锁:大部分数据库系统都提供了用于检测死锁的工具或
API,可以使用这些工具或API来监视数据库的死锁情况,并及时采取措施。
8,优化数据库设计和查询:优化数据库表格设计和查询语句,包括索引的建立、SQL 查询
语句的优化等,可以降低数据库死锁的风险。
阻塞的特征:
1,阻塞的SQL不会互相影响,它是单向的。
2,数据库无法自动识别阻塞并进行处理。
3,SQL日志中不会记录阻塞信息,表现形式只会是某SQL执行时间超长。
处理阻塞需要人工介入,大致方向有两个:减少并发操作,以及减少慢SQL。具体措施:
1,使用低级别锁:尽量使用低级别的行级别锁而不是高级别的表级别锁,以减少对资源的
阻塞。
2,使用索引: 通过在 WHERE 子句中使用索引列可以极大地加快 SQL 查询速度,从而减
少对相关资源的占用时间。
3,分批处理数据:如果需要处理大量数据,可以将其分批处理,以便避免一次性占用过多
的资源。
4,避免长事务和长查询:长时间运行的事务和长时间运行的查询会持续占用资源,导致其
他操作被阻塞。应该避免这种情况并定期清理无用的事务和查询。
5,调整数据库参数: 可以通过调整数据库参数来优化 SQL 执行性能,例如增加缓存大
小、调整线程池大小等。
6,定期优化数据库: 定期检查和优化数据库表格结构、索引、存储引擎等,可以提高数据
库的性能和响应速度。
7,合理设计 SQL 查询语句:合理的 SQL 查询语句设计可以有效地降低系统的负载和阻塞
风险。
SQL执行流程分析
对SQL执行流程所涉及的每一个环节进行优化,最终结果就是最优的。按照这个思路,先来
看看SQL执行流程:
客户端层的优化:
1,减少连接消耗:多次请求合并为一次,合理设置连接池的大小和连接施放时间。注意连
接数并不是越多越好。连接过多除了导致资源消耗增加外,还会额外引入更多的线程切换成本。
这里提供一个经验公式用于推断连接数的个数:【连接数=CPU核心数*2+1】
2,降低查询频率:合理使用缓存,将多条请求合并为同一条。
Mysql连接层的优化:
1,增加连接数。
2,及时释放不使用的连接。
3,Mysql相关参数调优。
查询缓存层的优化:查询缓存层设计得比较纠结,默认已经禁用了,仅当读远远大于写的场
景才有用。这一层的优化可以忽略。
SQL解析器和预处理器:这两个组件的作用是生成一颗正确的语法树。它的使用对用户来说
是黑盒,无法干预。
查询优化器:这一层会根据语法树生成多个执行计划,并进行优选。对SQL的优化,主要集
中在这里。
执行引擎:这一层根据执行计划进行调度,对用户来说,只能通过干预执行计划来间接干预
执行引擎的工作。
存储引擎层的优化:根据业务场景选择合适的存储引擎。例如归档表往往对事务的要求比较
弱,又以读为主,可以设置使用Mysiam引擎。再比如一些高频使用的小表可直接使用Memory
引擎常驻内存。
分析执行计划
在SQL语句前面加上explain并执行,可以列出该sql语句的执行计划,其语法格式如下:
【
explain {sql}】。在MySQL 5.6.3 以前的版本,只能分析SELECT。MySQL5.6.3以后就可以
分析update、delete和insert了。
一个典型的执行计划信息如下:
可以看到上述SQL语句出现了多行执行计划信息。这些执行计划表达的含义就是该SQL执行
时需要经过的步骤和其它信息,每条数据就是执行计划的一个步骤。接下来详细看看每个字段的
含义。
id字段:
id表示SQL语句执行的顺序。数字越大的执行计划越先被执行。如果数字相同,则按从上往
下的顺序依次执行。例如,子查询的优先级会高于外层查询。
如果是并列的查询,没有父子之分,查询优化器会自动进行优化:它会选用笛卡尔积相对较
小的结果作为中间结果,从而使得它们先被执行。Mysql的优化器是基于开销的,更小的中间结
果消耗更少的资源。此外,这也要求我们在做优化时,选择更小的表作为驱动表。
select_type字段:
这个字段表示查询的类型。查询类型很多,这里列举几个常见的:
1,simple。simple表示普通查询,它不包含子查询也不包含union查询。
2,primary。如果一个SQL语句包含子查询,那么最外层的查询类型就是primary。
3,subquery。如果一个SQL语句包含子查询,那么最内层的子查询类型就是subquery。
4,derived。意思是衍生查询。如果在得到最终结果之前,一个查询操作用到了临时表,那
么该查询的类型就是derived。比如一个union查询,Mysql会先执行union右边的语句,得到中
间结果放到临时表里,然后执行union左边的语句并与刚刚的临时表关联,其类型就是derived。
5,union。使用了union查询时,出现在union右边的查询会被标记为union类型。
6,union result。这种类型表示所有的union语句执行完毕后产生的中间结果。即使有多个
union查询,union result也只有一项。
table字段:
table字段表示查询过程中用到的表,包括普通的表和中间结果的表。普通表就不用细说
了,显示哪个表名,就表示针对那张表的查询。在union查询时,有时候table字段会显示类似
【
<union1,3,4>】这种值。这个值表示把ID为1,3,4的查询产生的结果集,作为当前查询所使
用的table。
type字段:
这个字段很关键,其含义是连接类型。不同的连接类型其执行效率差别很大,下面按性能由
高到低的顺序排列它们,并分别解释每一项的含义:
1,system。system表示查询系统表,表里只有一行记录。这个是最快的,然而是一种特
例,没有讨论的价值,基本不会出现。
2,const。它表示通过索引一次就找到了。通过一次索引就能找到数据的情况只有一种:
通过唯一索引查询数据,并且只查询到了一条数据。例如【
select * from t_table where
id=1】。
3,eq_ref。它通常出现在join语句中。对于前表中的每个索引列,都只能匹配到后表中的
唯一结果,此时后表的连接类型就是eq_ref。既然只能匹配到唯一结果,那多半是唯一性的索引
了。一般而言,join语句后面on的条件是唯一索引时会匹配到这种类型。
4,ref。它也是通常出现在join语句中。如果使用的不是唯一性的索引,那么前表中的索引
列的值可能对应后表中的多个结果,此时后表的连接类型就是ref。一般而言,join语句后面on
的条件是非唯一索引时会匹配到这种类型。
5,range。range表示使用索引进行范围查找。通常【
in (...),between... and ...】或者
【where column_a>{number}】这种类型的语句会匹配到range类型。
6,index。index的意思是走索引的全表扫描。将所有索引扫一遍去找对应结果,这个已经
很慢了。遇到index类型的查询,说明需要优化。
误解:很多人认为“index”表示走索引的查询,这个理解大大的错,index的性能很低,说不
定还不如顺序的全表扫描,至少不用随机读。
7,all。all是最糟糕的情况,它表示不走索引全表扫描。我们应该尽量避免all。
8,Null。这是一种特殊情况。当前查询不需要查询表时,其type会是Null。比如获取系统
时间就不需要查询表,那么其执行计划显示的type就是Null。
我们对SQL语句的优化,需要达到
range
级别以上。
possible_keys字段:
这个是指查询中可能会用到的索引,不必特别关注。
key字段:
指实际查询中使用的索引,这个也不必特别关注。但是如果这个字段的值是Null,说明该查
询没有用到索引。一般来说,组合索引使用时如果不符合最左匹配,会出现“possible_keys"有
值,但”key"字段为空的情况。
Extra字段:
extra这个单词的意思是额外的。这里这个字段表示针对该查询干了哪些额外的事情,也是
一个比较重要的字段。关于这个字段的值,常用的是以下几个:
1,Using filesort。如果没有使用索引排序,而是使用了额外的排序规则,那么Extra就会
记录Using filesort。
2,Using temporary。tempoary的意思是临时工,它表示Mysql使用了临时表存储中间结
果。一般而言【
group by,distinct】之类的语句需要用到“临时工”。
3,Using index。它表示使用了覆盖索引。命中覆盖索引则不需要回表。另外,之前介绍索
引条件下推时也说到,索引条件下推时,extra字段的值也会是Using index。
4,Using where。它表示存储引擎返回数据后,server层使用where条件进行了数据筛选。
5,Select tables optimized away。这个表示没走到查询阶段就可以返回结果了。按官网
的说法,“在没有GROUP BY子句的情况下,基于索引优化MIN/MAX操作,或者对于MyISAM
存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成
优化。”
拿到数据后,最后一步就是返回数据给客户端了。Mysql采用的是一种即时返回的策略。它
不会等所有结果集就位后一起返回,而是从产生第一条结果时就开始返回数据到客户端。当然,
如果配置了缓存,结果会放到缓存里。返回数据是走网络,所以一个显而易见的结论是:传输的
数据越少返回越快。所以提取数据时,应该指明需要的字段,而不是无脑写“ * ”。
索引结构与调优
Mysql采用经过改良的B+树来存储索引数据。 B+树是一个加强版的B树,Mysql的B+树
与传统的B+树还不太一样,它有以下特征:
1,关键字个数等于路数。
2,InnoDB里,B+树节点不存储实际数据,所有数据放到最底层的叶子节点里。这些叶子
节点投影成了一个有序数组。
3,InnoDB里,每个叶子节点维护了一个指向它下一个叶子节点的指针,这样就形成了一个
双向链表。由于B树的叶子节点投影本身有序,这就成了一个有序的双向链表。这个有序链表对
范围查找非常友善,不需要再回到根节点重复IO。
4,这棵树的分叉非常多,使得这棵树的结构非常扁平。这样的结构能够在存储尽可能多的
树的同时,让IO次数尽可能少。假设这棵树只有三层,按每条数据1KB计算,大约有130万子节
点,能存储两千多万条数据。
5,存储数据时,需要进行子节点的分裂和合并重新调整。
整体结构如下:
根据以上结构,能推导出以下优化经验:
1,仅在需要的时候创建索引。索引再能满足需求的前提下,越少越好。一方面索引本身也
会占据不少的数据库资源(磁盘+内存),另一方面,在对数据进行增删改时,需要同步更新索
引树导致额外开销。
2,索引的字段要保证比较高的离散度。这是因为,如果一个字段的离散度太低,那么扫描
这颗B+树时就需要走更多的路数。当离散度低到一定程度时,甚至还不如直接走叶子节点扫全
表,这样至少能适用磁盘顺序读,比走索引更快。
3,创建联合需要符合最左匹配原则。对于联合索引,B+树上的键值也是从左往右构建和匹
配索引数据的。如果左边第一个命中,则直接按第一个走。未命中会继续走第二个,以此类推。
4,应用覆盖索引减少回表操作。根据上面的B+树结构可知,聚簇索引的叶子节点存储原始
数据。所以如果SQL的返回值刚好是索引值,那就不需要走叶子节点取数,直接在索引层就能返
回了。
5,有序ID减少页分裂。由于这棵B+树要保证叶子节点有序,所以如果插入了无序的ID,会
重新排列叶子节点。但如果数据本身有序,那么只需要顺序往后写即可,会更加高效。
6,查询条件里尽量不要有函数运算或子查询。索引树放在存储引擎层,只能做简单的逻辑
判断。如果写SQL时能保证逻辑简单,那这部分逻辑可以下推到存储引擎层,从而只返回符合条
件的数据。否则,只能用尽可能简单的方式扫描尽可能多的数据,再由执行引擎过滤掉不符合要
求的数据,导致性能低下。
SQL优化经验
其实,掌握了SQL的分析方法,优化方法也就出来了。有很多零零碎碎的优化经验,这里简
单列一些:
1,尽量避免在WHERE子句中使用函数,因为会使索引失效。
2,使用JOIN语句而不是子查询,因为JOIN语句更有效率。
3,确保表有适当的索引,以加快查询速度。
4,避免在SELECT语句中使用“*”,尽可能只选择需要的列。
5,尽量避免使用OR操作符,它会导致全表扫描而不是使用索引。
6,对于大型数据集,请考虑分区表或使用分页技术。
7,使用EXPLAIN语句来检查查询执行计划,找到问题并进行优化。
8,使用合适的数据类型,例如使用INT代替VARCHAR存储数字类型。
9,将多个单行查询组合成一个查询,以减少与数据库服务器的通信次数。
10,定期清理不再使用的索引和表,以保持数据库性能稳定。
个人经验和网传资料太过片面,想要系统掌握SQL优化的方法,还是建议看官网。以下是官
方提供的优化办法:
https://dev.mysql.com/doc/refman/5.7/en/optimizing-innodb-loggin
g.html
。
Mysql使用基于开销的优化器,大多数时候它能够很好工作。如果查询优化器没有按预期的
方式工作,可以使用 FORCE INDEX 扫描表告诉 MySQL强制走索引。与使用给定索引相比,表
扫描非常昂贵:
1
SELECT * FROM t1, t2 FORCE INDEX (index_for_column)
2
WHERE t1.col_name=t2.col_name;