常用SQL技巧

1.1 检索包含最大和最小值的行

select max(test_score),min(test_score) from student

1.2 巧用rand()/rand(n)提取随机行

select * from table order by rand()

1.3 group by的with rollup子句做统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

create table sales(

year int not null,
country varchar(20) not null,
product varchar(32) not null,
profit int
);


insert into sales values(2004,'china','tnt2004',2001);
insert into sales values(2004,'china','tnt2004',2002);
insert into sales values(2004,'china','tnt2004',2003);
insert into sales values(2005,'china','tnt2005',2004);
insert into sales values(2005,'china','tnt2005',2005);
insert into sales values(2005,'china','tnt2005',2006);
insert into sales values(2005,'china','tnt2005',2007);
insert into sales values(2005,'china','tnt2005',2008);
insert into sales values(2005,'china','tnt2005',2009);
insert into sales values(2006,'china','tnt2006',2010);
insert into sales values(2006,'china','tnt2006',2011);
insert into sales values(2006,'china','tnt2006',2012);


select year,sum(profit) from sales group by year;

select year,sum(profit) from sales group by year with rollup;

当你使用ROLLUP时, 你不能同时使用ORDER BY子句进行结果排序。换言之, ROLLUP和ORDER BY 是互相排斥的LIMIT 用在ROLLUP 后面。

1.4 bit group functions做统计

bit_count:用来计算二进制数中包含1的个数

bit_or:就是对两个二进制数进行或运算

bit_and:就是对两个二进制数进行并运算

SQL优化

通过show status和应用特点了解各种SQL的执行频率。

首先我们了解mysql数据库的一些运行状态如何查询(比如想知道当前mysql运行的时间/一共执行了多少次select/update/delete.. / 当前连接)

show status

常用的:

show status like ‘uptime’ ;

show stauts like ‘com_select’ show status like ‘com_insert’ …类推 update delete

show session或global status like …. 如果你不写session或global,默认是session 会话,指取出当前窗口的执行,如果你想看所有session,从mysql 启动到现在,则应该 global。

show status like ‘connections’;

//显示慢查询次数

show status like ‘slow_queries’;

以下几个参数对Myisam 和Innodb 存储引擎都计数:

  1. Com_select 执行select 操作的次数,一次查询只累加1;

  2. Com_insert 执行insert 操作的次数,对于批量插入的insert 操作,只累加一次;

  3. Com_update 执行update 操作的次数;

  4. Com_delete 执行delete 操作的次数;

以下几个参数是针对Innodb 存储引擎计数的,累加的算法也略有不同:

  1. Innodb_rows_read select 查询返回的行数;

  2. Innodb_rows_inserted 执行Insert 操作插入的行数;

  3. Innodb_rows_updated 执行update 操作更新的行数;

  4. Innodb_rows_deleted 执行delete 操作删除的行数;

对于事务型的应用,通过Com_commit 和Com_rollback 可以了解事务提交和回滚的情况,对于回滚操作非常频繁的数据库,可能意味着应用编写存在问题。

此外,以下几个参数便于我们了解数据库的基本情况:

  1. Connections 试图连接Mysql 服务器的次数

  2. Uptime 服务器工作时间

  3. Slow_queries 慢查询的次数

定位执行效率较低的SQL

可以通过以下两种方式定位执行效率较低的SQL 语句:

  1. 可以通过慢查询日志定位那些执行效率较低的sql 语句,用–log-slowqueries[=file_name]选项启动时,mysqld 写一个包含所有执行时间超过long_query_time 秒的SQL语句的日志文件。可以链接到管理维护中的相关章节。

  2. 慢查询日志在查询结束以后才纪录,所以在应用反映执行效率出现问题的时候查询慢查询日志并不能定位问题,可以使用show processlist 命令查看当前MySQL 在进行的线程,包括线程的状态,是否锁表等等,可以实时的查看SQL 执行情况,同时对一些锁表操作进行优化。

通过explain分析低效SQL的执行计划

select_type: select 类型

A:simple:表示不需要union操作或者不包含子查询的简单select查询。有连接查询时,外层的查询为simple,且只有一个

B:primary:一个需要union操作或者含有子查询的select,位于最外层的单位查询的select_type即为primary。且只有一个

C:union:union连接的两个select查询,第一个查询是dervied派生表,除了第一个表外,第二个以后的表select_type都是union

D:dependent union:与union一样,出现在union 或union all语句中,但是这个查询要受到外部查询的影响

E:union result:包含union的结果集,在union和union all语句中,因为它不需要参与查询,所以id字段为null

F:subquery:除了from字句中包含的子查询外,其他地方出现的子查询都可能是subquery

G:dependent subquery:与dependent union类似,表示这个subquery的查询要受到外部表查询的影响

H:derived:from字句中出现的子查询,也叫做派生表,其他数据库中可能叫做内联视图或嵌套select

table: 输出结果集的表

显示的查询表名,如果查询使用了别名,那么这里显示的是别名,如果不涉及对数据表的操作,那么这显示为null,如果显示为尖括号括起来的就表示这个是临时表,后边的N就是执行计划中的id,表示结果来自于这个查询产生。如果是尖括号括起来的,与类似,也是一个临时表,表示这个结果来自于union查询的id为M,N的结果集。

type: 表示表的连接类型

当表中仅有一行是type的值为system是最佳的连接类型;

当select操作中使用索引进行表连接时type的值为ref;

当select的表连接没有使用索引时,经常会看到type的值为ALL,表示对该表进行了全表扫描,这时需要考虑通过创建索引来提高表连接的效率。

依次从好到差:system,const,eq_ref,ref,fulltext,ref_or_null,unique_subquery,index_subquery,range,index_merge,index,ALL,除了all之外,其他的type都可以使用到索引,除了index_merge之外,其他的type只可以用到一个索引

A:system:表中只有一行数据或者是空表,且只能用于myisam和memory表。如果是Innodb引擎表,type列在这个情况通常都是all或者index

B:const:使用唯一索引或者主键,返回记录一定是1行记录的等值where条件时,通常type是const。其他数据库也叫做唯一索引扫描

C:eq_ref:出现在要连接过个表的查询计划中,驱动表只返回一行数据,且这行数据是第二个表的主键或者唯一索引,且必须为not null,唯一索引和主键是多列时,只有所有的列都用作比较时才会出现eq_ref

D:ref:不像eq_ref那样要求连接顺序,也没有主键和唯一索引的要求,只要使用相等条件检索时就可能出现,常见与辅助索引的等值查找。或者多列主键、唯一索引中,使用第一个列之外的列作为等值查找也会出现,总之,返回数据不唯一的等值查找就可能出现。

E:fulltext:全文索引检索,要注意,全文索引的优先级很高,若全文索引和普通索引同时存在时,mysql不管代价,优先选择使用全文索引

F:ref_or_null:与ref方法类似,只是增加了null值的比较。实际用的不多。

G:unique_subquery:用于where中的in形式子查询,子查询返回不重复值唯一值

H:index_subquery:用于in形式子查询使用到了辅助索引或者in常数列表,子查询可能返回重复值,可以使用索引将子查询去重。

I:range:索引范围扫描,常见于使用>,<,is null,between ,in ,like等运算符的查询中。

J:index_merge:表示查询使用了两个以上的索引,最后取交集或者并集,常见and ,or的条件使用了不同的索引,官方排序这个在ref_or_null之后,但是实际上由于要读取所个索引,性能可能大部分时间都不如range

K:index:索引全表扫描,把索引从头到尾扫一遍,常见于使用索引列就可以处理不需要读取数据文件的查询、可以使用索引排序或者分组的查询。

L:all:这个就是全表扫描数据文件,然后再在server层进行过滤返回符合要求的记录。

possible_keys: 表示查询时,可以使用的索引列.

key: 表示使用的索引

key_len: 索引长度

ref

如果是使用的常数等值查询,这里会显示const,如果是连接查询,被驱动表的执行计划这里会显示驱动表的关联字段,如果是条件使用了表达式或者函数,或者条件列发生了内部隐式转换,这里可能显示为func

rows: 扫描范围

Extra: 执行情况的说明和描述

A:distinct:在select部分使用了distinc关键字

B:no tables used:不带from字句的查询或者From dual查询

C:使用not in()形式子查询或not exists运算符的连接查询,这种叫做反连接。即,一般连接查询是先查询内表,再查询外表,反连接就是先查询外表,再查询内表。

D:using filesort:排序时无法使用到索引时,就会出现这个。常见于order by和group by语句中

E:using index:查询时不需要回表查询,直接通过索引就可以获取查询的数据。

F:using join buffer(block nested loop),using join buffer(batched key accss):5.6.x之后的版本优化关联查询的BNL,BKA特性。主要是减少内表的循环数量以及比较顺序地扫描查询。

G:using sort_union,using_union,using intersect,using sort_intersection:

using intersect:表示使用and的各个索引的条件时,该信息表示是从处理结果获取交集

using union:表示使用or连接各个使用索引的条件时,该信息表示从处理结果获取并集

using sort_union和using sort_intersection:与前面两个对应的类似,只是他们是出现在用and和or查询信息量大时,先查询主键,然后进行排序合并后,才能读取记录并返回。

H:using temporary:表示使用了临时表存储中间结果。临时表可以是内存临时表和磁盘临时表,执行计划中看不出来,需要查看status变量,used_tmp_table,used_tmp_disk_table才能看出来。

I:using where:表示存储引擎返回的记录并不是所有的都满足查询条件,需要在server层进行过滤。查询条件中分为限制条件和检查条件,5.6之前,存储引擎只能根据限制条件扫描数据并返回,然后server层根据检查条件进行过滤再返回真正符合查询的数据。5.6.x之后支持ICP特性,可以把检查条件也下推到存储引擎层,不符合检查条件和限制条件的数据,直接不读取,这样就大大减少了存储引擎扫描的记录数量。extra列显示using index condition

J:firstmatch(tb_name):5.6.x开始引入的优化子查询的新特性之一,常见于where字句含有in()类型的子查询。如果内表的数据量比较大,就可能出现这个

K:loosescan(m..n):5.6.x之后引入的优化子查询的新特性之一,在in()类型的子查询中,子查询返回的可能有重复记录时,就可能出现这个

除了这些之外,还有很多查询数据字典库,执行计划过程中就发现不可能存在结果的一些提示信息

确定问题并采取响应的优化措施

索引的存储分类

myisam 表的数据文件和索引文件是自动分开的;innodb 的数据和索引是存储在
同一个表空间里面,但可以有多个文件组成。
创建索引语法如下:

1
2
3
4
5
6

CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name
[USING index_type]
ON tbl_name (index_col_name,...)
index_col_name:
col_name [(length)] [ASC | DESC]

索引的存储类型目前只有两种(btree 和hash),具体和表的模式相关:

myisam btree

innodb btree

memory/heap hash,btree

mysql 目前不支持函数索引,只能对列的前一部分(length)进行索引,例:

create index ind_test on table1(name(5))

对于char 和varchar 列,使用前缀索引将大大节省空间。

MySQL如何使用索引

索引用于快速找出在某个列中有一特定值的行。对相关列使用索引是提高SELECT 操作性能的最佳途径。

查询要使用索引最主要的条件是查询条件中需要使用索引关键字,如果是多列索引,那么只有查询条件使用了多列关键字最左边的前缀时,才可以使用索引,否则将不能使用索引。

下列情况下,Mysql 不会使用已有的索引:

1.如果mysql 估计使用索引比全表扫描更慢,则不使用索引。例如:如果key_part1均匀分布在1 和100 之间,下列查询中使用索引就不是很好:

SELECT * FROM table_name where key_part1 > 1 and key_part1 < 90

2.如果使用heap 表并且where 条件中不用=索引列,其他> 、<、>=、<=均不使用索引;

3.如果不是索引列的第一部分;

4.如果like 是以%开始;

5.对where 后边条件为字符串的一定要加引号,字符串如果为数字mysql 会自动转为字符串,但是不使用索引。

查看索引的使用情况

如果索引正在工作,Handler_read_key 的值将很高,这个值代表了一个行被索引值读的次数,很低的值表明增加索引得到的性能改善不高,因为索引并不经常使用。

Handler_read_rnd_next 的值高则意味着查询运行低效,并且应该建立索引补救。这个值的含义是在数据文件中读下一行的请求数。如果你正进行大量的表扫描,该值较高。通常说明表索引不正确或写入的查询没有利用索引。

show status like 'Handler_read%';

定期分析表

ANALYZE TABLE

语法:

ANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...

本语句用于分析和存储表的关键字分布。在分析期间,使用一个读取锁定对表进行锁定。这对于MyISAM, BDB 和InnoDB 表有作用。对于MyISAM 表,本语句与使用myisamchk -a 相当。

CHECK TABLE

语法:

CHECK TABLE tbl_name [, tbl_name] ... [option] ...

option = {QUICK | FAST | MEDIUM | EXTENDED | CHANGED}

检查一个或多个表是否有错误。CHECK TABLE 对MyISAM 和InnoDB 表有作用。对于MyISAM 表,关键字统计数据被更新。

CHECK TABLE 也可以检查视图是否有错误,比如在视图定义中被引用的表已不存在。

CHECKSUM TABLE

语法:

CHECKSUM TABLE tbl_name [, tbl_name] ... [ QUICK | EXTENDED ]

报告一个表校验和。

使用optimize table

OPTIMIZE TABLE

语法:

OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...

如果已经删除了表的一大部分,或者如果您已经对含有可变长度行的表(含有VARCHAR, BLOB 或TEXT 列的表)进行了很多更改,则应使用OPTIMIZE TABLE。被删除的记录被保持在链接清单中,后续的INSERT 操作会重新使用旧的记录位置。您可以使用OPTIMIZE TABLE 来重新利用未使用的空间,并整理数据文件的碎片。

OPTIMIZE TABLE 只对MyISAM, BDB 和InnoDB 表起作用。

大批量插入数据

对于Myisam 类型的表,可以通过以下方式快速的导入大量的数据。

1
2
3
ALTER TABLE tblname DISABLE KEYS;
loading the data
ALTER TABLE tblname ENABLE KEYS;

这两个命令用来打开或者关闭Myisam 表非唯一索引的更新。在导入大量的数据到一个非空的Myisam 表时,通过设置这两个命令,可以提高导入的效率。对于导入大量数据到一个空的Myisam 表,默认就是先导入数据然后才创建索引的,所以不用进行设置。

而对于Innodb 类型的表,这种方式并不能提高导入数据的效率。对于Innodb 类型的表,我们有以下几种方式可以提高导入的效率:

a. 因为Innodb 类型的表是按照主键的顺序保存的,所以将导入的数据按照主键的顺序排列,可以有效的提高导入数据的效率。如果Innodb 表没有主键,那么系统会默认创建一个内部列作为主键,所以如果可以给表创建一个主键,将可以利用这个优势提高导入数据的效率。

b. 在导入数据前执行SET UNIQUE_CHECKS=0,关闭唯一性校验,在导入结束后执行SETUNIQUE_CHECKS=1,恢复唯一性校验,可以提高导入的效率。

c. 如果应用使用自动提交的方式,建议在导入前执行SET AUTOCOMMIT=0,关闭自动提交,导入结束后再执行SET AUTOCOMMIT=1,打开自动提交,也可以提高导入的效率。

优化insert语句

如果你同时从同一客户插入很多行,使用多个值表的INSERT 语句。这比使用分开INSERT 语句快(在一些情况中几倍)。

Insert into test values(1,2),(1,3),(1,4)…

如果你从不同客户插入很多行,能通过使用INSERT DELAYED 语句得到更高的速度。Delayed 的含义是让insert 语句马上执行,其实数据都被放在内存的队列中,并没有真正写入磁盘;这比每条语句分别插入要快的多;LOW_PRIORITY 刚好相反,在所有其他用户对表的读写完后才进行插入。

Insert delayed into test values(1,2),(1,3),(1,4)…

Insert low_priority into test values(1,2),(1,3),(1,4)…

将索引文件和数据文件分在不同的磁盘上存放(利用建表中的选项)。

如果进行批量插入,可以增加bulk_insert_buffer_size 变量值的方法来提高速度,但是,这只能对myisam 表使用。

在my.cnf中添加如下语句,将insert语句的长度设为最大。

Max_allowed_packet=1M

Net_buffer_length=2k

当从一个文本文件装载一个表时,使用LOAD DATA INFILE。这通常比使用很多INSERT 语句快20 倍。

1
2
3
4
5
6
7
8
9
10

//数据

1669,Jim,Smith
337,Mary,Jones
2005,Linda,Black

示例语句:

load data infile "/data/mysql/e.sql" into table tablename fields terminated by ',';

根据应用情况使用replace 语句代替insert。replace low_priority into ta values(1,2);
replace具备替换拥有唯一索引或者主键索引重复数据的能力,也就是如果使用replaceinto插入的数据的唯一索引或者主键索引与之前的数据有重复的情况,将会删除原先的数据,然后再进行添加。

根据应用情况使用ignore 关键字忽略重复记录。
insert ignore into
当插入数据时,如出现错误时,如重复数据,将不返回错误,只以警告形式返回。所以使用ignore请确保语句本身没有问题,否则也会被忽略掉。例如:INSERT IGNORE INTO books (name) VALUES ('MySQL Manual')

优化group by语句

默认情况下,MySQL 排序所有GROUP BY col1,col2,….。查询的方法如同在查询中指定ORDER BY col1,col2,…。如果显式包括一个包含相同的列的ORDER BY子句,MySQL 可以毫不减速地对它进行优化,尽管仍然进行排序。

如果查询包括GROUP BY 但你想要避免排序结果的消耗,你可以指定ORDER BY NULL禁止排序。

例如:

1
2
3

INSERT INTO foo
SELECT a, COUNT(*) FROM bar GROUP BY a ORDER BY NULL;

优化order by语句

在某些情况中,MySQL 可以使用一个索引来满足ORDER BY 子句,而不需要额外的排序。where 条件和order by 使用相同的索引,并且order by的顺序和索引顺序相同,并且order by的字段都是升序或者都是降序。

1
2
3
4
5
6
7
8
9
10
11
12
13

例如:下列sql 可以使用索引:
SELECT * FROM t1 ORDER BY key_part1,key_part2,... ;
SELECT * FROM t1 WHERE key_part1=1 ORDER BY key_part1 DESC, key_part2
DESC;
SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 DESC;
但是以下情况不使用索引:
SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 ASC;
--order by 的字段混合ASC 和DESC
SELECT * FROM t1 WHERE key2=constant ORDER BY key1;
--用于查询行的关键字与ORDER BY 中所使用的不相同
SELECT * FROM t1 ORDER BY key1, key2;
--对不同的关键字使用ORDER BY。

优化join语句

Mysql4.1 开始支持SQL 的子查询。这个技术可以使用SELECT 语句来创建一个单列的查询结果,然后把这个结果作为过滤条件用在另一个查询中。使用子查询可以一次性的完成很多逻辑上需要多个步骤才能完成的SQL 操作,同时也可以避免事务或者表锁死,并且写起来也很容易。但是,有些情况下,子查询可以被更有效率的连接(JOIN)..替代。

假设我们要将所有没有订单记录的用户取出来,可以用下面这个查询完成:

SELECT * FROM customerinfo WHERE CustomerID NOT in (SELECT CustomerIDFROM salesinfo)

如果使用连接(JOIN).. 来完成这个查询工作,速度将会快很多。尤其是当salesinfo表中对CustomerID 建有索引的话,性能将会更好,查询如下:

SELECT * FROM customerinfo LEFT JOIN salesinfoON customerinfo.CustomerID=salesinfo.CustomerID WHERE salesinfo.CustomerID IS NULL

连接(JOIN).. 之所以更有效率一些,是因为MySQL 不需要在内存中创建临时表来完成这个逻辑上的需要两个步骤的查询工作。

mysql如何优化or条件

对于or 子句,如果要利用索引,则or 之间的每个条件列都必须用到索引;如果没有索引,则应该考虑增加索引。

查询优先还是更新优先

MySQL 还允许改变语句调度的优先级,它可以使来自多个客户端的查询更好地协作,这样单个客户端就不会由于锁定而等待很长时间。改变优先级还可以确保特定类型的查询被处理得更快。

我们首先应该确定应用的类型,判断应用是以查询为主还是以更新为主的,是确保查询效率还是确保更新的效率,决定是查询优先还是更新优先。

下面我们提到的改变调度策略的方法主要是针对Myisam 存储引擎的,对于Innodb 存储引擎,语句的执行是由获得行锁的顺序决定的。

MySQL 的默认的调度策略可用总结如下:

  1. 写入操作优先于读取操作。

  2. 对某张数据表的写入操作某一时刻只能发生一次,写入请求按照它们到达的次序来处理。

  3. 对某张数据表的多个读取操作可以同时地进行。

MySQL 提供了几个语句调节符,允许你修改它的调度策略:

  1. LOW_PRIORITY关键字应用于DELETE、INSERT、LOAD DATA、REPLACE和UPDATE。

  2. HIGH_PRIORITY关键字应用于SELECT和INSERT语句。

  3. DELAYED关键字应用于INSERT和REPLACE语句。

如果写入操作是一个LOW_PRIORITY(低优先级)请求,那么系统就不会认为它的优先级高于读取操作。在这种情况下,如果写入者在等待的时候,第二个读取者到达了,那么就允许第二个读取者插到写入者之前。只有在没有其它的读取者的时候,才允许写入者开始操作。这种调度修改可能存在LOW_PRIORITY 写入操作永远被阻塞的情况。

SELECT 查询的HIGH_PRIORITY(高优先级)关键字也类似。它允许SELECT 插入正在等待的写入操作之前,即使在正常情况下写入操作的优先级更高。另外一种影响是,高优先级的SELECT 在正常的SELECT 语句之前执行,因为这些语句会被写入操作阻塞。

如果你希望所有支持LOW_PRIORITY 选项的语句都默认地按照低优先级来处理,那么请使用–low-priority-updates 选项来启动服务器。通过使用INSERT HIGH_PRIORITY 来把INSERT 语句提高到正常的写入优先级,可以消除该选项对单个INSERT 语句的影响。

使用SQL提示

SELECT SQL_BUFFER_RESULTS ...

将强制MySQL 生成一个临时结果集。只要所有临时结果集生成后,所有表上的锁定均被释放。这能在遇到表锁定问题时或要花很长时间将结果传给客户端时有所帮助。

当处理一个会让客户端耗费点时间才能处理的大结果集时, 可以考虑使用SQL_BUFFER_RESULT 提示字。这样可以告诉MySQL 将结果集保存在一个临时表中,这样可以尽早的释放各种锁。

USE INDEX

在你查询语句中表名的后面,添加USE INDEX 来提供你希望MySQ 去参考的索引列表,就可以让MySQL 不再考虑其他可用的索引。

Eg:SELECT * FROM mytable USE INDEX (mod_time, name) ...

IGNORE INDEX

如果你只是单纯的想让MySQL 忽略一个或者多个索引,可以使用IGNORE INDEX 作为Hint。

Eg:SELECT * FROM mytale IGNORE INDEX (priority) ...

FORCE INDEX

为强制MySQL 使用一个特定的索引,可在查询中使用FORCE INDEX 作为Hint。

Eg:SELECT * FROM mytable FORCE INDEX (mod_time) ...

其他优化措施

  1. 使用持久的连接数据库以避免连接开销。

  2. 经常检查所有查询确实使用了必要的索引。

  3. 避免在频繁更新的表上执行复杂的SELECT 查询,以避免与锁定表有关的由于读、写冲突发生的问题。

  4. 对于没有删除的行操作的MyISAM 表,插入操作和查询操作可以并行进行,因为没有删除操作的表查询期间不会阻塞插入操作.对于确实需要执行删除操作的表,尽量在空闲时间进行批量删除操作,避免阻塞其他操作。

  5. 充分利用列有默认值的事实。只有当插入的值不同于默认值时,才明确地插入值。这减少MySQL 需要做的语法分析从而提高插入速度。

  6. 对经常访问的可以重构的数据使用内存表,可以显著提高访问的效率。

  7. 通过复制可以提高某些操作的性能。可以在复制服务器中分布客户的检索以均分负载。为了防止备份期间对应用的影响,可以在复制服务器上执行备份操作。

  8. 表的字段尽量不使用自增长变量,在高并发情况下该字段的自增可能对效率有比较大的影响,推荐通过应用来实现字段的自增长。