分类: linux

  • MYSQL 实例CPU超过100%的分析

    关于云数据库实例cpu 超过100%,通常这种情况都是由于sql 性能问题导致的,下面我用一则案例来分析:

    用户实例xxx反馈cpu 超过100%,实例偶尔出现卡住的现象

    1.原理:cpu 消耗过大通常情况下都是有慢sql 造成的,这里的慢sql 包括全表扫描,扫描数据量过大,内存排序,磁盘排序,锁争用等待等;

    2.表现现象:sql 执行状态为:sending data,Copying to tmp table,Copying to tmp table on disk,Sorting result,locked;

    3.解决方法:用户可以登录到云数据,通过show processlist查看当前正在执行的sql,当执行完show processlist后出现大量的语句,通常其状态出现sending data,Copying to tmp table,Copying to tmp table on disk,Sorting result, Using filesort 都是sql有性能问题;

    A.sending data表示:sql正在从表中查询数据,如果查询条件没有适当的索引,则会导致sql执行时间过长;

    B.Copying to tmp table on disk:出现这种状态,通常情况下是由于临时结果集太大,超过了数据库规定的临时内存大小,需要拷贝临时结果集到磁盘上,这个时候需要用户对sql进行优化;

    C.Sorting result, Using filesort:出现这种状态,表示sql正在执行排序操作,排序操作都会引起较多的cpu消耗,通常的优化方法会添加适当的索引来消除排序,或者缩小排序的结果集;

    通过show processlist发现如下sql:

    Sql A.

    | 2815961 | sanwenba | 10.241.142.197:55190 | sanwenba |

    Query | 0 | Sorting result | select z.aid,z.subject from

    www_zuowen z right join www_zuowenaddviews za on za.aid=z.aid order by

    za.viewnum desc limit 10;

    性能sql:

    select z.aid,z.subject from www_zuowen z right join www_zuowenaddviews za

    on za.aid=z.aid order by za.viewnum desc limit 10;

     

    用explain 查看执行计划:

    sanwenba@3018 10:00:54>explain select z.aid,z.subject from www_zuowen z

    right join www_zuowenaddviews za on za.aid=z.aid order by za.viewnum desc

    limit 10;

    +—-+————-+——-+——–+—————+———+———+—————–+——

    | id | select_type | table | type | possible_keys | key | key_len | ref |

    rows | Extra |

    +—-+————-+——-+——–+—————+———+———+—————–+——

    | 1 | SIMPLE | za | index | NULL | viewnum | 6 |

    NULL | 537029 | Using index; Using filesort |

    | 1 | SIMPLE | z | eq_ref | PRIMARY | PRIMARY | 3 |

    sanwenba.za.aid | 1 | |

     

    添加适当索引消除排序:

    sanwenba@3018 10:02:33>alter table www_zuowenaddviews add index

    ind_www_zuowenaddviews_viewnum(viewnum);

    sanwenba@3018 10:03:27>explain select z.aid,z.subject from www_zuowen z

    right join www_zuowenaddviews za on za.aid=z.aid order by za.viewnum desc

    limit 10;

    +—-+————-+——-+——–+—————+——————————–+———+-

    | id | select_type | table | type | possible_keys | key |

    key_len | ref | rows | Extra |

    +—-+————-+——-+——–+—————+——————————–+———+-|

    1 | SIMPLE | za | index | NULL |

    ind_www_zuowenaddviews_viewnum | 3 | NULL | 10 | Using index |

    | 1 | SIMPLE | z | eq_ref | PRIMARY PRIMARY | 3 | sanwenba.za.aid

    | 1 | |

    +—-+————-+——-+——–+—————+——————————–+———+-

    Sql B:

    | 2825321 | netzuowen | 10.200.120.41:44172 | netzuowen |

    Query | 2 | Copying to tmp table on disk |

    SELECT * FROM `www_article` WHERE 1=1 ORDER BY rand() LIMIT 0,30

     

    这种sql order by rand()同样也会出现排序;

    netzuowen@3018 10:23:55>explain SELECT * FROM `www_zuowensearch`

    WHERE checked = 1 ORDER BY rand() LIMIT 0,10 ;

    +—-+————-+——————+——+—————+——–+———+——-+——+

    | id | select_type | table | type | possible_keys | key | key_len | ref |

    rows | Extra |

    +—-+————-+——————+——+—————+——–+———+——-+——+

    | 1 | SIMPLE | www_zuowensearch | ref | newest | newest | 1 |

    const | 1443 | Using temporary; Using filesort |

    +—-+————-+——————+——+—————+——–+———+——-+——+

    这种随机抽取一批记录的做法性能是很差的,表中的数据量越大,性能就越差。

    第一种方案,即原始的Order By Rand() 方法:

     

    $sql=”SELECT * FROM content ORDER BY rand() LIMIT 12″;

    $result=mysql_query($sql,$conn);

    $n=1;

    $rnds=”;

    while($row=mysql_fetch_array($result)){

    $rnds=$rnds.$n.”.

    href=’show”.$row[‘id’].”-“.strtolower(trim($row[‘title’])).”‘>”.$row[‘title’].”

    />\n”;

    $n++;

    }

    3万条数据查12条随机记录,需要0.125秒,随着数据量的增大,效率越来越低。

     

    第二种方案,改进后的JOIN 方法:

     

    for($n=1;$n<=12;$n++){

    $sql=”SELECT * FROM `content` AS t1

    JOIN (SELECT ROUND(RAND() * (SELECT MAX(id) FROM `content`)) AS id) AS t2

    WHERE t1.id >= t2.id ORDER BY t1.id ASC LIMIT 1″;

    $result=mysql_query($sql,$conn);

    $yi=mysql_fetch_array($result);

    $rnds = $rnds.$n.”.

    href=’show”.$yi[‘id’].”-“.strtolower(trim($yi[‘title’])).”‘>”.$yi[‘title’].”
    \n”;

    }

    3万条数据查12条随机记录,需要0.004秒,效率大幅提升,比第一种方案提升

    了约30倍。缺点:多次select查询,IO开销大。

     

    第三种方案,SQL语句先随机好ID序列,用IN 查询(飘易推荐这个用法,IO

    开销小,速度最快):

     

    $sql=”SELECT MAX(id),MIN(id) FROM content”;

    $result=mysql_query($sql,$conn);

    $yi=mysql_fetch_array($result);

    $idmax=$yi[0];

    $idmin=$yi[1];

    $idlist=”;

    for($i=1;$i<=20;$i++){

    if($i==1){ $idlist=mt_rand($idmin,$idmax); }

    else{ $idlist=$idlist.’,’.mt_rand($idmin,$idmax); }

    }

    $idlist2=”id,”.$idlist;

    $sql=”select * from content where id in ($idlist) order by field($idlist2) LIMIT

    0,12″;

    $result=mysql_query($sql,$conn);

    $n=1;

    $rnds=”;

    while($row=mysql_fetch_array($result)){

    $rnds=$rnds.$n.”.

    href=’show”.$row[‘id’].”-“.strtolower(trim($row[‘title’])).”‘>”.$row[‘title’].”

    />\n”;

    $n++;

    }

    3万条数据查12条随机记录,需要0.001秒,效率比第二种方法又提升了4倍左右,比第一种方法提升120倍。注,这里使用了order by field($idlist2) 是为了不排序,否则IN 是自动会排序的。缺点:有可能遇到ID被删除的情况,所以需要多选几个ID。

     

    C.出现sending data的情况:

    | 2833185 | sanwenba | 10.241.91.81:45964 | sanwenba | Query

    | 1 | Sending data | SELECT * FROM `www_article` WHERE

    CONCAT(subject,description) like ‘%??%’ ORDER BY aid desc LIMIT 75,15

    性能sql:

    SELECT * FROM `www_article` WHERE CONCAT(subject,description) like

    ‘%??%’ ORDER BY aid desc LIMIT 75,15

    这种sql是典型的sql分页写法不规范的情况,需要将sql进行改写:

    select * from www_article t1,(select aid from www_article where

    CONCAT(subject,description) like ‘%??%’ ORDER BY aid desc LIMIT 75,15)t2 where t1.aid=t2.aid;

    注意这里的索引需要改用覆盖索引:aid+ subject+description

    总结:

     

     

     

    Sql优化是性能优化的最后一步,虽然位于塔顶,他最直影响用户的使用,但也是最容易优化的步骤,往往效果最直接。RDS-mysql由于有资源的隔离,不同的实例规格拥有的iops能力不同,比如新1型提供的iops为150个,也就是每秒能够提供150次的随机磁盘io操作,所以如果用户的数据量很大,内存很小,由于iops的限制,一条慢sql就很有可能消耗掉所有的io资源,而影响其他的sql查询,对于数据库来说就是所有的sql需要执行很长的时间才能返回结果,对于应用来说就会造成整体响应的变慢。

  • MySQL实际内存分配情况介绍

    内存是重要的性能参数,常常出现由于异常的sql请求以及待优化的数据库导致内存利用率升高,更有甚者由于OOM导致实例发生HA切换。

    MySQL的内存大体可以分为两部分:共享内存和session私有内存,下面详细介绍下各部分的构成。

    1. 共享内存

    以下为240M内存规格RDS实例的共享内存分配示意:

    mysql>show variables where variable_name in (
    'innodb_buffer_pool_size','innodb_log_buffer_size','innodb_additional_mem_pool_size','key_buffer_size','query_cache_size'
    );
    +---------------------------------+-----------------+
    | Variable_name                   | Value           |
    +---------------------------------+-----------------+
    | innodb_additional_mem_pool_size | 2097152         |
    | innodb_buffer_pool_size         | 67108864        |
    | innodb_log_buffer_size          | 1048576         |
    | key_buffer_size                 | 16777216        |
    | query_cache_size                | 0               |
    +---------------------------------+-----------------+
    共返回 5 行记录,花费 342.74 ms.
    • innodb_buffer_pool
      该部分缓存是innodb引擎最重要的缓存区域,是通过内存来弥补物理数据文件的重要手段。其中主要包含有数据页、索引页、undo页、insert buffer、自适应哈希索引、锁信息以及数据字典等信息。在进行sql的读和写的操作首先并不是对物理数据文件操作,而是先对buffer_pool进行操作,然后再通过checkpoint等机制写回数据文件。该空间大的优点就是可以提升数据库的性能、加快sql运行速度,缺点是故障恢复速度较慢。在RDS上会采用实例规格配置的80%作为该部分大小(上图即是240M*0.8=192M)。
    • innodb_log_buffer
      该部分主要存放redo log的信息。InnoDB会首先将redo log写在这里,然后按照一定频率将其刷新回重做日志文件中。该空间不需要太大,因为一般情况下该部分缓存会以较快频率刷新至redo log(Master Thread会每秒刷新、事务提交时会刷新、其空间少于1/2同样会刷新)。在RDS上会设置1M的大小。
    • innodb_additional_mem_pool
      该部分主要存放InnoDB内的一些数据结构。经常是在buffer_pool中申请内存的时候还需要在额外内存中申请空间存储该对象的结构信息。该大小主要与表数量有关,表数量越大需要更大的空间。在RDS中统一设置为2M。
    • key_buffer
      该部分是MyISAM表的重要缓存区域。该部分主要存放MyISAM表的键。MyISAM表不同于InnoDB表,其缓存的索引缓存是放在key_buffer中的,而数据缓存则存储于操作系统的内存中。RDS的系统是MyISAM引擎的,因此在RDS中是给予该部分一定量的空间的。所有的实例统一为16M。
    • query_cache
      该部分是对查询结果做缓存以减少解析sql和执行sql的花销。主要适合于读多写少的应用场景,因为它是按照sql语句的hash值进行缓存的,当表数据发生变化后即失效。在RDS上关闭了该部分的缓存。

    2. Session私有内存

    上面这些内存空间是实例创建的时候即分配的内存空间,并且是所有连接共享的。而出现OOM异常的实例都是由于下面各个连接私有的内存造成的。

    主要包括以下部分(以下为测试实例配置):

    mysql>show variables where variable_name in (
    'read_buffer_size','read_rnd_buffer_size','sort_buffer_size','join_buffer_size','binlog_cache_size','tmp_table_size'
    );
    +-------------------------+-----------------+
    | Variable_name           | Value           |
    +-------------------------+-----------------+
    | binlog_cache_size       | 262144          |
    | join_buffer_size        | 262144          |
    | read_buffer_size        | 262144          |
    | read_rnd_buffer_size    | 262144          |
    | sort_buffer_size        | 262144          |
    | tmp_table_size          | 262144          |
    +-------------------------+-----------------+
    共返回 6 行记录,花费 356.54 ms.
    • read_buffer&read_rnd_buffer
      分别存放了对顺序和随机扫描(例如按照排序的顺序访问)的缓存。当thread进行顺序或随机扫描数据时会首先扫描该buffer空间以避免更多的物理读。每个sessionRDS给予256K的大小。
    • sort_buffer
      需要执行order by和group by的sql都会分配sort_buffer用来存储排序的中间结果,当排序的过程中如果存储带下大于sort_buffer_size的话会在磁盘生成临时表以完成操作。根据MySQL的文档可知在linux系统中,当分配空间大于2M时会使用mmap() 而不是 malloc() 来进行内存分配,导致效率降低。在RDS上给予256K。
    • join_buffer
      MySQL仅支持nest loop的join算法,处理逻辑是驱动表的一行和非驱动表联合查找,这时就可以将非驱动表放入join_buffer,不需要访问拥有并发保护机制的buffer_pool。RDS给予256K大小。
    • binlog_cache
      该区域用来缓存该thread的binlog日志。在一个事务还没有commit之前会先将其日志存储于binlog_cache中,等到事务commit后会将其binlog刷回磁盘上的binlog文件以持久化。同样该大小为256K。
    • tmp_table
      不同于上面的各个session层次的buffer,这个参数是可以在控制台上修改。是指用户内存临时表的大小,如果该thread创建的临时表超过它设置的大小会把临时表转换为磁盘上的一张MyISAM临时表。如果用户在执行事务的时候遇到类似“”这样的错误的时候可以考虑将其修改更大一些。
    • [Err] 1114 - The table '/home/mysql/data3081/tmp/#sql_6197_2' is full
  • MySQL 慢日志 介绍

    什么是慢日志?

    MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10,意思是运行10S以上的语句。默认情况下,MySQL数据库并不启动慢查询日志,需要我们手动来设置这个参数,当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件,也支持将日志记录写入数据库表。

    参考文档:

    什么情况下产生慢日志?

    看图说话,有很多开关影响着慢日志的生成,相关的参数后面会挨个说明。从上图可以看出慢日志输出的内容有两个,第一执行时间过长(大于设置的long_query_time阈值);第二未使用索引,或者未使用最优的索引。这两种日志默认情况下都没有打开,特别是未使用索引的日志,因为这一类的日志可能会有很多,所以还有个特别的开关log_throttle_queries_not_using_indexes用于限制每分钟输出未使用索引的日志数量。

    慢日志相关参数


    以上应该是最完整的和慢日志相关的所有参数,大多数参数都有前置条件,所以在使用的时候可以参照上面的流程图。5.6官方文档:

    https://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html

    https://dev.mysql.com/doc/refman/5.6/en/server-options.html

    慢日志输出内容

    第一行:标记日志产生的时间,准确说是SQL执行完成的时间点,改行记录每一秒只打印一条;

    第二行:客户端的账户信息,两个用户名(第一个是授权账户,第二个为登录账户),客户端IP地址,还有mysqld的线程ID;

    第三行:查询执行的信息,包括查询时长,锁持有时长,返回客户端的行数,扫描行数。通常我需要优化的就是最后一个内容,尽量减少SQL语句扫描的数据行数。

    第四行:通过代码看,貌似和第一行的时间没有区别。

    第五话:最后就是产生慢查询的SQL语句;

    --log-short-format=true

    如果mysqld启动时指定了--log-short-format参数,则不会输出第一、第二行。

    log-queries-not-using-indexes=on   
    
    log_throttle_queries_not_using_indexes > 0 :
    

    如果启用了以上两个参数,每分钟超过log_throttle_queries_not_using_indexes配置的未使用索引的慢日志将会被抑制,被抑制的信息会被汇总,每分钟输出一次。格式如下:

    
    # Time: 170526 11:26:10
    # User@Host: [] @ [] Id: 38
    # Query_time: 0.021872 Lock_time: 0.008620 Rows_sent: 0 Rows_examined: 0
    SET timestamp=1495769170;
    throttle:         14 'index not used' warning(s) suppressed.;
    

    慢日志分析工具

    1. 官方自带工具: mysqldumpslow
    2. 开源工具:mysqlsla
    3. percona-toolkit:工具包中的pt-query-digest工具可以分析汇总慢查询信息,具体逻辑可以看SlowLogParser这个函数;

     

    慢日志的清理与备份

    删除:直接删除慢日志文件,执行flush logs(必须的);

    备份:先用mv重命名文件(不要跨分区),然后执行flush logs(必须的);

    另外修改系统变量slow_query_log_file也可以立即生效;

    执行flush logs,系统会先close当前的句柄,然后重新open;mv , rm日志文件系统并不会报错,具体的原因可以Google下linux i_count i_nlink ;

  • MySQL 开发实践

    1.MySQL读写性能是多少,有哪些性能相关的重要参数?

    这里做了几个简单压测实验

    机器:8核CPU,8G内存
    表结构(尽量模拟业务):12个字段(1个bigint(20)为自增primary key,5个int(11),5个varchar(512),1个timestamp),InnoDB存储引擎。
    实验1(写):insert => 6000/s
    前提:连接数100,每次insert单条记录
    分析:CPU跑了50%,这时磁盘为顺序写,故性能较高

    实验2(写):update(where条件命中索引) => 200/s
    前提:连接数100,10w条记录,每次update单条记录的4个字段(2个int(11),2个varchar(512))
    分析:CPU跑2%,瓶颈明显在IO的随机写

    实验3(读):select(where条件命中索引) => 5000/s
    前提:连接数100,10w条记录,每次select单条记录的4个字段(2个int(11),2个varchar(512))
    分析:CPU跑6%,瓶颈在IO,和db的cache大小相关

    实验4(读):select(where条件没命中索引) => 60/s
    前提:连接数100,10w条记录,每次select单条记录的4个字段(2个int(11),2个varchar(512))
    分析:CPU跑到80%,每次select都需遍历所有记录,看来索引的效果非常明显!

    几个重要的配置参数,可根据实际的机器和业务特点调整

    max_connecttions:最大连接数

    table_cache:缓存打开表的数量

    key_buffer_size:索引缓存大小

    query_cache_size:查询缓存大小

    sort_buffer_size:排序缓存大小(会将排序完的数据缓存起来)

    read_buffer_size:顺序读缓存大小

    read_rnd_buffer_size:某种特定顺序读缓存大小(如order by子句的查询)

    PS:查看配置方法:show variables like '%max_connecttions%';

    2.MySQL负载高时,如何找到是由哪些SQL引起的?

    方法:慢查询日志分析(MySQLdumpslow)

    慢查询日志例子,可看到每个慢查询SQL的耗时:

    # User@Host: edu_online[edu_online] @ [10.139.10.167]
    # Query_time: 1.958000 Lock_time: 0.000021 Rows_sent: 254786 Rows_examined: 254786
    SET timestamp=1410883292;
    select * from t_online_group_records;
    

    日志显示该查询用了1.958秒,返回254786行记录,一共遍历了254786行记录。及具体的时间戳和SQL语句。

    使用MySQLdumpslow进行慢查询日志分析

    MySQLdumpslow -s t -t 5 slow_log_20140819.txt

    输出查询耗时最多的Top5条SQL语句

    -s:排序方法,t表示按时间 (此外,c为按次数,r为按返回记录数等)
    -t:去Top多少条,-t 5表示取前5条

    执行完分析结果如下:

    Count: 1076100  Time=0.09s (99065s)  Lock=0.00s (76s)  Rows=408.9 (440058825), edu_online[edu_online]@28hosts
      select * from t_online_group_records where UNIX_TIMESTAMP(gre_updatetime) > N
    Count: 1076099  Time=0.05s (52340s)  Lock=0.00s (91s)  Rows=62.6 (67324907), edu_online[edu_online]@28hosts
      select * from t_online_course where UNIX_TIMESTAMP(c_updatetime) > N
    Count: 63889  Time=0.78s (49607s)  Lock=0.00s (3s)  Rows=0.0 (18), edu_online[edu_online]@[10x.213.1xx.1xx]
      select f_uin from t_online_student_contact where f_modify_time > N
    Count: 1076097  Time=0.02s (16903s)  Lock=0.00s (72s)  Rows=52.2 (56187090), edu_online[edu_online]@28hosts
      select * from t_online_video_info where UNIX_TIMESTAMP(v_update_time) > N
    Count: 330046  Time=0.02s (6822s)  Lock=0.00s (45s)  Rows=0.0 (2302), edu_online[edu_online]@4hosts
      select uin,cid,is_canceled,unix_timestamp(end_time) as endtime,unix_timestamp(update_time) as updatetime 
      from t_kick_log where unix_timestamp(update_time) > N
    

    以第1条为例,表示这类SQL(N可以取很多值,这里MySQLdumpslow会归并起来)在8月19号的慢查询日志内出现了1076100次,总耗时99065秒,总返回440058825行记录,有28个客户端IP用到。

    通过慢查询日志分析,就可以找到最耗时的SQL,然后进行具体的SQL分析

    慢查询相关的配置参数

    log_slow_queries:是否打开慢查询日志,得先确保=ON后面才有得分析

    long_query_time:查询时间大于多少秒的SQL被当做是慢查询,一般设为1S

    log_queries_not_using_indexes:是否将没有使用索引的记录写入慢查询日志

    slow_query_log_file:慢查询日志存放路径

    3.如何针对具体的SQL做优化?

    使用Explain分析SQL语句执行计划

    MySQL> explain select * from t_online_group_records where UNIX_TIMESTAMP(gre_updatetime) > 123456789;
    +----+-------------+------------------------+------+---------------+------+---------+------+------+-------------+
    | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       | +----+-------------+------------------------+------+---------------+------+---------+------+------+-------------+ |  1 | SIMPLE | t_online_group_records | ALL | NULL          | NULL | NULL    | NULL |   47 | Using where |
    +----+-------------+------------------------+------+---------------+------+---------+------+------+-------------+
    1 row in set (0.00 sec)
    

    如上面例子所示,重点关注下type,rows和Extra:

    type:使用类别,有无使用到索引。结果值从好到坏:… > range(使用到索引) > index > ALL(全表扫描),一般查询应达到range级别

    rows:SQL执行检查的记录数

    Extra:SQL执行的附加信息,如”Using index”表示查询只用到索引列,不需要去读表等

    使用Profiles分析SQL语句执行时间和消耗资源

    MySQL> set profiling=1; (启动profiles,默认是没开启的)
    MySQL> select count(1) from t_online_group_records where UNIX_TIMESTAMP(gre_updatetime) > 123456789; (执行要分析的SQL语句)
    MySQL> show profiles;
    +----------+------------+----------------------------------------------------------------------------------------------+
    | Query_ID | Duration   | Query |
    +----------+------------+----------------------------------------------------------------------------------------------+
    | 1 | 0.00043250 | select count(1) from t_online_group_records where UNIX_TIMESTAMP(gre_updatetime) > 123456789 |
    +----------+------------+----------------------------------------------------------------------------------------------+
    1 row in set (0.00 sec)
    MySQL> show profile cpu,block io for query 1; (可看出SQL在各个环节的耗时和资源消耗)
    +----------------------+----------+----------+------------+--------------+---------------+
    | Status | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out | +----------------------+----------+----------+------------+--------------+---------------+ ... | optimizing           | 0.000016 | 0.000000 | 0.000000 |            0 | 0 |
    | statistics | 0.000020 | 0.000000 |   0.000000 | 0 |             0 | | preparing            | 0.000017 | 0.000000 | 0.000000 |            0 | 0 |
    | executing | 0.000011 | 0.000000 |   0.000000 | 0 |             0 | | Sending data         | 0.000076 | 0.000000 | 0.000000 |            0 | 0 |
    ...
    

    SQL优化的技巧 (只提一些业务常遇到的问题)

    1. 最关键:索引,避免全表扫描。

    对接触的项目进行慢查询分析,发现TOP10的基本都是忘了加索引或者索引使用不当,如索引字段上加函数导致索引失效等(如where UNIX_TIMESTAMP(gre_updatetime)>123456789)

    +----------+------------+---------------------------------------+
    | Query_ID | Duration   | Query                                 |
    +----------+------------+---------------------------------------+
    |        1 | 0.00024700 | select * from mytable where id=100    |
    |        2 | 0.27912900 | select * from mytable where id+1=101  |
    +----------+------------+---------------------------------------+
    

    另外很多同学在拉取全表数据时,喜欢用select xx from xx limit 5000,1000这种形式批量拉取,其实这个SQL每次都是全表扫描,建议添加1个自增id做索引,将SQL改为select xx from xx where id>5000 and id<6000;

    +----------+------------+-----------------------------------------------------+
    | Query_ID | Duration   | Query                                               |
    +----------+------------+-----------------------------------------------------+
    |        1 | 0.00415400 | select * from mytable where id>=90000 and id<=91000 |
    |        2 | 0.10078100 | select * from mytable limit 90000,1000              |
    +----------+------------+-----------------------------------------------------+
    

    合理用好索引,应该可解决大部分SQL问题。当然索引也非越多越好,过多的索引会影响写操作性能

    1. 只select出需要的字段,避免select
      +----------+------------+-----------------------------------------------------+
      | Query_ID | Duration   | Query                                               |
      +----------+------------+-----------------------------------------------------+
      |        1 | 0.02948800 | select count(1) from ( select id from mytable ) a   |
      |        2 | 1.34369100 | select count(1) from ( select * from mytable ) a    |
      +----------+------------+-----------------------------------------------------+
      
    2. 尽量早做过滤,使Join或者Union等后续操作的数据量尽量小
    3. 把能在逻辑层算的提到逻辑层来处理,如一些数据排序、时间函数计算等
    4. …….

    PS:关于SQL优化,已经有足够多文章了,所以就不讲太全面了,只重点说自己1个感受:索引!基本都是因为索引!

    4.SQL层面已难以优化,请求量继续增大时的应对策略?

    • 分库分表
    • 使用集群(master-slave),读写分离
    • 增加业务的cache层
    • 使用连接池
  • MySQL 长时间运行查询

    1. 出现长时间执行的查询的原因

    2. 长时间执行的查询带来的问题

    3. 如何避免长时间执行的查询

    4. 如何处理长时间执行的查询

    4.1 DMS 处理会话

    4.2 设置查询最长执行时间

    4.3 创建事件自动清理长时间执行的查询


    1. 出现长时间执行的查询的原因

    在使用 MySQL 的过程中,由于某些原因,比如被 SQL 注入、SQL执行效率较差、DDL 语句引起表元数据锁等待等等,会出现运行时间很长的查询。

    • 由于被SQL注入而导致的长时间查询:

    • 由于DDL语句引起表元数据锁等待:

     

    2. 长时间执行的查询带来的问题

    通常来说,除非是BI/报表类查询,否则长时间执行的查询对于应用缺乏意义。

    消耗系统资源,比如大量长时间查询可能会引起 CPU、IOPS 和/或 连接数 使用率过高等问题。

    带来系统不稳定的隐患(比如 InnoDB 引擎表上的长时间查询可能会导致 ibdata1 系统文件尺寸的增加)。

    3. 如何避免长时间执行的查询

    应用方面应注意增加防止 SQL 注入的保护。

    在新功能模块上线前,进行压力测试,避免出现执行效率很差的 SQL 大量执行的情况。

    尽量在业务低峰期进行索引创建删除、表结构修改、表维护和表删除操作。

    4. 如何处理长时间执行的查询

    4.1 DMS 清理会话

    也可以通过命令 show processlist; 查看当前执行会话,清理长时间查询。

    4.2 设置查询最长执行时间

    可以通过设置 loose_max_statement_time 参数来限制查询的最长执行时间,该参数单位是 毫秒(MS)。

    # 参数名称 默认值 最小值 最大值 作用
    1 loose_max_statement_time 0 0 4294967295 限制SQL语句执行的最长时间,单位毫秒(ms)

    比如:

    long_running_sql_01.png

    注:

    • 修改该参数设置,对修改设置前已经存在的会话不生效;对修改设置后新创建的会话有效。
    • loose_max_statement_time 限制 SQL 执行 时间;如果 DML 操作出现 InnoDB 行锁等待,锁等待时间是不计入执行时间的。

    4.3 创建事件自动清理长时间执行的查询

    创建 MySQL事件,自动清理长时间执行的查询。

    比如下面的代码会每 5 分钟清理一次当前用户运行时间超过 1 个小时且非锁等待会话。

    create event my_long_running_query_monitor
    on schedule every 5 minute
    starts '2015-09-15 11:00:00'
    on completion preserve enable do
    begin
      declare v_sql varchar(500);
      declare no_more_long_running_query integer default 0;
      declare c_tid cursor for
        select concat ('kill ',id,';') from 
        information_schema.processlist
        where time >= 3600
        and user = substring(current_user(),1,instr(current_user(),'@')-1)
        and command not in ('sleep')
        and state not like ('waiting for table%lock');
      declare continue handler for not found
        set no_more_long_running_query=1;
     
      open c_tid;
      repeat
        fetch c_tid into v_sql;
        set @v_sql=v_sql;
        prepare stmt from @v_sql;
        execute stmt;
        deallocate prepare stmt;
      until no_more_long_running_query end repeat;
      close c_tid;
    end;

    注:样例仅供参考,请结合应用情况自行调整监控条件和运行间隔。

  • MySQL查询缓存 (Query Cache) 的设置和使用

    1. 功能和适用范围

    功能:

    • 降低 CPU 使用率
    • 降低 IOPS 使用率(某些情况下)
    • 减少查询响应时间,提高系统的吞吐量

    适用范围:

    • 表数据修改不频繁、数据较静态
    • 查询(Select)重复度高
    • 查询结果集小于 1 MB

    注:

    • 查询缓存并不一定带来性能上的提升,在某些情况下(比如查询数量大,但重复的查询很少)开启查询缓存会带来性能的下降。

    2. 原理

    MySQL 对来自客户端的查询(Select)进行 Hash 计算得到该查询的Hash值,通过该Hash 值到查询缓存中匹配该查询的结果。

    如果匹配(命中),则将查询的结果集直接返回给客户端,不必再解析、执行查询。

    如果没有匹配(命中),则将 Hash 值和结果集保存在查询缓存中,以便以后使用。

    查询涉及的任何一个表中数据发生变化,RDS for MySQL 将查询缓存中所有与该表相关的查询结果集全部释放(删除)。

    3. 限制

    • 查询必须严格一致(大小写、空格、使用的数据库、协议版本、字符集等必须一致)才可以命中,否则视为不同查询。
    • 不缓存查询中的子查询结果集,仅缓存查询最终结果集。
    • 不缓存存储函数(Stored Function)、存储过程(Stored Procedure)、触发器(Trigger)、事件(Event)中的查询。
    • 不缓存含有每次执行结果变化的函数的查询,比如 now()、curdate()、last_insert_id()、rand()等。
    • 不缓存对 mysql、information_schema、performance_schema 系统数据库表的查询。
    • 不缓存使用临时表的查询。
    • 不缓存产生告警(Warnings)的查询。
    • 不缓存 Select … lock in share mode、Select … for update、 Select * from … where autoincrement_col is NULL 类型的查询。
    • 不缓存使用用户定义变量的查询。
    • 不缓存使用 Hint – SQL_NO_CACHE 的查询。

    4. 设置

    4.1 参数

    参数设置

    • query_cache_limit: 查询缓存中可存放的单条查询最大结果集、默认为 1 MB;超过该大小的结果集不被缓存。
    • query_cache_size: 查询缓存的大小。
    • query_cache_type: 是否开启查询缓存功能。

    取值为 0 :关闭查询功能

    取值为 1 :开启查询缓存功能,但不缓存 Select SQL_NO_CACHE 开头的查询。

    取值为 2 :开启查询缓存功能,但仅缓存 Select SQL_CACHE 开头的查询。

    注:

    • 修改 query_cache_type 需要重启实例(修改后实例会自动重启)。
    • 参数 query_cache_size 要求设置值为 1024 的整数倍,否则会提示 “参数格式错误,请重新输入”。

    4.2 开启

    参数 query_cache_size 大于 0 并且 query_cache_type 设置为 1 或者 2 的情况下,查询缓存开启。

    4.3 关闭

    设置参数 query_cache_size 为 0 或者设置 query_cache_type 为 0 关闭查询缓存。

    4.4 建议

    • query_cache_size 不建议设置的过大。过大的空间不但挤占实例其他内存结构的空间,而且会增加在缓存中搜索的开销。建议根据实例规格,初始值设置为 10MB 到 100 MB 之间的值,而后根据运行使用情况调整。
    • 建议通过调整 query_cache_size 的值来开启、关闭查询缓存,因为修改 query_cache_type 参数需要重启实例生效。
    • 查询缓存适用于特定的场景,建议充分测试后,再考虑开启,避免引起性能下降或引入其他问题。

    5. 验证效果

     SQL 命令

    1. show global status like Qca%’;

    query_cache_03.png

    可以通过 show global status like ‘Qca%’ 来获取查询缓存的使用状态。

    • Qcache_hits :查询缓存命中次数。
    • Qcache_inserts:将查询和结果集写入到查询缓存中的次数。
    • Qcache_not_cached:不可以缓存的查询次数。
    • Qcache_queries_in_cache:查询缓存中缓存的查询量。
  • MySQL 表上 Metadata lock 的产生和处理

    1. Metadata lock wait 出现的场景

    2. Metadata lock wait 的含义

    3. 导致 Metadata lock wait 等待的活动事务

    4. 解决方案

    5. 如何避免出现长时间 Metadata lock wait 导致表上相关查询阻塞,影响业务


    1. Metadata lock wait 出现的场景

    • 创建、删除索引
    • 修改表结构
    • 表维护操作(optimize table、repair table 等)
    • 删除表
    • 获取表上表级写锁 (lock table tab_name write)

    注:

    • 支持事务的 InnoDB 引擎表和 不支持事务的 MyISAM 引擎表,都会出现 Metadata Lock Wait 等待现象。
    • 一旦出现 Metadata Lock Wait 等待现象,后续所有对该表的访问都会阻塞在该等待上,导致连接堆积,业务受影响。

     

    2. Metadata lock wait 的含义

    为了在并发环境下维护表元数据的数据一致性,在表上有活动事务(显式或隐式)的时候,不可以对元数据进行写入操作。因此 MySQL 引入了 metadata lock ,来保护表的元数据信息。

    因此在对表进行上述操作时,如果表上有活动事务(未提交或回滚),请求写入的会话会等待在 Metadata lock wait 。

    3. 导致 Metadata lock wait 等待的活动事务

    • 当前有对表的长时间查询
    • 显示或者隐式开启事务后未提交或回滚,比如查询完成后未提交或者回滚。
    • 表上有失败的查询事务

    4. 解决方案

    • show processlist 查看会话有长时间未完成的查询,使用kill 命令终止该查询。

    • 查询 information_schema.innodb_trx 看到有长时间未完成的事务, 使用 kill 命令终止该查询。
    select concat('kill ',i.trx_mysql_thread_id,';') from information_schema.innodb_trx i,
      (select 
             id, time
         from
             information_schema.processlist
         where
             time = (select 
                     max(time)
                 from
                     information_schema.processlist
                 where
                     state = 'Waiting for table metadata lock'
                         and substring(info, 1, 5) in ('alter' , 'optim', 'repai', 'lock ', 'drop ', 'creat'))) p
      where timestampdiff(second, i.trx_started, now()) > p.time
      and i.trx_mysql_thread_id  not in (connection_id(),p.id);
    
    -- 请根据具体的情景修改查询语句
    -- 如果导致阻塞的语句的用户与当前用户不同,请使用导致阻塞的语句的用户登录来终止会话

    • 如果上面两个检查没有发现,或者事务过多,建议使用下面的查询将相关库上的会话终止
      -- RDS for MySQL 5.6
      
      select 
          concat('kill ', a.owner_thread_id, ';')
      from
          information_schema.metadata_locks a
              left join
          (select 
              b.owner_thread_id
          from
              information_schema.metadata_locks b, information_schema.metadata_locks c
          where
              b.owner_thread_id = c.owner_thread_id
                  and b.lock_status = 'granted'
                  and c.lock_status = 'pending') d ON a.owner_thread_id = d.owner_thread_id
      where
          a.lock_status = 'granted'
              and d.owner_thread_id is null;
      
      
      -- RDS for MySQL 5.5
      
      select 
          concat('kill ', p1.id, ';')
      from
          information_schema.processlist p1,
          (select 
              id, time
          from
              information_schema.processlist
          where
              time = (select 
                      max(time)
                  from
                      information_schema.processlist
                  where
                      state = 'Waiting for table metadata lock'
                          and substring(info, 1, 5) in ('alter' , 'optim', 'repai', 'lock ', 'drop ', 'creat', 'trunc'))) p2
      where
          p1.time >= p2.time
              and p1.command in ('Sleep' , 'Query')
              and p1.id not in (connection_id() , p2.id);
      
      -- RDS for MySQL 5.5 语句请根据具体的 DDL 语句情况修改查询的条件;
      -- 如果导致阻塞的语句的用户与当前用户不同,请使用导致阻塞的语句的用户登录来终止会话

       

    5. 如何避免出现长时间 metadata lock wait 导致表上相关查询阻塞,影响业务

    • 在业务低峰期执行上述操作,比如创建删除索引。
    • 在到RDS的数据库连接建立后,设置会话变量 autocommit 为 1 或者 on,比如 set autocommit=1; 或 set autocommit=on; 。
    • 考虑使用事件来终止长时间运行的事务,比如下面的例子中会终止执行时间超过60分钟的事务。
      create event my_long_running_trx_monitor
      on schedule every 60 minute
      starts '2015-09-15 11:00:00'
      on completion preserve enable do
      begin
        declare v_sql varchar(500);
        declare no_more_long_running_trx integer default 0; 
        declare c_tid cursor for
          select concat ('kill ',trx_mysql_thread_id,';') 
          from information_schema.innodb_trx 
          where timestampdiff(minute,trx_started,now()) >= 60;
        declare continue handler for not found
          set no_more_long_running_trx=1;
       
        open c_tid;
        repeat
          fetch c_tid into v_sql;
       set @v_sql=v_sql;
       prepare stmt from @v_sql;
       execute stmt;
       deallocate prepare stmt;
        until no_more_long_running_trx end repeat;
        close c_tid;
      end;

      注:请根据您自身情况,自行修改运行间隔和事务执行时长。

    • 执行上述1中操作前,设置会话变量 lock_wait_timeout 为较小值,比如 set lock_wait_timeout=30; 命令可以设置 metadata lock wait 的最长时间为 30 秒;避免长时间等待元数据锁影响表上其他业务查询。

     

  • MySQL InnoDB 锁等待和锁等待超时的处理

    1. Innodb 引擎表行锁等待和等待超时发生的场景

    2.Innodb 引擎行锁等待情况的处理

    2.1 Innodb 行锁等待超时参数 innodb_lock_wait_timeout

    2.2 大量行锁等待和行锁等待超时的处理a


    1. Innodb 引擎表行锁等待和等待超时发生的场景

    当一个 RDS MySQL 连接会话等待另外一个会话持有的互斥行锁时,会发生 Innodb 引擎表行锁等待情况。

    通常情况下,持有该互斥行锁的会话(连接)会迅速的执行完相关操作并释放掉持有的互斥锁(事务提交或者回滚),进而等待的会话在行锁等待超时时间到来前获得该互斥行锁,进行下一步操作。

    但在某些情况下,比如一个实例未感知到的来自客户端应用的数据库会话中断,持有该互斥行锁的会话长时间不释放该互斥行锁,此时如果有其他会话申请该互斥行锁,则会导致大量的行锁等待与行锁等待超时。

    2. Innodb 引擎行锁等待情况的处理

    本文提供的检查和处理方法,仅当正在发生 InnoDB 行锁等待的情况下才成立;因为 InnoDB 行锁等待默认超时时间为50秒,因此通常情况下不容易观察到行锁等待现场,可以通过将 innodb_lock_wait_timeout 参数设置为较大值来复现问题(生产环境不推荐使用过大的 innodb_lock_wait_timeout 参数值)。

    2.1. Innodb 行锁等待超时参数 innodb_lock_wait_timeout

    # 参数 默认值 最小值 最大值 说明
    1 innodb_lock_wait_timeout 50 1 1073741824 获取Innodb 行锁的等待时间,单位秒。可在会话级别设置

    该参数控制 Innodb 行锁等待的超时时间,单位为秒,RDS 实例该参数的默认值为 50(秒)。

    等待互斥锁的会话在等待 50 秒后会退出锁等待状态并返回下面的错误,这个行为称之为 Innodb 引擎表行锁等待超时。

    1. ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

    可以通过下面的命令查看当前会话和全局的参数设置。

    1. show variables like innodb_lock_wait_timeout’;  查看当前会话show global variables like innodb_lock_w%’;  查看全局设置

    该参数支持在会话级别修改,方便应用在会话级别单独设置某些特殊操作的行锁等待超时时间,如下:

    1. set innodb_lock_wait_timeout=1000; —设置当前会话 Innodb 行锁等待超时时间,单位秒

     

    2.2. 大量行锁等待和行锁等待超时的处理

    如果行锁等待和行锁等待超时持续发生,并且导致当前应用运行异常,那么需要获取到一直持有行锁的会话,并且终止该会话来释放持有的锁(会话对应的事务会回滚)。

    2.2.1 检查导致锁等待和锁超时的会话

    注:下面的方法必须在行锁等待正在发生的时候进行检查。

    方法 1: 通过 DMS  实例信息   Innodb 锁等待查看。

     

    方法 2:通过 DMS  实例信息  实例会话查看。

     

    方法 3: 在 DMS 无法登录的情况下,可以通过执行下面的查询,获得导致行锁等待和行锁等待超时的会话。

    1. select l.* from ( select Blocker role, p.id, p.user, left(p.host, locate(‘:’, p.host) - 1) host, tx.trx_id, tx.trx_state, tx.trx_started, timestampdiff(second, tx.trx_started, now()) duration,lo.lock_mode, lo.lock_type, lo.lock_table, lo.lock_index, tx.trx_query, lw.requesting_thd_id Blockee_id, lw.requesting_trx_id Blockee_trxfrom information_schema.innodb_trx tx,information_schema.innodb_lock_waits lw, information_schema.innodb_locks lo, information_schema.processlist pwhere lw.blocking_trx_id = tx.trx_id and p.id = tx.trx_mysql_thread_id and lo.lock_id =lw.blocking_lock_idunionselect Blockee role, p.id, p.user, left(p.host, locate(‘:’, p.host) - 1) host, tx.trx_id, tx.trx_state, tx.trx_started, timestampdiff(second, tx.trx_started, now()) duration,lo.lock_mode, lo.lock_type, lo.lock_table, lo.lock_index, tx.trx_query, null, nullfrom information_schema.innodb_trx tx, information_schema.innodb_lock_waits lw, information_schema.innodb_locks lo,information_schema.processlist pwhere lw.requesting_trx_id = tx.trx_id and p.id = tx.trx_mysql_thread_id and lo.lock_id = lw.requested_lock_id) l order by role desc, trx_state desc;

    比如:

    对于复杂的多个会话相互行锁等待情况,建议先终止 Role 为 Blocker 且 trx_state 为 RUNNING 的会话;终止后再次检查,如果仍旧有行锁等待,再终止新结果中的 Role 为 Blocker 且 trx_state 为 RUNNING 的会话。

    2.2.2 处理导致行锁等待和行锁等待超时的会话

    对于标识为 Blocker 的会话(持有锁阻塞其他会话的 DML 操作,导致行锁等待和行锁等待超时),确认业务可以接受其对应的事务回滚的情况下,可以将其终止。

    终止会话的方法请参考:RDS for MySQL如何终止会话

    比如,可以通过 Kill 命令来今后会话终止。

  • MySQL InnoDB表级锁等待

    1. 显式 lock table

    2. 隐式 lock table


    在 RDS MySQL 实例日常使用中,有些情况下会发现出现 Innodb 表级锁等待的情况,下面列出常见的2个原因。

     1. 显式 lock table

    执行了 lock tables tab_name read; 导致 DML 会话等待在表的表级锁上。

    会话 1

    lock tables tab_name read;

    会话 2

    会话 3

     

    2. 隐式 lock table

    mysqldump 使用默认参数进行数据导出时,会默认的开启 –lock-tables 选项,进而导致导出表上的DML操作等待在表级锁上。

    会话 1

    会话 2

    会话 3

    对于 mysqldump 方式的导出,建议在业务低峰期进行导出,并且设置 –single-transaction 选项进行 Innodb 引擎表导出,避免出现 Innodb 表级锁等待的情况。

     

  • MySQL 查询分析

    一个低效查询引发的思考

    上次在做银行对账,上传对账单后,出现对账超时的情况。查看日志发现,最后一条日志记录停在了对 c2c_zwdb.t_file_count 的查询 sql 上。使用 show processlist 命令来查看当前 SQL 的执行情况,如下:

    由上图可知,原来是发生锁表了 waiting for table level lock。

    引发锁表的 sql 语句就是上图中 status 为 updating 的语句为:

    update c2c_zwdb.t_file_count set Fcount=Fcount 1 where FFileName='1001_招商银行 (1).txt' and Ftype=2

    该条 update 语句还未执行完,给表 c2c_zwdb.t_file_count 加的写锁还没释放,又执行 select 读操作,select 语句会等待表级锁,导致阻塞而使银行对账超时。

    为什么这条 update 语句执行了如此久还没执行完呢?这个语句不够高效,当在数据量很大的情况下,执行效率更慢。

    定位 MySQL 性能瓶颈的方法很多,主要为这两种:慢查询与 explain 命令。

    一 慢查询

    慢查询,顾名思义,就是查询超过指定时间 long_query_time 的 SQL 语句查询称为”慢查询”。 慢查询帮我们找到执行慢的 SQL,方便我们对这些 SQL 进行优化。

    慢查询开启方法

    long_query_time 是用来定义慢于多少秒的才算”慢查询”。查询 long_query_time 的值如下:

    我们可以将其设置设置 long_query_time=2,如下。

    开启慢查询的方法,一是可以通过在配置文件 my.cnf 或 my.ini 中设置配置参数,二是可以通过命令行设置变量来即时启动慢查询日志,个人比较喜欢第二种即时性的。由下图可知,记录慢查询日志已开启,slow_query_log=ON。

    slow_query_log 是否打开记录慢查询日志

    slow_query_log_file 日志存放位置

    MySQLdumpslow命令

    接下来看看慢查询日志的格式是怎么样。例如,在 MySQL 中运行 select sleep(3);

    打开慢查询日志文件 MySQL-slow.log 的信息格式如下,说明这条 sql 语句执行用时 5.000183s,锁了 0s,查询返回 1 行,一共查了 0 行。

    随着 MySQL 数据库服务器运行时间的增加,可能会有越来越多的 SQL 查询被记录到了慢查询日志文件中,这时要分析慢查询日志就显得不是很容易了。MySQL 提供的 MySQLdumpslow 命令,可以很好地解决这个问题。

    MySQLdumpslow 的主要功能是统计不同慢 sql 的:

    • 执行次数(count)
    • 执行最长时间(time)
    • 累计总耗费时间(time)
    • 等待锁的时间(lock)
    • 发送给客户端的行总数(rows)
    • 扫描的行总数(rows)

    进入 MySQL/bin 目录,输入 MySQLdumpslow -help 或–help 可以看到这个工具的参数。

    -s,是表示按照何种方式排序,c、t、l、r 分别是按照执行次数、执行时间、等待锁时间、返回的记录数来排序,ac、at、al、ar 表示相应的平均值;

    • -r,是前面排序的逆序;
    • -t,是 top n 的意思,即为返回排序后前面多少条的数据;
    • -g,后边可以写一个正则匹配模式,大小写不敏感的;

    比如,执行./MySQLdumpslow -s c -t 5/data/zftMySQLData/MySQL-slow.log,得到执行次数最多的前 5 个查询,如下图所示。

    执行./MySQLdumpslow -s r -t 10 /data/zftMySQLData/MySQL-slow.log,得到返回记录数最多的前 10 个查询。

    使用 MySQLdumpslow 命令可以非常明确的得到各种我们需要的查询语句,对 MySQL 查询语句的监控、分析、优化是 MySQL 优化的第一步,也是非常重要的一步。

    二 explain 分析查询

    在分析查询性能时,EXPLAIN 关键字同样很管用。EXPLAIN 关键字一般放在 SELECT 查询语句的前面,使用 EXPLAIN 关键字可以模拟优化器执行 SQL 查询语句,从而知道 MySQL 是如何处理 SQL 语句的。这可以帮助分析查询语句效率低下的原因或是表结构的性能瓶颈。通过 explain 命令可以得到:

    – 表的读取顺序

    – 数据读取操作的操作类型

    – 哪些索引可以使用

    – 哪些索引被实际使用

    – 表之间的引用

    – 每张表有多少行被优化器查询

    Explain的用法

    Explain tablename 或

    Explain [EXTENDED] SELECT select_options

    前者可以得出一个表的字段结构等等,后者主要是给出相关的一些索引信息,本文要讲述的重点是后者。

    首先看看 explain 的输出参数:

    这些参数中,各个参数的含义如下,

    Id:本次 select 的标识符。在查询中每个 select 都有一个顺序的数值。

    Select_type:select 类型,主要是区别普通查询和联合查询、子查询之类的复杂查询。主要有这几种:

    • SIMPLE:这个是简单的 sql 查询,不使用 UNION 或者子查询。
    • PRIMARY:子查询中最外层的 select。
    • UNION:UNION 中的第二个或后面的 SELECT 语句。
    • DEPENDENT UNION:UNION 中的第二个或后面的 SELECT 语句,取决于外面的查询。
    • UNION RESULT:UNION 的结果。
    • SUBQUERY:子查询中的第一个 SELECT。
    • DEPENDENT SUBQUERY:子查询中的第一个 SELECT,取决于外面的查询。
    • DERIVED:派生表的 SELECT(FROM 子句的子查询)。

    Table:输出行所引用的表。

    Type:联合查询所使用的类型。

    type 显示的是访问类型,是较为重要的一个指标,结果值从好到坏依次是:

    system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

    一般来说,得保证查询至少达到 range 级别,最好能达到 ref。

    possible_keys:指出 MySQL 能使用哪个索引在该表中找到行。如果是空的,没有相关的索引。这时要提高性能,可通过检验 WHERE 子句,看是否引用某些字段,或者检查字段不是适合索引。

    Key:显示 MySQL 实际决定使用的键。如果没有索引被选择,键是 NULL。

    key_len:显示 MySQL 决定使用的键长度。如果键是 NULL,长度就是 NULL。文档提示特别注意这个值可以得出一个多重主键里 MySQL 实际使用了哪一部分。

    Ref:显示哪个字段或常数与 key 一起被使用。

    Rows:这个数表示 MySQL 要遍历多少数据才能找到,在 innodb 上是不准确的。
    Extra:如果是 Only index,这意味着信息只用索引树中的信息检索出的,这比扫描整个表要快。

    如果是 where used,就是使用上了 where 限制。

    如果是 impossible where 表示用不着 where,一般就是没查出来啥。

    如果此信息显示 Using filesort 或者 Using temporary 的话会很吃力,WHERE 和 ORDER BY 的索引经常无法兼顾,如果按照 WHERE 来确定索引,那么在 ORDER BY 时,就必然会引起 Using filesort,这就要看是先过滤再排序划算,还是先排序再过滤划算。

    现在我们再用 explain 来看看前面案例的 sql 执行情况。首先,先看看 t_file_count 的表结构如下,该表的索引是 FId。

    未执行完的 sql 语句是 update c2c_zwdb.t_file_count set Fcount=Fcount 1 where FFileName='1001_招商银行 (1).txt' and Ftype=2

    将其转换为 select 语句,select count(*) from c2c_zwdb.t_file_count where FFileName='1001_招商银行 (1).txt' and Ftype=2。执行explain命令如下:

    由上图可见,type=all,key=NULL,该 sql 未使用索引,是一个效率非常低的全表扫描,在数据量很大的情况下,性能情况可想而知。

Copyright © 2014-2025 奋奋的愤愤 | 京ICP备14029030号-1