【提出问题】

从数据表t通过分页查询的方式读取数据,读取时要根据a1排序。t有80万行记录,当OFFSET很大时,读取速度很慢。优化后查询速度提升很快。
下图是表的定义,一共有几十个字段,RowLength大概500字节。除了主键,没有其他索引。
CREATE TABLE `t` (
  `a0` ) NOT NULL,
  `a1` ) NOT NULL,
  `a2` ,) NOT NULL DEFAULT '0.000000000',
  `a3` ,) NOT NULL DEFAULT '0.000000000',
  `a4` ,) NOT NULL DEFAULT '0.000000000',
  `a5` ,) NOT NULL DEFAULT '0.000000000',
  `a6` ,) NOT NULL DEFAULT '0.000000000',
  `a7` ,) NOT NULL DEFAULT '0.000000000',
  `a8` ,) NOT NULL DEFAULT '0.000000000',
  `a9` ,) NOT NULL DEFAULT '0.000000000',
  `b1` ,) NOT NULL DEFAULT '0.000000000',
  `b2` ,) NOT NULL DEFAULT '0.000000000',
  `b3` ,) NOT NULL DEFAULT '0.000000000',
  `b4` ,) NOT NULL DEFAULT '0.000000000',
  `b5` ,) NOT NULL DEFAULT '0.000000000',
  `b6` ,) NOT NULL DEFAULT '0.000000000',
  `b7` ,) NOT NULL DEFAULT '0.000000000',
  `b8` ,) NOT NULL DEFAULT '0.000000000',
  `b9` ,) NOT NULL DEFAULT '0.000000000',
  `c1` ,) NOT NULL DEFAULT '0.000000000',
  `c2` ,) NOT NULL DEFAULT '0.000000000',
  `c3` ,) NOT NULL DEFAULT '0.000000000',
  `c4` ,) NOT NULL DEFAULT '0.000000000',
  `c5` ,) NOT NULL DEFAULT '0.000000000',
  `c6` ,) NOT NULL DEFAULT '0.000000000',
  `c7` ) ',
  `c8` ) ',
  `c9` ) ',
  `d1` ) ',
  `d2` ) ',
  `d3` ,) NOT NULL DEFAULT '0.000000000',
  `d4` ,) NOT NULL DEFAULT '0.000000000',
  `d5` ,) NOT NULL DEFAULT '0.000000000',
  `d6` ,) NOT NULL DEFAULT '0.000000000',
  `d7` ,) NOT NULL DEFAULT '0.000000000',
  `d8` ,) NOT NULL DEFAULT '0.000000000',
  `d9` ) ',
  `e1` ,) NOT NULL DEFAULT '0.000000000',
  `e2` ,) NOT NULL DEFAULT '0.000000000',
  `e3` ,) NOT NULL DEFAULT '0.000000000',
  `e4` ,) NOT NULL DEFAULT '0.000000000',
  `e5` ,) NOT NULL DEFAULT '0.000000000',
  `e6` ,) NOT NULL DEFAULT '0.000000000',
  `e7` ,) NOT NULL DEFAULT '0.000000000',
  `e8` ,) NOT NULL DEFAULT '0.000000000',
  `e9` ,) NOT NULL DEFAULT '0.000000000',
  `f1` ,) NOT NULL DEFAULT '0.000000000',
  `f2` ,) NOT NULL DEFAULT '0.000000000',
  PRIMARY KEY (`a0`,`a1`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

表基本信息

SELECT * FROM tables where table_name='t'\G
. row ***************************
  TABLE_CATALOG: def
   TABLE_SCHEMA: test
     TABLE_NAME: t
     TABLE_TYPE: BASE TABLE
         ENGINE: InnoDB
        VERSION:
     ROW_FORMAT: Compact
     TABLE_ROWS:
 AVG_ROW_LENGTH:
    DATA_LENGTH:
MAX_DATA_LENGTH:
   INDEX_LENGTH:
      DATA_FREE:
 AUTO_INCREMENT: NULL
    CREATE_TIME:  ::
    UPDATE_TIME: NULL
     CHECK_TIME: NULL
TABLE_COLLATION: utf8_general_ci
       CHECKSUM: NULL
 CREATE_OPTIONS:
  TABLE_COMMENT:

原始SQL

--SQL1--
,;
 row in SET (6.67 sec)

--SQL2--
,) AS A USING (a0, a1);
 row in set (0.90 sec)
 SQL2使用的是经典的延迟关联优化: 首先根据条件查出主键,再用INNER JOIN查出需要的数据集。
 
问题:为什么SQL2速度有这么大的提升呢?  
 
【背景知识】
  1. 使用 Optimizer Trace 观察SQL执行过程(http://www.cnblogs.com/skylerjiang/p/mysql_optimizer_trace_intro.html
  2. MySQL排序内部原理探秘 (http://geek.csdn.net/news/detail/105891
 
查看sort_buffer配置,打开Optimizer Trace
SHOW variables like '%sort%';
+--------------------------------+---------------------+
| Variable_name                  | Value               |
+--------------------------------+---------------------+
                |
             |
+--------------------------------+---------------------+
排序buffer为8M

SET optimizer_trace="enabled=on";
;
SELECT trace FROM information_schema.optimizer_trace\G

【问题分析】

SQL1执行过程

,; SELECT trace FROM information_schema.optimizer_trace\G

  
(右键新窗口打开查看完整图片)

解析
  1. SELECT *, 读取数据的时候,需要读取所有的字段。
  2. a1没有索引,将使用全表扫描。
  3. priority_queue优化检查
    1. LIMIT 100000,1  需要排序TOP 100001行(再丢弃100000行,取1行)
    2. 每行454字节,100001行需要45,400,454字节,大于sort_buffer_size(8,388,608),数据集无法直接纳入buffer
    3. 尝试去除additionl_fields(数据字段),使用sortkey+rowid的方式,row_size=74,可以在buffer中存放
    4. 估算merge_sort和priority_queue_cost,前者比后者小,放弃使用priority_queue
  4. sort_mode=<sort_key,additional_fields>,需要使用外部排序,分片数量46。

SQL2执行过程

,) AS A USING (a0, a1);
SELECT trace FROM information_schema.optimizer_trace\G

解析
  1. 首先执行子查询 SELECT a0, a1 FROM t ORDER BY a1 DESC LIMIT 100000,1
    1. 只需要读取a0和a1两个字段
    2. a1没有索引,将使用全表扫描。
    3. priority_queue优化检查
      1. LIMIT 100000,1  需要排序TOP 100001行(丢弃100000行,取1行)
      2. 每行66字节,100001行需要6,600,066字节,小于sort_buffer_size(8,388,608),数据集可以纳入buffer
      3. 使用priority_queue
    4. 执行完毕,结果存储在临时表A里面
  2. 临时表A只有1行,连表查询时,可以使用 t 的主键,非常快速。

结论

  1. 全表扫描时,SQL1需要读取所有字段(大约500字节),SQL2只需要读取2个字段(小于100字节)。
  2. SQL1需要使用外部排序,分片数量又比较多(46个),所以比较慢。
  3. SQL2数据可以存放在sort_buffer里面,还可以启用优先队列优化,加速明显。  

【场景扩展】

1. Limit对执行过程的影响

 稍微修改下SQL语句,把 LIMIT 100000,1 修改为 LIMIT 600000,1,看看执行过程。
 

SQL1执行过程

,;
 row in set (8.82 sec)

SELECT trace FROM information_schema.optimizer_trace\G

这次的执行过程完全一样,但决策依据稍有不同。600001行数据,即使去除additional fields,也需要44,400,074字节,超过了8M的sort_buffer_size,not_enough_space,无法使用priority_queue。原来是因为priority_queue_cost>merge_sort_cost而放弃。

SQL2执行过程

,) AS A USING (a0, a1);
 row in set (1.05 sec)

SELECT trace FROM information_schema.optimizer_trace\G

执行过程有比较大改变:由于LIMIT太大,超过了sort_buffer,所以

  • 无法启用优先队列优化,

使用了外部排序,共8个分片。

 

2. 不同sort_mode的影响

以上测试,都是additional_fields模式,如果是rowid模式,会怎么样呢?
;

SQL1执行过程

,;
 row in set (4.16 sec)

排序模式变成了rowid模式,速度变快了。这是因为排序时只需要读取排序字段和rowid,外部排序的分片数量减少了。

SQL2执行过程

,) AS A USING (a0, a1);
 row in set (1.05 sec)

执行模式没有改变,因为sizeof(a0+a1)<100,还是能用priority_queue优化。

【参考资料】

  1. 使用 Optimizer Trace 观察SQL执行过程(http://www.cnblogs.com/skylerjiang/p/mysql_optimizer_trace_intro.html
  2. MySQL排序内部原理探秘 (http://geek.csdn.net/news/detail/105891
  3. filesort.cc 源代码阅读笔记 http://www.cnblogs.com/skylerjiang/p/6269310.html

【MySQL】查询优化实例解析-延迟关联优化的更多相关文章

  1. MySQL 分页查询优化——延迟关联优化

    目录 1.   InnoDB表的索引的几个概念 2.   覆盖索引和回表 3.   分页查询 4.   延迟关联优化 写在前面 下面的介绍均是在选用MySQL数据库和Innodb引擎的基础开展.我们先 ...

  2. mysql 利用延迟关联优化查询(select * from your_table order by id desc limit 2000000,20)

    其实在我们的工作中类似,select * from your_table order by id desc limit 2000000,20会经常遇见,比如在分页中就很常见. 如果我们的sql中出现这 ...

  3. Mysql覆盖索引与延迟关联

    延迟关联:通过使用覆盖索引查询返回需要的主键,再根据主键关联原表获得需要的数据.   为什innodb的索引叶子节点存的是主键,而不是像myisam一样存数据的物理地址指针? 如果存的是物理地址指针不 ...

  4. Mysql查询优化器之关于JOIN的优化

    连接查询应该是比较常用的查询方式,连接查询大致分为:内连接.外连接(左连接和右连接).自然连接 下图展示了 LEFT JOIN.RIGHT JOIN.INNER JOIN.OUTER JOIN 相关的 ...

  5. PHP使用Mysql事务实例解析

    <?php //数据库连接 $conn = mysql_connect('localhost', 'root', ''); mysql_select_db('test', $conn); mys ...

  6. mysql通过“延迟关联”进行limit分页查询优化的一个实例

    最近在生产上遇见一个分页查询特别慢的问题,数据量大概有200万的样子,翻到最后一页性能很低,差不多得有4秒的样子才能出来整个页面,需要进行查询优化. 第一步,找到执行慢的sql,如下: SELECT  ...

  7. 【MySQL】性能优化 之 延迟关联

    [背景]  某业务数据库load 报警异常,cpu usr 达到30-40 ,居高不下.使用工具查看数据库正在执行的sql ,排在前面的大部分是: SELECT id, cu_id, name, in ...

  8. MySQL性能优化之延迟关联

    [背景]  某业务数据库load 报警异常,cpu usr 达到30-40 ,居高不下.使用工具查看数据库正在执行的sql ,排在前面的大部分是: SELECT id, cu_id, name, in ...

  9. mysql优化----大数据下的分页,延迟关联,索引与排序的关系,重复索引与冗余索引,索引碎片与维护

    理想的索引,高效的索引建立考虑: :查询频繁度(哪几个字段经常查询就加上索引) :区分度要高 :索引长度要小 : 索引尽量能覆盖常用查询字段(如果把所有的列都加上索引,那么索引就会变得很大) : 索引 ...

随机推荐

  1. C++ 头文件系列(deque)

    简介 deque是double ended queue(即双端队列)的简称. 就像C++中的大部分容器的一样,deque具有以下属性: 顺序的(sequence) 动态增长的(dynamic grow ...

  2. 关于SQL 数据表中的密码加密

    首先,都知道一个字节(byte)等于八位二进制数.在数据表中将密码字段设置为binary类型,再结合哈希散列运算可以实现密码加密. 下面介绍下binary 和 varbinary: binary 和  ...

  3. Python2 基于urllib2 的HTTP请求类

    一个利用urllib2模块编写的下载器,虽然有了requests模块,但是毕竟标准库 import urllib2,random class strong_down(): def __init__(s ...

  4. mysql数据库主从搭建

    一.最近一直在学习mysql的东西,刚好看到mysql如何搭建主从数据库,搜集了很多资料后大致了解了mysql主从复置的原理.以下是我的理解: 举例master为主数据库,slave为从数据库. sl ...

  5. 用ES6语法和方式写gulp

    安装依赖模块 npm i -g gulp npm i gulp babel-core babel-preset-es2015 --save-dev 在创建文件 .babelrc(文件名) : (文件内 ...

  6. 将stack翻译成"堆栈"实在是误人子弟

    也不知道从何时起,也不知道是哪个"教授"还是"老师",将stack翻译成堆栈(据说台湾叫做"堆叠").窃以为,这种翻译实在是误人子弟(题外话 ...

  7. nodejs 包引用的终极结论

    通常我们用exports 或module.exports 来导出一个文件中的接口和字段,用require来引用导出的对象.那么这个exports 和 module.exports到底有啥关联呢? 1. ...

  8. 【Python】Python中对象管理与垃圾回收中两个很有意思的问题

    再Python中是利用引用计数来实现对象管理和垃圾回收的,即其他对象引用该对象时候,其引用计数加1,反之减1,当引用计数为0时候,被垃圾收集器回收. Python解释器对对象以及计数器的管理分为以下两 ...

  9. LNA

    low noise amplifier ,低噪声放大器. PA主要侧重输出功率,LNA侧重噪声系数,所以LNA用于前级,PA用作末级.

  10. Meta标签中的format-detection属性及含义让IPHONE的数字可以改变颜色

    format-detection翻译成中文的意思是“格式检测”,顾名思义,它是用来检测html里的一些格式的,那关于meta的format-detection属性主要是有以下几个设置: meta na ...