Andy Niu �����ĵ�

Andy Niu

Andy Niu Help  1.0.0.0
优化

变量

 in与exists的区别
 
 count有关
 
 查询缓存
 
 mysql千万级大数据SQL查询优化30条经验
 

详细描述

变量说明

count有关
1、count有两个作用:统计某个字段有值的记录数;统计结果集的记录数。
2、count括号内的表达式不为null,就是统计结果集的记录数。也就是说,count(1),count(*),count(100),count('aa') 
    都是等价的,对于说法,count(1) 是第一个字段,count(*)展开成所有的列,都是错误的。
    注:括号内的表达式为null,即count(null)返回 0
3、count(*) 统计结果集的记录数,也就是总的行数,而主键是不允许为null的,因此主键取值的个数也就是行数。
    count(*) 也就可以认为对主键扫描,与count(key)一样,而innodb肯定是有主键索引的。
4、因此,count(*) 与count(key)的效率基本一样。而对于一般的字段,count(col) 效率要差很多。
    但是这要根据实际的需求,如果是查询总的行数,使用count(*),如果查询某一个字段有值的个数,使用count(col)
5、有些情况下,不要求精确值,只需要记录的近似值。这个时候可以使用查询执行计划,如下:
    desc select count(*) from student;
    效率非常高,返回扫描的行数是个近似值。
6、查询执行计划返回的信息记录在information_schema.tables,查询tables也可以得到行数,如下:
    select table_name,table_rows from information_schema.tables where table_name='student';
7、特别注意:对于MyISAM引擎,select count(*) from student; 不带where,查询效率非常高,原因是:MyISAM引擎记录了总的行数,
    因此直接返回。根据这个特点,考虑id<5的记录很少,对于 select count(*) from student where id >5; 可以转化为
    select count(*) from student;减去 select count(*) from student where id <=5; 
    这样大大地减少了扫描的行数。
参见
in与exists的区别
1、使用5.6版本,准备表和存储过程
    mysql> create table a(ID int,NAME varchar(64));
    Query OK, 0 rows affected
    
    mysql> create table b(ID int,NAME varchar(64));
    Query OK, 0 rows affected
    
    BEGIN
    
    declare vI int default 0;
        
    start transaction;
    while(vI < 100000) do 
        insert into a(id,name) values(vI,concat('Andy',vI));
        set vI= vI+1;
    end while;
    commit;
            
    END
2、添加10万条记录,下面两个查询是等价的,大概耗时不到0.1秒
    select sql_no_cache * from a where name ='Andy100' or name ='Andy200';
    select sql_no_cache * from a where name in ('Andy100','Andy200');
3、对name加上索引,create index index_a_name on a(name); 上面的两个查询耗时不到0.01秒
    查看执行计划,可以看到使用了a表上面的索引index_a_name,如下:
    mysql> desc select sql_no_cache * from a where name in ('Andy100','Andy200');
    +----+-------------+-------+-------+---------------+--------------+---------+------+------+-----------------------+
    | id | select_type | table | type  | possible_keys | key          | key_len | ref  | rows | Extra                 |
    +----+-------------+-------+-------+---------------+--------------+---------+------+------+-----------------------+
    |  1 | SIMPLE      | a     | range | index_a_name  | index_a_name | 195     | NULL |    2 | Using index condition |
    +----+-------------+-------+-------+---------------+--------------+---------+------+------+-----------------------+
    1 row in set
4、理解in的原理,in的执行流程如下:
    对于in后面的集合,遍历,对于每一个元素,使用索引index_a_name 定位到记录,因此很快。
5、但是特别注意的是:对于not in没有办法执行上面的流程,因为没有办法遍历in后面的集合
    只能先遍历表a的记录,检查不在in的集合中,因此没有办法使用索引。
    执行计划如下:
    mysql> desc select sql_no_cache * from a where name not in ('Andy100','Andy200');
    +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+
    | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows  | Extra       |
    +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+
    |  1 | SIMPLE      | a     | ALL  | index_a_name  | NULL | NULL    | NULL | 97617 | Using where |
    +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+
    1 row in set
    也就是说,对于in,使用表a的索引。
6、现在考虑,a,b两个表都不使用索引,分别加上10万条记录,1千条记录,对于下面的操作:
    select sql_no_cache * from a where a.name in (select b.name from b);      // 15秒
    select sql_no_cache * from a where a.name not in (select b.name from b);  // 0.5秒
    这是为什么?
7、查看执行计划,使用desc extended + show warnings,如下:
    mysql> desc extended select sql_no_cache * from a where name in (select name from b);
    +----+--------------+-------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+
    | id | select_type  | table       | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra                                              |
    +----+--------------+-------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+
    |  1 | SIMPLE       | <subquery2> | ALL  | NULL          | NULL | NULL    | NULL | NULL  |        0 | NULL                                               |
    |  1 | SIMPLE       | a           | ALL  | NULL          | NULL | NULL    | NULL | 97617 |      100 | Using where; Using join buffer (Block Nested Loop) |
    |  2 | MATERIALIZED | b           | ALL  | NULL          | NULL | NULL    | NULL |  1000 |      100 | NULL                                               |
    +----+--------------+-------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+
    3 rows in set
    
    mysql> show warnings;
    +-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    | Level | Code | Message                                                                                                                                                                        |
    +-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    | Note  | 1003 | /* select#1 */ select sql_no_cache `niu1`.`a`.`ID` AS `ID`,`niu1`.`a`.`NAME` AS `NAME` from `niu1`.`a` semi join (`niu1`.`b`) where (`niu1`.`a`.`NAME` = `<subquery2>`.`name`) |
    +-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    1 row in set
    
    mysql> desc extended select sql_no_cache * from a where name not in (select name from b);
    +----+-------------+-------+------+---------------+------+---------+------+-------+----------+-------------+
    | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra       |
    +----+-------------+-------+------+---------------+------+---------+------+-------+----------+-------------+
    |  1 | PRIMARY     | a     | ALL  | NULL          | NULL | NULL    | NULL | 97617 |      100 | Using where |
    |  2 | SUBQUERY    | b     | ALL  | NULL          | NULL | NULL    | NULL |  1000 |      100 | NULL        |
    +----+-------------+-------+------+---------------+------+---------+------+-------+----------+-------------+
    2 rows in set
    
    mysql> show warnings;
    +-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    | Level | Code | Message                                                                                                                                                                                                                                                                                                                                                                                                            |
    +-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    | Note  | 1003 | /* select#1 */ select sql_no_cache `niu1`.`a`.`ID` AS `ID`,`niu1`.`a`.`NAME` AS `NAME` from `niu1`.`a` where (not(<in_optimizer>(`niu1`.`a`.`NAME`,`niu1`.`a`.`NAME` in ( <materialize> (/* select#2 */ select `niu1`.`b`.`NAME` from `niu1`.`b` where 1 having 1 ), <primary_index_lookup>(`niu1`.`a`.`NAME` in <temporary table> on <auto_key> where ((`niu1`.`a`.`NAME` = `materialized-subquery`.`name`))))))) |
    +-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    1 row in set
    原因找到了,也就是对于not in, mysql使用了<in_optimizer>优化
8、对表a的name字段加上索引,查询时间如下:
    select sql_no_cache * from a where a.name in (select b.name from b);      // 0.015秒
    select sql_no_cache * from a where a.name not in (select b.name from b);  // 0.5秒
    也就是说,对表a的name字段加上索引,大幅度提高效率,性能提高了1000倍。
    查看执行计划,如下:
    mysql> desc extended select sql_no_cache * from a where name in (select name from b);
    +----+-------------+-------+------+---------------+--------------+---------+-------------+------+----------+------------------------------+
    | id | select_type | table | type | possible_keys | key          | key_len | ref         | rows | filtered | Extra                        |
    +----+-------------+-------+------+---------------+--------------+---------+-------------+------+----------+------------------------------+
    |  1 | SIMPLE      | b     | ALL  | NULL          | NULL         | NULL    | NULL        | 1000 |      100 | Using where; Start temporary |
    |  1 | SIMPLE      | a     | ref  | index_a_name  | index_a_name | 195     | niu1.b.NAME |    1 |      100 | End temporary                |
    +----+-------------+-------+------+---------------+--------------+---------+-------------+------+----------+------------------------------+
    2 rows in set
    
    mysql> show warnings;
    +-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    | Level | Code | Message                                                                                                                                                                     |
    +-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    | Note  | 1003 | /* select#1 */ select sql_no_cache `niu1`.`a`.`ID` AS `ID`,`niu1`.`a`.`NAME` AS `NAME` from `niu1`.`a` semi join (`niu1`.`b`) where (`niu1`.`a`.`NAME` = `niu1`.`b`.`NAME`) |
    +-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    1 row in set
9、现在考虑使用exists,如下:
    select sql_no_cache * from a where exists (select 1 from b where b.name=a.name); // 60秒
    执行计划,如下:
    mysql> desc extended select sql_no_cache * from a where exists (select 1 from b where b.name=a.name);
    +----+--------------------+-------+------+---------------+------+---------+------+-------+----------+-------------+
    | id | select_type        | table | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra       |
    +----+--------------------+-------+------+---------------+------+---------+------+-------+----------+-------------+
    |  1 | PRIMARY            | a     | ALL  | NULL          | NULL | NULL    | NULL | 97617 |      100 | Using where |
    |  2 | DEPENDENT SUBQUERY | b     | ALL  | NULL          | NULL | NULL    | NULL |  1000 |      100 | Using where |
    +----+--------------------+-------+------+---------------+------+---------+------+-------+----------+-------------+
    2 rows in set
10、理解exists的原理,exists的执行流程如下:
    遍历表a的记录,把字段name拿到exists后面的查询中,检查是否满足条件。
    要解决问题,需要在表b上,建立索引。
11、建立索引之后,查询耗时1秒,执行计划如下:
    mysql> desc extended select sql_no_cache * from a where exists (select 1 from b where b.name=a.name);
    +----+--------------------+-------+------+---------------+--------------+---------+-------------+-------+----------+-------------+
    | id | select_type        | table | type | possible_keys | key          | key_len | ref         | rows  | filtered | Extra       |
    +----+--------------------+-------+------+---------------+--------------+---------+-------------+-------+----------+-------------+
    |  1 | PRIMARY            | a     | ALL  | NULL          | NULL         | NULL    | NULL        | 97617 |      100 | Using where |
    |  2 | DEPENDENT SUBQUERY | b     | ref  | index_b_name  | index_b_name | 195     | niu1.a.NAME |     1 |      100 | Using index |
    +----+--------------------+-------+------+---------------+--------------+---------+-------------+-------+----------+-------------+
    2 rows in set
12、总结in与exists,如下:
    a、in的执行流程:对于in后面的集合,遍历每一个元素,去和前面表的字段比较,因此速度取决于前面表的字段是否有索引。
    b、exists的执行流程:遍历前面表的记录,把字段拿到exists后面表的查询中,检查是否满足条件,因此速度取决于后面表的字段是否有索引。
        既然是检查是否满足条件,不关心后面的select内容,可以直接是select 1
    c、也就是说,in使用前面表的索引,exists使用后面表的索引。
        not in不能使用前面表的索引,因为后面的集合是不确定的,相反只能先遍历前面表的记录
        不同的是,not exists也可以使用后面的索引。
    d、现在比较下面的两种查询方式:
        select sql_no_cache * from a where name in (select name from b);
        select sql_no_cache * from a where exists (select 1 from b where b.name=a.name);
        表a的记录个数是10万,表b的记录个数是1千。
        in相当于使用了1千次10万记录的索引
        exists相当于使用了10万次1千记录的索引
        我们知道,索引是使用B+tree(一种排序多叉树),1千记录的索引和10万记录的索引,单次查询的差别不是很大,
        (可以想象二叉树是log2的关系,多叉树是是logn的关系),速度主要取决于查询索引的次数。
        因此,肯定是exists的速度慢很多。
    e、根据上面的推论,使用exists,反过来查询速度就很快,
        select sql_no_cache * from b where exists (select 1 from a where a.name=b.name);
        因为使用了1千次10万记录的索引
mysql千万级大数据SQL查询优化30条经验
1、对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
2、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,
    如:select id from t where num is null可以在num上设置默认值0,
    确保表中num列没有null值,然后这样查询:select id from t where num=0
3、应尽量避免在 where 子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描。
4、应尽量避免在 where 子句中使用or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,
    如:select id from t where num=10 or num=20
    可以这样查询:select id from t where num=10 union all select id from t where num=20
5、in 和 not in 也要慎用,否则会导致全表扫描,如:select id from t where num in(1,2,3) 对于连续的数值,
    能用 between 就不要用in了:select id from t where num between 1 and 3
6、下面的查询也将导致全表扫描:select id from t where name like '李%'若要提高效率,可以考虑全文检索。
7、如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,
    但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。
    然 而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。
    如下面语句将进行全表扫描:select id from t where num=@num
    可以改为强制查询使用索引:select id from t with(index(索引名)) where num=@num
8、应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。
    如:select id from t where num/2=100应改为:select id from t where num=100*2
9、应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。
    如:select id from t where substring(name,1,3)='abc' ,name以abc开头的id 
    应改为: select id from t where name like 'abc%'
10、不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
11、在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,   否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
12、不要写一些没有意义的查询,如需要生成一个空表结构:select col1,col2 into #t from t where 1=0
    这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:create table #t(...)
13、很多时候用 exists 代替 in 是一个好的选择:select num from a where num in(select num from b)
    用下面的语句替换:select num from a where exists(select 1 from b where num=a.num)
14、并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,
    SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,
    那么即使在sex上建了索引也对查询效率起不了作用。
15、索引并不是越多越好,索引固然可 以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,
    因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。
    一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
16、应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,
    一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。
    若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
17、尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。
    这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
18、尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,
    其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
19、任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
20、尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
21、避免频繁创建和删除临时表,以减少系统表资源的消耗。
22、临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。
    但是,对于一次性事件,最好使用导出表。
23、在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,
    避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
24、如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,
    然后 drop table ,这样可以避免系统表的较长时间锁定。
25、尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
26、使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
27、与临时表一样,游标并不是不可使 用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,
    尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。
    如果开发时 间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
28、在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。
    无需在执行存储过程和触发器的每个语句后向客户端发送DONE_IN_PROC 消息。
29、尽量避免大事务操作,提高系统并发能力。
30、尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
查询缓存
1、从Mysql4.0开始,支持查询缓存,把查询结果记录下来,遇到同样的查询直接返回,提高效率。
2、query_cache_type 查询缓存是否开启
    select @@query_cache_type;
    set @@query_cache_type=on;
3、have_query_cache 已经可用
    select @@have_query_cache;      // 变量是只读的
4、query_cache_size 缓存大小
    query_cache_size 只有全局作用域,没有会话作用域。而且设置太小不起作用,单位是字节,如下:
    mysql> set @@global.query_cache_size=10000;
    Query OK, 0 rows affected
    
    mysql> select @@global.query_cache_size;
    +---------------------------+
    | @@global.query_cache_size |
    +---------------------------+
    |                         0 |
    +---------------------------+
    1 row in set
    
    mysql> set @@global.query_cache_size=1000000;
    Query OK, 0 rows affected
    
    mysql> select @@global.query_cache_size;
    +---------------------------+
    | @@global.query_cache_size |
    +---------------------------+
    |                    999424 |
    +---------------------------+
    1 row in set
5、query_cache_limit 控制查询结果的最大值,也就是说,查询结果超过一定大小,不缓存。
    query_cache_limit只有全局作用域。
6、show status like 'qcache%';       // 这是状态变量,也就是mysql运行过程中的状态
    记录查询缓存的相关信息,其中Qcache_queries_in_cache 是缓存结果的个数。
7、考虑下面的需求,在查询性能测试的时候,不希望有查询缓存,否则结果没有意义。怎么解决这个问题?
    a、设置会话query_cache_type=off; 注意是会话query_cache_type,如果是全局query_cache_type,重连mysql才会起作用。
    b、每次查询的时候,使用reset query cache,清除缓存信息,Qcache_queries_in_cache个数为0
    c、使用sql_no_cache,告诉mysql不要缓存查询结果,Qcache_queries_in_cache个数不会增加,如下:
        select sql_no_cache count(*) from student;
8、注意:Mysql只对查询和非常简单的存储过程(只包含简单的select),才进行缓存。比如:
    // 进行缓存
    BEGIN
        select count(*) from student;
    END
    
    // 不进行缓存
    BEGIN
    declare i int default 0;
    while (i<1) do
        select count(*) from student;
        set i = i+1;
    end while;
    END
9、某个操作会导致缓存中的记录无效,考虑下面的情况,
    select count(*) from student;
    delete from student where id = 100;
    select count(*) from student;
    这种情况,Mysql执行select count(*) from student;缓存增加一条记录,当执行 delete from student where id = 100;之后,
    Mysql认为缓存记录select count(*) from student; 无效,删除这条记录,Qcache_queries_in_cache个数减1,下一次的查询
    select count(*) from student; 重新执行一次。
10、注意:上面缓存的测试使用mysql客户端进行的,使用Navicat的时候,也要在新建查询中,
    先执行一下set @@session.query_cache_type=off;
参见
Copyright (c) 2015~2016, Andy Niu @All rights reserved. By Andy Niu Edit.