Sql性能优化看这一篇就够了

前言:

一个优秀开发的必备技能:性能优化,包括:JVM调优、缓存、Sql性能优化等。本文主要讲基于Mysql的索引优化。
首先我们需要了解执行一条查询SQL时Mysql的处理过程:

其次我们需要知道,我们写的SQL在Mysql的执行顺序是怎么样的?sql的执行顺序对sql的性能优化很有帮助,很重要。在建立复合索引的时候需要考虑到这点。

例:

在tb_dept中建立一个复合索引 idx_parent_id_code:

 然后看下两个sql 解释的结果:

1)在当前索引下,哪一个sql索引利用率高?

借助于上文中查询SQL的执行顺序,是先执行 WHERE再执行 GROUP BY 的,即:

第一个sql执行的顺序是先执行了 where后的 parent_id然后执行了 group by 后的 dept_code,顺序是和索引的顺序是一致的,type等级为ref,扫描行数rows为 4;

而第二个sql是先执行了 where后的 dept_code然后执行了 group by 后的 parent_id,顺序是和索引的顺序是不一致的,type等级为index,扫描行数rows为 19;

从解释结果看,第一条的sql索引利用率高于第二条的。(后文会讲到:索引type从优到差:System-->const-->eq_ref-->ref-->ref_or_null-->index_merge-->unique_subquery-->index_subquery-->range-->index-->all.)

或者从扫描的行数rows对比数据源也可直观的看出,两个语句的性能:

2)怎么优化?

如果业务中用到第二个sql,那么就需要调整索引的顺序和sql执行顺序一致。

或者两个sql都用到了,那么就再建一个复合索引 (idx_code_parent_id)

然后再看下第二条的执行计划:

执行计划分析(下面就是本文的重点内容了):

通过explain可以知道mysql是如何处理语句的,并分析出查询或是表结构的性能瓶颈,其实就是在干查询优化器的事,通过expalin可以得到:

1. 表的读取顺序
2.表的读取操作的操作类型
3.哪些索引可以使用
4. 哪些索引被实际使用
5.表之间的引用
6.每张表有多少行被优化器查询

从上文的例子中我们可以看到执行explain时,结果会有一个表格,这个表格就是分析结果,下面我们来一个一个说明下这个表的表头:

Id:  MySQL QueryOptimizer 选定的执行计划中查询的序列号。表示查询中执行select 子句或操作表的顺序,id 值越大优先级越高,越先被执行。id 相同,执行顺序由上至下。

Select_type: 一共有9中类型,只介绍常用的4种:

SIMPLE: 简单的 select 查询,不使用 union 及子查询

PRIMARY: 最外层的 select 查询

UNION: UNION 中的第二个或随后的 select 查询,不 依赖于外部查询的结果集

DERIVED: 用于 from 子句里有子查询的情况。 MySQL 会 递归执行这些子查询, 把结果放在临时表里。

Table:  输出行所引用的表

Type:  从优到差的顺序如下:(红色标识的是常见的级别。)

system-->const-->eq_ref-->ref-->ref_or_null-->index_merge-->unique_subquery-->index_subquery-->range-->index-->all.

各自的含义如下:

system:  表仅有一行。这是 const 连接类型的一个特例。

const:  const 用于用常数值比较 PRIMARY KEY 时。

eq_ref: 查询使用了索引为主键或唯一键的全部时使用。即:通过索引关键字可能查找到一个符合条件的行。

ref:  通过索引关键字可能查找到多个符合条件的行。

ref_or_null:  如同 ref, 但是 MySQL 必须在初次查找的结果里找出 null 条目,然后进行二次查找。

index_merge: 说明索引合并优化被使用了。

unique_subquery:  在某些 IN 查询中使用此种类型,而不是常规的 ref:valueIN (SELECT primary_key FROM single_table WHERE some_expr)

index_subquery:  在 某 些 IN 查 询 中 使 用 此 种 类 型 , 与unique_subquery 类似,但是查询的是非唯一 性索引

range:  检索给定范围的行。当使用 <>、>、>=、<、<=、BETWEEN 或者 IN 操作符时,会使用到range。

index:  全表扫描,只是扫描表的时候按照索引次序进行而不是行。主要优点就是避免了排序, 但是开销仍然非常大。

all: 最坏的情况,从头到尾全表扫描。

possible_keys :  哪些索引可能有助于查询。如果为空,说明没有可用的索引。

key:  实际从 possible_key 选择使用的索引,如果为 NULL,则没有使用索引。很少的情况 下,MYSQL 会选择优化不足的索引。这种情 况下,可以在 SELECT语句中使用 USE INDEX (indexname)来强制使用一个索引或者用IGNORE INDEX(indexname)来强制 MYSQL 忽略索引

key_len:  使用的索引的长度。在不损失精确性的情况 下,长度越短越好。

ref:  显示索引的哪一列被使用了 

rows:   请求数据返回的大概行数

extra:  其他信息,出现Using filesort、Using temporary 意味着不能使用索引,效率会受到重大影响。应尽可能对此进行优化。

Using filesort: 没有办法利用现有索引进行排序,需要额外排序,建议:根据排序需要,创建相应合适的索引

Using temporary: 需要用临时表存储结果集,通常是因为group by的列列上没有索引。也有可能是因为同
时有group by和order by,但group by和order by的列又不一样 

Using index : 利用覆盖索引,无需回表即可取得结果数据(即数据直接从索引文件中读取),这种结果是好的。

其中重要的几个就是 key、type 、rows、extra,其中key为null、all 、index时,需要调整、优化索引。一般需要达到 ref、eq_ref 级别,范围查找需要达到 range,extra有Using filesort、Using temporary 的一定需要优化,根据rows可以直观看出优化结果。

优化手段:

① SQL优化

  • 避免 SELECT *,只查询需要的字段。
  • 小表驱动大表,即小的数据集驱动大的数据集:
    当B表的数据集比A表小时,用in优化 exist两表执行顺序是先查B表再查A表查询语句:SELECT * FROM tb_dept WHERE id in (SELECT id FROM tb_dept) ;
    当A表的数据集比B表小时,用exist优化in ,两表执行顺序是先查A表,再查B表,查询语句:SELECT * FROM A WHERE EXISTS (SELECT id FROM B WHERE A.id = B.ID) ;
  • 尽量使用连接代替子查询,因为使用 join 时,MySQL 不会在内存中创建临时表。

② 优化索引的使用

  • 尽量使用主键查询,而非其他索引,因为主键查询不会触发回表查询。
  • 不做列运算,把计算都放入各个业务系统实现
  • 查询语句尽可能简单,大语句拆小语句,减少锁时间
  • or 查询改写成 union 查询
  • 不用函数和触发器
  • 避免 %xx 查询,可以使用:select * from t where reverse(f) like reverse('%abc');
  • 少用 join 查询
  • 使用同类型比较,比如 '123' 和 '123'、123 和 123
  • 尽量避免在 where 子句中使用 != 或者 <> 操作符,查询引用会放弃索引而进行全表扫描
  • 列表数据使用分页查询,每页数据量不要太大
  • 避免在索引列上使用 is null 和 is not null

③ 表结构设计优化

  • 使用可以存下数据最小的数据类型。
  • 尽量使用 tinyint、smallint、mediumint 作为整数类型而非 int。
  • 尽可能使用 not null 定义字段,因为 null 占用 4 字节空间。数字可以默认 0 ,字符串默认 “”
  • 尽量少用 text 类型,非用不可时最好独立出一张表。
  • 尽量使用 timestamp,而非 datetime。
  • 单表不要有太多字段,建议在 20 个字段以内。

Mysql常用数据类型存储大小及范围:https://blog.csdn.net/HXNLYW/article/details/100104768


3.如果以上优化还是有问题,可以使用show profiles 分析sql 性能

show profiles  

show profile for query  [queryId]

具体请查看:https://blog.csdn.net/aeolus_pu/article/details/7818498

结尾:

    本文是最近学习Mysql索引优化的一些总结和记录,如有不对的地方,欢迎评论吐槽。


附:

索引相关知识:

———— 查看表索引:
show index from 【table】

———— 直接创建索引
CREATE INDEX indexName ON table(column(length))

———— 修改表结构的方式添加索引
ALTER tableADD INDEX indexName ON (column(length))
---主键索引
ALTER  TABLE  `table_name`  ADD  PRIMARY  KEY (  `column`  ) 
---唯一索引
ALTER  TABLE  `table_name`  ADD  UNIQUE (`column` ) 
---普通索引
ALTER  TABLE  `table_name`  ADD  INDEX index_name (  `column`(length)  )
---复合索引 
ALTER  TABLE  `table_name`  ADD  INDEX index_name (  `column1`,  `column2`,  `column3`  )

length的确定:
如果索引列长度过长,这种列索引时将会产生很大的索引文件,不便于操作,可以使用前缀索引方式进行索引,前缀索引应该控制在一个合适的点,控制在0.31黄金值即可(大于这个值就可以创建)。
SELECT COUNT(DISTINCT(LEFT(`title`,10)))/COUNT(*) FROM Arctic; -- 这个值大于0.31就可以创建前缀索引,Distinct去重复

———— 删除索引:
1)ALTER TABLE table_name DROP INDEX index_name
2)DROP INDEX index_name ON table_name;

MyISAM 和 InnoBD区别:

  

  
  MyISAM

  
  InnoDB

  
主键

允许没有任何索引和主键的表存在,

myisam的索引都是保存行的地址。

如果没有设定主键或者非空唯一索引,就会自动生成一个6字节的主键(用户不可见)

innodb的数据是主索引的一部分,其他索引保存的是主索引的值。

  事务处理上方面:

  
  MyISAM类型的表强调的是性能,其执行数度比InnoDB类型更快,但是不提供事务支持、不支持外键  InnoDB提供事务支持事务,外部键(foreign key)等高级数据库功能

  
  DML操作
  
  如果执行大量的SELECT,MyISAM是更好的选择

  
  1.如果你的数据执行大量的INSERTUPDATE,出于性能方面的考虑,应该使用InnoDB表
  2.DELETE   FROM table时,InnoDB不会重新建立表,而是一行一行的删除。
自动增长

  
  

  myisam引擎的自动增长列必须是索引,如果是组合索引,自动增长可以不是第一列,他可以根据前面几列进行排序后递增。

innodb引擎的自动增长必须是索引,如果是组合索引也必须是组合索引的第一列。
count()函数myisam保存有表的总行数,如果select count(*) from table;会直接取出出该值innodb没有保存表的总行数,如果使用select count(*) from table;就会遍历整个表,消耗相当大,但是在加了wehre       条件后,myisam和innodb处理的方式都一样。
  
  
  表锁

  
  提供行锁,另外,InnoDB表的行锁也不是绝对的,如果在执行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB表同样会锁全表, 例如update table set num=1 where name like "%aaa%"

mysql相关配置参数优化:

• sort-buffer-size/join-buffer-size / read-rnd-buffer-size,4~8MB为宜
• optimizer_switch=“index_condition_pushdown=on,mrr=on,mrr_cost
_based=off,batched_key_access=on”
• tmp-table-size = max-heap-table-size,100MB左右为宜
• log-queries-not-using-indexes & log_throttle_queries_not_using_indexes

 

 

  • 110
    点赞
  • 804
    收藏
    觉得还不错? 一键收藏
  • 21
    评论
1 性能优化 1.1 避免频繁 commit,尤其是把 commit 写在循环体中每次循环都进行commit。 1.2 使用绑定变量,避免常量的直接引用。 示例:以下书写不符合本规范。 INSERT INTO sm_users (user_id, user_name, created_by, creation_date) VALUES (1, 'Tang', -1, SYSDATE); 建议用如下方式操作: DECLARE v_user_id sm_users.user_id%TYPE; v_user_name sm_users_user_name%TYPE; v_created_by sm_users.created_by%TYPE; v_creation_date sm_users.creation_date%TYPE; BEGIN ... INSERT INTO sm_users (user_id, user_name, created_by, creation_date) VALUES (v_user_id, v_user_name, v_created_by, v_creation_date); END; 1.3 Operator 的使用规范  IN  比较容易写及清晰易懂  但效能是比较低的  ORACLE试图将其转换成多个表的连接,如果转换不成功则先执行IN里面的子查询,再查询外层的表记录,如果转换成功则直接采用多个表的连接方式查询。由此可见用IN的SQL至少多了一个转换的过程。一般的SQL都可以转换成功,但对于含有分组统计等方面的SQL就不能转换了。  NOT IN  此操作是强列推荐不使用的,因为不能应用表的索引。  推荐方案:用NOT EXISTS 或(Outer-Join+判断为空)方案代替 例如: SELECT deptno FROM dept WHERE deptno NOT IN(SELECT deptno FROM emp) 建议写成: SELECT deptno FROM dept, emp WHERE dept.deptno = emp.deptno(+) AND emp.deptno IS NULL  <>  永远不会用到索引的  推荐方案:用其它相同功能的操作运算代替,如: a<>0 改为 a>0 or a<0 a<>’’ 改为 a>’’  IS NULL 或IS NOT NULL  一般是不会应用索引的,因为B-tree索引是不索引空值的。  推荐方案:用其它相同功能的操作运算代替,如: a is not null 改为 a>0 或a>’’  不允许字段为空,而用一个default代替空值,如业扩申请中状态区位不允许为空, default为申请。  > 及 <  有索引就会采用索引查找  但有的情况下可以对它进行优化  如一个表有100万记录,一个数值型字段A,30万记录的A=0,30万记录的A=1,39万记录的A=2,1万记录的A=3。那么执行A>2与A>=3的效果就有很大的区别了,因为A>2时ORACLE会先找出为2的记录索引再进行比较,而A>=3时ORACLE则直接找到=3的记录索引。  LIKE  LIKE可以应用通配符查询,里面的通配符组合可能达到几乎是任意的查询,但是如果用得不好则会产生性能上的问题,如LIKE ‘%5400%’ 这种查询不会引用索引,而LIKE ‘X5400%’则会引用范围索引。性能肯定大大提高。  UNION  SQL在运行时先取出数个查询的结果,再用排序空间进行排序删除重复的记录,最后返回结果集,如果表数据量大的话可能会导致用磁盘进行排序。  实际大部分应用中是不会产生重复的记录,推荐采用UNION ALL操作符替代UNION,因为UNION ALL操作只是简单的将两个结果合并后就返回。  Exists 示例:当有 A、B 两个结果集,当结果集 B 很大时,A 较小时,适用 exists,如: SELECT * FROM a WHERE EXISTS(SELECT 1 FROM b WHERE a.COLUMN = b.COLUMN); 当结果集 A 很大时,B 很小时,适用 in,如: SELECT * FROM a WHERE a.COLUMN IN(SELECT b.COLUMN FROM b) 1.4 SQL书写的影响  同一功能同一性能不同写法SQL的影响  Select * from zl_yhjbqk  Select * from dlyx.zl_yhjbqk(带表所有者的前缀)  Select * from DLYX.ZL_YHJBQK(大写表名)  Select * from DLYX.ZL_YHJBQK(中间多了空格)  以上四个SQL在ORACLE分析整理之后产生的结果及执行的时间是一样的,但是从ORACLE共享内存SGA的原理,可以得出ORACLE对每个SQL 都会对其进行一次分析,并且占用共享内存,如果将SQL的字符串及格式写得完全相同则ORACLE只会分析一次,共享内存也只会留下一次的分析结果,这不仅可以减少分析SQL的时间,而且可以减少共享内存重复的信息,ORACLE也可以准确统计SQL的执行频率。  WHERE后面的条件顺序影响  Select * from zl_yhjbqk where dy_dj = '1KV以下' and xh_bz=1  Select * from zl_yhjbqk where xh_bz=1 and dy_dj = '1KV以下'  以上两个SQL中dy_dj(电压等级)及xh_bz(销户标志)两个字段都没进行索引,所以执行的时候都是全表扫描,第一条SQL的dy_dj = '1KV以下'条件在记录集内比率为99%,而xh_bz=1的比率只为0.5%,在进行第一条SQL的时候99%条记录都进行dy_dj及xh_bz的比较,而在进行第二条SQL的时候0.5%条记录都进行dy_dj及xh_bz的比较,以此可以得出第二条SQL的CPU占用率明显比第一条低。  查询表顺序的影响  在FROM后面的表中的列表顺序会对SQL执行性能影响,在没有索引及ORACLE没有对表进行统计分析的情况下ORACLE会按表出现的顺序进行链接,由此因为表的顺序不对会产生十分耗服务器资源的数据交叉。(注:如果对表进行了统计分析,ORACLE会自动先进小表的链接,再进行大表的链接)  对条件字段的一些优化  采用函数处理的字段不能利用索引,如: substr(hbs_bh,1,4)=’5400’,优化处理:hbs_bh like ‘5400%’ trunc(sk_rq)=trunc(sysdate), 优化处理: sk_rq>=trunc(sysdate) and sk_rq<trunc(sysdate+1)  进行了显式或隐式的运算的字段不能进行索引,如: ss_df+20>50,优化处理:ss_df>30 ‘X’||hbs_bh>’X5400021452’,优化处理:hbs_bh>’5400021542’ sk_rq+5=sysdate,优化处理:sk_rq=sysdate-5 hbs_bh=5401002554,优化处理:hbs_bh=’ 5401002554’ 注:此条件对hbs_bh 进行隐式的to_number转换,因为hbs_bh字段是字符型  条件内包括了多个本表的字段运算时不能进行索引,如: ys_df>cx_df,无法进行优化 qc_bh||kh_bh=’5400250000’,优化处理:qc_bh=’5400’ and kh_bh=’250000’  HINT  是在ORACLE产生的SQL分析执行路径不满意的情况下要用到的。它可以对SQL进行以下方面的提示  目标方面的提示:  COST(按成本优化)  RULE(按规则优化)  CHOOSE(缺省)(ORACLE自动选择成本或规则进行优化)  ALL_ROWS(所有的行尽快返回)  FIRST_ROWS(第一行资料尽快返回)  执行方法的提示:  USE_NL(使用NESTED LOOPS方式联合)  USE_MERGE(使用MERGE JOIN方式联合)  USE_HASH(使用HASH JOIN方式联合)  索引提示:  INDEX(TABLE INDEX)(使用提示的表索引进行查询)  其它高级提示(如并行处理等等) 1.5 索引的规则: 建立索引常用的原则如下: 1. 表的主键、外键必须有索引 2. 数据量超过 1000 行的表应该有索引 3. 经常与其它表进行连接的表,在边接字段上应建立索引 4. 经常出现在 where 子句中的字段且过滤性极强的,特别是大表的字段,应该建立索引 5. 索引字段,尽量避免值为 null 6. 复合索引的建立需要仔细分析;尽量考虑用单字段索引代替:  正确选择复合索引中的第一个字段,一般是选择性较好的且在 where 子句中常的字段上。  复合索引的几个字段是否经常同时以and方式出现在where子句中?单字段查询是否极少甚至没有?如果是,则可以建立复合索引;否则考虑单字段索引。  如果复合索引中包含的字段经常单独出现在 where 子句中,则分解为多个单字段索引。  如果复合索引所包含的字段超过 3 个,那么仔细考虑其必要性,考虑减少复合的字段。  如果既有单字段索引,又有这几个字段上的复合索引,一般可以删除复合索引; 7. 频繁 DDL 的表,不要建立太多的索引 8. 删除无用的索引,避免对执行计划造成负面影响 9. 让 SQL 语句用上合理的索引,合理让 SQL 语句使用索引的原则如下:  首先,看是否用上了索引,对于该使用索引而没有用上索引SQL 语句,应该想办法用上索引。  其次,看是否用上正确的索引了,特别复杂的 SQL 语句,当其中 where 子句包含多个带有索引的字段时,更应该注意索引的选择是否合理。错误的索引不仅不会带来性能的提高,相反往往导致性能的降低。  针对如何用上合理的索引,以 Oracle 数据中的例子进行说明:  任何对列的操作都可能导致全表扫描,这里所谓的操作包括数据库函数、计算表达式等等,查询时要尽可能将操作移至等式的右边,甚至去掉函数。  避免不必要的类型转换,要了解“隐藏”的类型转换。  增加查询的范围,限制全范围的搜索。  索引选择性低,但资料分布差异很大时,仍然可以利用索引提高效率。  Oracle 优化器无法用上合理索引的情况下,利用 hint 强制指定索引。  使用复合索引且第一个索引字段没有出现在 where 中时,建议使用 hint 强制。 1.6 索引使用优化  建立Plan_Table CREATE TABLE PLAN_TABLE ( STATEMENT_ID VARCHAR2(30), TIMESTAMP DATE, REMARKS VARCHAR2(80), OPERATION VARCHAR2(30), OPTIONS VARCHAR2(30), OBJECT_NODE VARCHAR2(128), OBJECT_OWNER VARCHAR2(30), OBJECT_NAME VARCHAR2(30), OBJECT_INSTANCE NUMBER(38), OBJECT_TYPE VARCHAR2(30), OPTIMIZER VARCHAR2(255), SEARCH_COLUMNS NUMBER(38), ID NUMBER(38), PARENT_ID NUMBER(38), POSITION NUMBER(38), OTHER LONG )  Syntax 说明: explain plan set statement_id = user_define for select ... 将结果显示 SELECT LPAD(' ', 2 *(LEVEL - 1)) || operation op, options, object_name, POSITION FROM plan_table START WITH ID = 0 AND STATEMENT_ID = user_define CONNECT BY PRIOR ID = parent_id AND STATEMENT_ID = user_define  示例 如要测试下面SQL: SELECT c.short, a.cday, a.card_no, a.qty FROM sales.stockiohis a, sales.product_info b, sales.vendor c WHERE a.card_no = b.card_no AND b.vendorid = c.vendorid AND a.produce_no = '2007090001' AND a.CATEGORY = '10' AND a.iotype = '1' 新增文件:例 d:\mydoc\plan.sql '0001'为user_define为使用者自定义编号 EXPLAIN PLAN SET STATEMENT_ID = '0001' FOR SELECT 'X' FROM sales.stockiohis a ,sales.product_info b ,sales.vendor c WHERE a.card_no = b.card_no AND b.vendorid = c.vendorid AND a.produce_no = '2007090001' AND a.CATEGORY = '10' AND a.iotype = '1' / SET arraysize 1 SET line 100 COLUMN op format a40 COLUMN object_name format a20 COLUMN options format a20 SELECT LPAD(' ', 2 *(LEVEL - 1)) || operation op, options, object_name, POSITION FROM plan_table START WITH ID = 0 AND STATEMENT_ID = '0001' CONNECT BY PRIOR ID = parent_id AND STATEMENT_ID = '0001' / DELETE FROM plan_table WHERE STATEMENT_ID = '0001' / COMMIT / 结果 1.7 避免不必要的排序 说明:对查询结果进行排序会大大的降低系统的性能,group与union都会对数据作排序,要耗费较多的内存,视状况用union all既可,不然有时数据太大又要进行union的排序,会导致Oracle数据库SORT_AREA_SIZE不足发生系统错误。 1.8 对于数字型的Primary Key,建议用序列 sequence 产生。 说明:除非是单据的单号,要求必须是唯一,并且依据流水号不可以跳号,不然在大量交易的表格中,不在乎跳耗时,要取得唯一的Primary Key 建议使用Oracle Sequence这样速度会较快,而且不会有锁定(Lock)的问题。
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值