MYSQL百万级数据分页查询优化实战

MYSQL大数据量分页查询优化

一、记一次mysql分页查询优化

最近项目中,需要将公司老的订单日志数据迁移到新的ElasticSearch统一日志存储,我们老日志数据是分库分表存储在mysql数据库中(按天分表),单表数据量在500w左右,本人就写了一个小程序负责mysql到es的数据迁移,功能其实很简单,但其中出现了一些没有考虑到的问题,比如查询的效率问题,在此做下记录。老日志数据库如下
MYSQL百万级数据分页查询优化实战_第1张图片

CREATE TABLE `OrderOperLog20191210` (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `A1` varchar(50) NOT NULL DEFAULT '' COMMENT '订单编号',
  `A2` varchar(20) NOT NULL DEFAULT '' COMMENT 'PNR编号',
  `A3` int(11) NOT NULL DEFAULT '0' COMMENT '机票序号(1:支付宝支付操作 / 2:支付宝退款操作)',
  `A4` varchar(20) NOT NULL DEFAULT '' COMMENT '机票编号?',
  `A5` varchar(20) NOT NULL DEFAULT '' COMMENT '日志类型(订单 / 账单 / ……)',
  `A6` varchar(20) NOT NULL DEFAULT '' COMMENT '操作类型(预订 / 出票 / 退票 / 废票 / 手工补单 / 自动出票 / 提交到支付接口 / 收到网上支付通知 / 提交到退款接口 / 收到网上退款通知 / ……)',
  `A7` datetime NOT NULL DEFAULT '1900-01-01 00:00:00' COMMENT '操作时间',
  `A8` varchar(4000) NOT NULL DEFAULT '' COMMENT '操作说明',
  `A9` varchar(20) NOT NULL DEFAULT '' COMMENT '操作单位编号',
  `A10` varchar(100) NOT NULL DEFAULT '' COMMENT '操作单位名称',
  `A11` varchar(50) NOT NULL DEFAULT '' COMMENT '操作员编号',
  `A12` varchar(50) NOT NULL DEFAULT '' COMMENT '操作员名称',
  `PNRContent` varchar(5000) NOT NULL DEFAULT '' COMMENT '操作内容(二进制)',
  `DepartmentID` int(11) NOT NULL DEFAULT '0' COMMENT '能看见该操作的部门编号',
  `DepartmentName` varchar(100) NOT NULL DEFAULT '' COMMENT '能看见该操作的部门名称',
  PRIMARY KEY (`ID`),
  KEY `IDX_OrderOperLog20191210_1` (`A1`),
  KEY `IDX_OrderOperLog20191210_2` (`A6`)
) ENGINE=MyISAM AUTO_INCREMENT=112824 DEFAULT CHARSET=utf8 COMMENT='订单操作日志表:';

功能其实就是按时间查询老日志数据,批量插入es,这里不多介绍。开发完成后开始运行程序,迁移数据,发现查询速度及其之慢。。。完全不能接受。查询日志,一次请求大概在2分钟左右,排查后发现sql执行占用了大部分时间。
优化前
MYSQL百万级数据分页查询优化实战_第2张图片

二、sql查询优化

	以下是分页查询的原始语句,按天分页查询数据,一次查询1000条。优化LIMIT分页
分页计算:
起始:Offset = (pageNumber - 1) * pageSize;
每页条数:pageSize = 1000
LIMIT Offset,pageSize 

SELECT ID,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,PNRContent,departmentID,departmentName FROM OrderOperLog20200103 Order By ID LIMIT 1200000,1000
分析一下执行sql,发现执行语句并未用到索引,且进行了全表扫描
explain SELECT ID,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,PNRContent,departmentID,departmentName FROM OrderOperLog20200103 Order By ID LIMIT 1200000,1000
{
  "code": 1,
  "message": "ok",
  "content": [
    {
      "id": 1,
      "select_type": "SIMPLE",
      "table": "OrderOperLog20200103",
      "type": "ALL",
      "possible_keys": null,
      "key": null,
      "key_len": null,
      "ref": null,
      "rows": 2254017,
      "Extra": "Using filesort"
    }
  ],
  "time": 1586240844974
}

在系统中需要进行分页操作的时候,我们通常会使用LIMIT加上偏移量的办法实现,同时加上合适的ORDERBY子句。如果有对应的索引,通常效率会不错,否则,MySQL需要做大量的文件排序操作。这里就出现一个问题,当在偏移量非常大的时候,如limit 1200000,1000这样的查询,MySQL需要查询1201000条记录然后只返回最后1000条,前面12000000条记录都将被抛弃,这样的代价非常高。而且,越到后面,访问的数据越多,表末的数据近乎接近全表扫描。如果查询多个字段,就会造成全表扫描。要优化这种查询,要么是在页面中限制分页的数量,要么是优化大偏移量的性能。
优化此类分页查询的一个最简单的办法就是尽可能地使用索引覆盖扫描,而不是查询所有的列。然后根据需要做一次关联操作再返回所需的列。对于偏移量很大的时候,这样做的效率会提升非常大。考虑下面的查询:
注意:在使用order by时,经常出现Using filesort,因此对于此类sql语句需尽力优化,使其尽量使用Using index
1.先按ID 进行order by排序,id使用index索引排序效率高。
select ID FROM OrderOperLog20200103 Order By ID LIMIT 1200000,1000

优化思路一
我们改成下面的形式,只查出需要的字段,通过子查询使用ID索引扫描出查询记录,再做关联查询带出其他字段。实际就是利用表的覆盖索引来加速分页查询(利用了索引查询的语句中如果只包含了那个索引列(覆盖索引),那么这种情况会查询很快)

SELECT ID,A1,A6,A7,A8,A9,A10,A11,A12,PNRContent,DepartmentID,DepartmentName FROM OrderOperLog20200103 INNER JOIN (select ID FROM OrderOperLog20200103
Order By ID LIMIT 1200000,1000) as innerT USING(ID)

这里的“延迟关联”将大大提升查询效率,它让MySQL扫描尽可能少的页面,获取需
要访问的记录后再根据关联列回原表查询需要的所有列。这个技术也可以用于优化关联
查询中的LIMIT子句。
优化思路二
LIMIT和OFFST的问题,其实是OFFSET的问题.它会导致MySQL扫描大量不需要的
行然后再抛弃掉。如果可以使用书签记录上次取数据的位置,那么下次就可以直接从该
书签记录的位置开始扫描,这样就可以避免使用OFFSET。例如,下面先通过内查询,确定OFFSET的位置,

SELECT ID,A1,A6,A7,A8,A9,A10,A11,A12,PNRContent,DepartmentID,DepartmentName FROM OrderOperLog20200103 
WHERE ID >= (select ID FROM OrderOperLog20200103 Order By ID LIMIT 1200000,1) LIMIT 1000

因为我们业务日志使用的ID是数据库自增主键,所以这里我们直接可以通过((pageNumber - 1) * pageSize)确定偏移量OFFSET的位置,比如查询1201页,再次优化结果如下

SELECT ID,A1,A6,A7,A8,A9,A10,A11,A12,PNRContent,DepartmentID,DepartmentName FROM OrderOperLog20200103 
WHERE ID >1200000 LIMIT 1000

总结
order by优化
①MySQL支持两种方式的排序filesort和index,Using index是指MySQL扫描索引本身完成排序。index效率高,filesort效率低。
②order by满足两种情况会使用Using index。
#1.order by语句使用索引最左前列。
#2.使用where子句与order by子句条件列组合满足索引最左前列。
③尽量在索引列上完成排序,遵循索引建立(索引创建的顺序)时的最佳左前缀法则。
④如果order by的条件不在索引列上,就会产生Using filesort。

分页优化
1.直接使用数据库提供的SQL语句,SELECT * FROM 表名称 LIMIT M,N,只适用于数据量在千级别。
2.建立主键或唯一索引, 利用索引来加速分页查询
3.基于索引再排序,保证数据的查询完整性,利用MySQL支持ORDER操作可以利用索引快速定位部分元组,避免全表扫描
比如: 读第1000到1019行元组(pk是主键/唯一键).
SELECT * FROM your_table WHERE pk>=1000 ORDER BY pk ASC LIMIT 0,20
4.利用"子查询/连接+索引"快速定位元组的位置,然后再读取元组.,就是上面所讲到的例子。

你可能感兴趣的:(mysql)