ITPub博客

首页 > 数据库 > Oracle > high performance mysql notes

high performance mysql notes

Oracle 作者:abc.sql 时间:2014-01-20 22:04:10 0 删除 编辑
3.1 什么是性能优化:
     降低机器资源使用率?不是。就是要榨干机器资源;
     提升每秒查询量(提高数据库吞吐量),降低响应时间。
     首先要有一个非常准确的测量数据,这是一个非常耗时的工作,甚至90%的调优时间都在做这个测量。有了准确的数据,也就好对症下药了
     完成一项任务所需要的时间分为两部分:执行时间和等待时间。前者就要把任务分解到子任务,确定哪里的执行时间比较长;后者可能要查看整个计算机的状态,因为数据库可能在等待其它系统资源,比如争用CPU,磁盘。
    可以采用性能剖析的办法来进行分析。比如mysql profiler可以反应sql执行的每一个步骤和时间;Performance Schema的使用。
    成本过高的优化不要去做;量少但非常严重的问题要处理;一些隐蔽的问题不要漏掉;不要过于相信平均值,要看看直方图,标准差,会有一些奇异点暴露问题
3.2对应用程序进行性能剖析
   推荐了New Relic的软件
   监控PHP程序有一个工具叫ifP(instrumentation-for-php),在http://code.google.com/p/instrumentation-for-php/
3.3 剖析mysql查询
    使用pt-query-digest分析slow log
    使用profiler分析sql 执行计划,使用information_schema。profiling查找profilling信息
    使用show status
    使用 performance_schma
3.4 诊断间歇性问题
    所谓间歇性问题,就是不可复现,突然出现,独立分析sql又都正常的问题。可能的原因:调用了很慢的外部服务,比如curl; memcached过期导致大量db查询; DNS超时;DB锁竞争;并发度超限;
    可用的检查办法:
    a. 定期查询show global status: 
mysqladmin -uroot -p ext -i1|awk '                                                                 /Queries/{q=$4-qp;qp=$4}       #把上次的值存到qp里,然后本次值减去qp的,就是一秒内的query数量
 /Threads_connected/{tc=$4}                                                                   
 /Threads_running/{printf "] ] ] ",q,tc,$4}'  

    b. show processlist;
  mysql -e "show processlistG"|grep State: |sort|uniq -c |sort -rn
     结果:
    744 State:
     67 State: Sending data
     36 State: freeing item
     8  State: NULL
     6  State: end
     4  State: Updating
     ... ... ... ...
   
    c.使用查询日志
     统计每秒钟的慢查询数量,排序输出
awk '/^# Time:/{print $3, $4, c;c=0}/^# User/{c++}' mysql_slow.log |sort -k 3 -nr
结果类似这样:
140106 7:00:01 278
140106 10:20:01 269
140106 7:40:01 266
140106 12:20:01 264
140106 6:40:01 261
140106 7:10:01 254  

3.4.2捕获诊断数据
      系统状态,CPU利用率,磁盘空间使用率,ps输出,mysql信息:show status,processlist,show innodb status.
      linux内部诊断工具有oprofile,strace,GDB,pt-pmp

3.5其它剖析工具
   user_statistics表
   strace会使mysql变慢,不适合在生产环境中使用
   pt-ioprofile

4.1选择优化的数据类型
4.1.6
 Mysql在内部使用整数存储ENUM和SET,在做比较操作时转换为字符串。
 使用unsigned int存储IP地址,如果要显示成4段样式,可以用inet_ntoa和inet_aton来回转换
mysql <wbr><wbr>高性能

4.2常见的schema设计陷阱
4.3范式和反范式
4.4缓存表和汇总表
    字符串可以直接转成datetime:
     select concat(left(now(),14),'00:00') - interval 1 hour; 取上一个小时的整点。
    flexviews是第三方的物化视图工具
    计数器表: 一般只有一行作为计数器,这样锁竞争太激烈。可以多插入一些行,比如100,每行都是一个计数器,同时新加一个slot列,作为槽位,使用如下语句递增计数器
update hit_counter set cnt=cnt+1 where slot = rand() * 100;
当要获取新的序号时,这样获取:
select sum(cnt) from hit_counter;
这样就把写竞争拆分成100个小锁,大大增加了并发性。但100个定时器的sum还是顺序地往上走,却不会锁(因为只是select)。
还可以再新加一个date列,一天100个计数器,如下表结构:
create table daily_hit_counter (
  day date not null,
  slot tinyint unsigned not null,
  cnt int unsigned not null,
  primary key(day,slot)
 ) engine=innodb;
day 版的增加计数器是这样:
insert into daily_hit_counter(day,slot,cnt) 
 values(current_date,rand()*100,1) 
 on duplicate key update cnt = cnt +1;
如果希望减少表中的行数,可以把所有计数器都加到0号slot上,然后把其余的都删掉:
update daily_hit_counter as c
          inner join (
               select day,sum(cnt) as cnt,min(slot) as mslot 
               from daily_hit_counter
               group by day
             ) as x using(day)
     set c.cnt=if(c.slot=x.mslot,x.cnt,0),
         c.slot=if(c.slot=x.mslot,0,c.slot);  #
delete from daily_hit_counter where slot <> 0 and cnt= 0;
结果演示:
mysql> select * from daily_hit_counter;
+------------+------+-----+
| day        | slot | cnt |
+------------+------+-----+
| 2014-01-06 |    0 |   5 |
| 2014-01-06 |    1 |  13 |
| 2014-01-06 |    2 |  10 |
| 2014-01-06 |    3 |  15 |
| 2014-01-06 |    4 |  11 |
| 2014-01-06 |   12 |  11 |
| 2014-01-06 |   13 |   8 |
| 2014-01-06 |   14 |  14 |
| 2014-01-06 |   15 |  14 |
| 2014-01-06 |   16 |   9 |
| 2014-01-07 |    0 |   3 |
| 2014-01-07 |    1 |  11 |
| 2014-01-07 |    2 |   7 |
| 2014-01-07 |    3 |   8 |
update之后就成为
+------------+------+------+
| day        | slot | cnt  |
+------------+------+------+
| 2014-01-06 |    0 | 1026 |
| 2014-01-06 |    1 |    0 |
| 2014-01-06 |    2 |    0 |
| 2014-01-06 |    3 |    0 |
| 2014-01-07 |    0 | 1040 |
| 2014-01-07 |    1 |    0 |
| 2014-01-07 |    2 |    0 |
| 2014-01-07 |    3 |    0 |
| 2014-01-07 |    4 |    0 |
| 2014-01-07 |    5 |    0 |
| 2014-01-07 |    6 |    0 |
删掉后就只剩下两行:
| day        | slot | cnt  |
+------------+------+------+
| 2014-01-06 |    0 | 1026 |
| 2014-01-07 |    0 | 1040 |
以下是自动插入day 版的存储过程:
delimiter //
create procedure a()
       begin
       declare i int default 0;
         while i < 1000 do 
           insert into daily_hit_counter(day,slot,cnt)   values(current_date- interval 1 day,rand()*100,1)   on duplicate key update cnt = cnt +1;
           insert into daily_hit_counter(day,slot,cnt)   values(current_date,rand()*100,1)   on duplicate key update cnt = cnt +1;
           set i=i+1;
         end while;
       end;//
delimiter ; 

4.5加快alter table的速度
  alter table ... alter column colname set default 5;  用alter column 子句修改列的默认值是很快的,比change column ,modify colum 都要快。
  有一些取巧的办法去快速修改表结构:
  如果 a. 去掉列的auto_increment属性,b.增加、移除、更改ENUM或SET的常量,可以新建一个新结构的表,然后flush tables with read lock,交换frm文件,最后释放锁。
  快速创建myisam表的索引:
  新建两个表,一个是原结构(含数据)一个是新结构,然后交换新结构表的frm和MYI文件给正式的表,别忘了加锁,最后用repaire table重建索引。


5.3.5 聚簇索引
聚簇索引并不是一种单独的索引类型,而是一种数据存储方式,它是连索引带数据一起的一种数据存储方式,不光是索引。在oracle里它叫索引组织表,其实就是一种表,数据呀,索引呀啥的,都有了,啥也没剩下了。
innodb和myisam的索引区别有两点:
     一个是innodb的叶子节点包括了主键值、事务id、回滚指针以及所有的剩余列。如果主键是一个列前缀索引,innodb也会包含完整的主键列和剩下的其它列。 myisam只是记录索引列和行号。
    第二个区别是,二级索引很不一样。对于myisam,二级索引和主键没什么区别,主键就是非空唯一的索引,对于innodb要复杂一些,他的二级索引的叶节点不是指向“行指针”,而是指向主键的,并以此作为指向行的“指针”。这样的策略减少了当出现行移动或者数据页分裂时二级索引的维护工作。

   最好避免随机的(不连续且值的分布范围非常大)聚簇索引,特别是对I/O密集型的应用。例如,从性能的角度考虑,使用UUID作为聚簇索引会很糟糕:它使得聚簇索引的插入变得完全随机,这是最坏的情况,使数据完全没有聚集特性。

5.3.6 索引覆盖
如果一个索引包含所有要查询的字段的值,就成为“索引覆盖”。在explain结果的extra列里有“using index”就表示用到了索引覆盖。
索引覆盖要求select的列和where列出的列都要在索引中列出。这个条件有点高,有时where列出的列都包含在索引里,但select就不一定,这种情况可以通过“deferred join”的技巧来部分使用索引覆盖。
思路是把索引包含的列组成一个子查询,得出部分结果(比如要查询的行的主键),然后再根据这个部分结果(主键)去获取全部结果。
例子:
  select * from products where actor='SEAN CARREY' and title like '%APPOLO%'
   type:ref
   key: ACTOR
   ref: const
   Extra: Using where

  重新设计索引,将索引扩展至覆盖三个数据列(actor,title,prod_id),重写sql:
  expain select * from products join (
                                select prod_id from products where actor='SEAN CARREY' and
                                title like '%APOLLO%'
                                 ) as t1 on (t1.prod_id=products.prod_id)
  子查询select部分是prod_id列, where部分是actor,title,这三个列刚好全部被索引包含,可以用到索引覆盖。返回的prod_id给外层查询根据主键查询,减少了总成本。
  ****************************** 1.row *****************************************
         id: 1
  select_type: PRIMARY
        table:
             ... omitted ...
  ****************************** 1.row *****************************************
         id: 1
  select_type: PRIMARY
        table: products
             ... omitted ...
 ****************************** 1.row *****************************************
         id: 2
    select_type:DERIVED
          table: products
           type: ref
   possible_key: ACTOR,ACTOR_2,IX_PROD_ACTOR
            key: ACTOR_2
        key_len: 52
            ref:
           rows:11
          extra: Using where; Using index


5.3.7 是用索引扫描来做排序
mysql有两种方式可以生成有序的结果:通过排序操作或者按索引顺序扫描。如果explain结果的type列值是"index",则说明mysql使用了索引扫描来排序。
只有当索引的列顺序和order by子句的顺序完全一致,并且所有列的排序方向都一样时,才能使用索引对结果进行排序。如果查询需要关联多张表,则只有当order by子句引用的字段全部为第一个表时,才能使用索引排序。order by子句和查找型查询的限制是一样的:需要满足索引的最左前缀的要求;否则只会执行排序操作而不会使用索引排序。
有一种情况下order by子句可以不满足索引的最左前缀的要求,就是前导列为常量的时候。如果where 子句或join子句中对这些列制指定了常量,就可以弥补索引的不足。
索引列(a,b,c)
则 ... where a = 'xxx' order by b,c 是可以使用索引排序的
则...  where a = 'xxx' order by b 也可以

5.3.9冗余和重复索引
一般来讲,重复的索引是不需要的,这也包括左前缀索引是重复的。但有一些例外情况:

第一:比如select count(*) from a where colx = xx; 在colx上建有index, 对下面的语句:
    select cola,colb from a where colx=xx来说可能索引(colx,cola,colb)会更有效率,会使用到索引覆盖。而这两个索引是左前缀重复的,然后却都有用武之地。光保留(colx) query2变慢,光保留(colx,cola,colb) query1会稍稍慢一些,以下是测试数据
                only index1         only index2             both
myisam , q1       114.96               25.4                112.19
myisam,  q2       9.97                 16.34               16.37
innodb,  q1       108.55               100.33              107.97
innodb,  q2       12.12                28.04               28.06
第二:因为Inoodb的二级索引的叶节点是指向主键的,索引对于一个二级索引(colx)实际上是(colx,pkey), 所以(colx)和(colx,pkey)同时存在,就是重复的了。

5.3.11 索引和锁
这是一个很重要的内容,但书上写得不多,仅仅举了一个例子,是间隙锁的一个例子。
 actorid < 5 and actor <> 1 返回2,3,4但也会把1锁住。

5.4.1 支持多种过滤条件
对于一些使用得非常频繁的列,虽然这些列的选择性不高,但由于使用很频繁,所以如果能把它们都用到索引,还是会快一些。比如sex, country。那么我们建一个(sex,country,lname,compny,...)的索引,希望能尽量多地适用于不同的sql。
对于那些不使用这两列的查询也可以用一种办法用到这个索引。方法简称IN方法,具体是:建立一个复合索引,然后sql里的选择加上  and sex in ('m','f')让mysql选择这个索引。这样就不用改索引了。

5.4.2避免多个范围条件
所谓多范围查询,比如: and col1 > 100 and col2 between 'ab' and 'yz'; mysql无法同时使用(col1)和(col2)索引,只能使用其中之一。松散索引扫描就可以实现一个索引上使用多个范围条件,也就不需要上面的IN方法了。

5.4.3 优化排序
对于翻页逻辑会很慢,比如 
select from profiles where sex='M' order by rating limit 100000,10
一种优化方法是deferred join,改写如下:
select from profiles inner join (
        select from profiles 
        where x.sex='M' order by rating limit 100000,10
        ) as x using ();

5.5.2 更新索引统计信息
innodb会在表首次打开,或者执行naalye table,抑或表的大小发生非常大的变化的时候计算索引的统计信息。
inndb在打开某些informaction_schema表,或者使用show table staus 和hsow index,或在mysql客户端开启自动补全功能的时候都会触发索引统计信息的更新。如果服务器上有大量数据,这可能就是个很严重的问题。可以关闭innodb_stats_on_metadata参数来避免上面提到的问题。percona里通过innodb_stats_auto_update参数来禁止通过自动采集的方式更新索引统计信息,这是需要手动执行analyze table命令来更新统计信息。
另外,mysql还能使用系统表来持久化这些索引统计信息。percona 5.1中控制参数是innodb_use_sys_stats_table, mysql5.6是innodb_analyze_is_persistent

6.2慢查询基础
6.2.1 确定只向mysql申请需要的数据
1. 如果只取有限行,用limit限制
2. 如果只需要某些列,不要用select *

6.2.2 为得到上面预申请的数据,mysql需要扫描的行数
有三个指标:响应时间,扫描的行数和返回的行数。后两者比例越接近1:1就越好,但通常达不到。关键就是where条件的利用情况:
最好的情况: 由存储引擎层完成,在索引中使用where条件国旅不匹配的记录。这个应该是ICP(Index                      Condition Pushdown)
较好的情况: 索引覆盖(extra: Using index)返回记录,也是依赖索引就可以滤过不要的并返回结果,不过它              是在mysql 服务层实现的(与上面的情况相区别),也不需要回表查询。
次些的情况: 从数据表中返回数据,然后过滤不满足条件的(extra: Using where).
如果尝试去提高这个比例,可以
1. 尽量按上面的三种情况使用where的列索引
2. 改变库表结构,使用汇总表
3. 把复杂sql改写成简单的,比如巨型delete改成一次delete一点,join拆成单表查询,不过这都要具体分析

6.4 查询执行的基础
查询执行路径
high <wbr>performance <wbr>mysql <wbr>notes

6.4.1 mysql server/client通信协议
这个协议是半双工的。任何一个时刻要么服务器向客服端发数据,要么是反过来。
多数连接mysql的库函数都可以获得全部结果集并缓存在内存里,也可以不缓存,一行一行获得。通常是缓存的,服务区在数据都到达客户端的缓存后才能断开连接。注意这个时候数据仅仅是在客户端驱动的缓存,还没到真正的客户代码。这样有助于少占用服务端时间,尽早释放服务端资源。但问题是如果返回一个很大的结果集,库函数会花很多时间和内存去处理这些数据,这种情况就宁可不要缓存,直接从服务器拿数据处理。
对于php来讲,mysql_query是缓存版的,还有一个mysql_unbuffered_query不缓存版。
perl的不缓存是在连接的时候指定mysql_use_result=1

6.4.2查询缓存

6.4.3 查询优化处理
一. 语法解析器和预处理
二.查询优化器
   show status like 'last_query_cost';可以查看上条语句的查询成本:结果是多少个数据页的随机查找才能完成上述查询。
   mysql能处理的优化类型:
     重新定义关联表的顺序
     将外连接转为内连接
     使用等价变换规则
     优化count(),min(),max(): min就找b-tree的最左节点,max就最右,这个时候explain里面的可以看到“select tables optimized away”。所谓count()优化就是myisam预存了一个变量保存总数。
     预估并转化为常数表达式
     索引覆盖扫描
     等值传播
     子查询优化
     提前终止查询(limit)
     列表IN的比较: mysql将IN()列表中的数据先进行排序,然后通过二分查找的方法来确定列表中的值是否满足条件,这是一个O(logn)复杂度操作,而其它很多数据库把IN换成OR的复杂度是O(n),这一点mysql还是比较快的。
三. mysql如何执行关联查询
    主要就是Nested Loop(嵌套循环关联),选择驱动表是个技术活。
四.执行计划
    explain extended 后查看show warnings就可以看到mysql重构的查询。
五.关联查询优化:主要就是驱动表的选择
六.排序优化:两个版本的order by  算法。当查询需要所有列的长度不超过参数max_length_for_sort_data       时,mysql使用优化后的排序方法。
七.查询执行引擎
八.返回结果给客户端

6.5 mysql查询优化器的局限性
6.5.1 关联子查询:where id in (select ...) 这类的语句
一般来讲mysql实现子查询的效率不高,一般是把它换成join来做。但也不是绝对,具体还是要具体分析。
6.5.2UNION的限制:在每个select后面加上limit
6.5.3索引和并优化
6.5.4等值传递:mysql会把一个表上面的in列表数据传给其它表。如果这个列表很大,就会导致优化和执行都很慢。不过这个问题很罕见。
6.5.5 无并行执行
6.5.6 无哈希关联
6.5.7 关于松散索引扫描(loose index scan): 在mysql 5.6里面已经支持
   select ... from tb1 where b between 2 and 3; 
   索引的前导字段是a, 但查询中只指定了字段b,mysql无法使用这个索引。但可以考虑松散索引扫描: 先扫描a列的第一个值对应的b列的范围,然后再跳到a列第二个不同值扫描对应的b列的范围
       high <wbr>performance <wbr>mysql <wbr>notes
6.5.8 有时最大最小值的优化不够智能,虽然b-tree的最左列就是最小的,但如果查询没用到索引,那他还是会全表扫描。这个时候可以改写sql,加上limit 1;
如 select min(actor_id) from actor where first_name = 'xxxx';
first_name 无索引。

6.5.9 在同一个表上查询和更新
 下面的查询希望把两个表中相似行(type相等)的数量记录到字段cnt中
     update tbl
             inner join (
                          select type,count(*) as cnt
                          from tbl 
                          group by type
                        ) as der USING(type)
     set tbl.cnt=der.cnt;

6.7 优化特定类型的查询
6.7.1 优化count()查询
count(*)用于统计行数
count(colname 或列的表达式)用于统计列植的数量,此种情况是把NULL排除的。
下面是一个计算满足某种条件行数的示例:通过一个查询返回各种不同颜色商品的数量,此时不能用or语句(select count(color='blue' or color='red') from items),因为这样无法区分不同颜色的商品数量;也不能使用and,比如color = 'blue' and color 'red' ,两个and条件相互排斥。可以这样:
 mysql>select sum(if(color='bule',1,0)) as blue, sum(if(color='red',1,0)) as red from items;
也可以用count
mysql>select count(color='blue' or NULL) as blue, count(color='red' or NULL) as red from items;

更进一步的优化count()是一个很难的问题,因为这意味这要访问大量数据。一般来讲,mysql层面能做的就是尽量使用索引覆盖扫描。如果这个不行,就要考虑修改应用的架构,增加汇总表或使用memcached这样的外部缓存机制。“快速,精准和实现简单”很难都瞒住,必须舍掉其一。

6.7.2优化关联操作
两大信条:
a. 确保ON或USING子句中的列上有索引。在创建索引的时候只需要在关联顺序中的第二个表的相应列上创建索引。如果优化器的关联顺序是B,A,那么就只需要在A表上的对应列上建索引就够了。
b. 确保任何的group by 和order by中的表达式只涉及到一个表中的列,这样mysql才有可能使用索引来优化。

6.7.3优化子查询
一般来讲是使用关联查询代替,当然最详尽的方案是具体分析。不过在mysql5.6或mariaDB,就不需要用关联查询代替了。

6.7.5 优化limit 分页
1.  使用delayed join,改写成临时表的子查询。内查询使用索引覆盖,得到主键,再和外查询关联,得到索要找的列数据。
2.  获取上次分页的最后记录的主键id0,下次使用这个id0:  where id > id0 limit offset. (假设从小到大的顺序获取记录)

6.7.9 使用用户自定义变量
一例: 打印出某个group分组总量的排序
表:create table film_actor(actor_id int ,film_id int);
数据:insert into film_actor values (1,100),(2,109),(3,102),(4,190),(4,123),(4,132),(4,129),(5,194),(5,190),(6,193),(6,187),(7,167),(7,105),(7,119),(7,121),(7,153),(7,149),(7,148),(7,147),(7,142),(7,141),(7,159),(7,158),(7,152);
解决方案:
set @curr_cnt:=0,@prev_cnt:=0,@rank:=0;
select actor_id, 
       @curr_cnt:=cnt as cnt, 
       @rank := if(@prev_cnt <> @curr_cnt,@rank + 1,@rank) as rank,
       @prev_cnt := @curr_cnt as dummy
 from ( 
       select count(*) as cnt ,actor_id 
       from film_actor 
       group by actor_id    
       order by cnt desc     limit 10      
      ) as der;
结果:  +----------+-----+------+-------+
| actor_id | cnt | rank | dummy |
+----------+-----+------+-------+
|        7 |  13 |    1 |    13 |
|        4 |   4 |    2 |     4 |
|        5 |   2 |    3 |     2 |
|        6 |   2 |    3 |     2 |
|        1 |   1 |    4 |     1 |
|        2 |   1 |    4 |     1 |
|        3 |   1 |    4 |     1 |
+----------+-----+------+-------+
分析:
子查询得到前两列的内容
select colxxx 字段列表的排列是有前后顺序的,放在后面的最后执行。
set 是初始化三个局部变量全零,
select的三个计算: 第一个是得到该行的cnt, 第二个计算rank, 第三个把本次的cnt保留供下一行比较。这三个计算是按顺序的,不能调换。

统计insert on duplicate key update的update数量
insert into t(c1,c2) values (4,4),(2,2),(3,1)
on duplicate key update 
  c1=values(c1) + (0*(@x:=@x+1));
表达式0*(@x:=@x+1)总是0,所以不会改变整个sql的结果,但每update一次@x变量就增加一次。最后,mysql会返回@x的值。

<!-- 正文结束 -->

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/653783/viewspace-1115819/ ,如需转载,请注明出处,否则将追究法律责任。

上一篇: 没有了~
下一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2011-03-30