如何优化mysql分页查询

接上篇:《如何写一个高效的索引,优化mysql查询(分页,回表,排序)》

现在需求改了,需要进行分页并且返回所有的字段。可以按照上篇的方法写一个三星索引,但有一个严重的问题,由于需要查询全部的列,而为了避免回表只能建立一个所有列的联合索引。这就等于把整个表的数据复制了一份。

CREATE TABLE `eyas_account` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_id` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户id',
  `nickname` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名',
  `phone` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '电话号码(或第三方账号ID)',
  `password` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '密码',
  `sex` int(4) NOT NULL DEFAULT '0' COMMENT '性别:0-未知,1-男,2-女',
  `birthdate` date DEFAULT NULL COMMENT '出生日期(精度:天)',
  `icon` varchar(300) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '用户头像',
  `address` varchar(300) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '地址',
  `comment` varchar(1000) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '个性签名',
  `last_login_time` datetime DEFAULT NULL COMMENT '上次登录时间',
  `createtime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updatetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `user_id_index` (`user_id`) USING BTREE,
  UNIQUE KEY `user_account_phone` (`phone`) USING BTREE,
  KEY `idx_birthdate` (`birthdate`)
) ENGINE=InnoDB AUTO_INCREMENT=3333174 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='用户账号表';


1. 分析问题

优化前的sql
select * from eyas_account where birthdate between ‘2012-01-01’ and ‘2015-01-01’ order by birthdate desc, id desc limit 10000,100

对于这个sql,由于已经存在idx_birthdate这个索引。所以where和order的速度是比较快的,只是不能回表而已。但问题也就出在这里显示10000行后的100条数据,如果只是这100条数据进行回表速度应该很快。可是由于mysql的查询机制导致了mysql的执行与我们想的不太一样。

  • 这条语句是这样执行的
    • 1,where,根据where条件找到所有birthdate在 2012-01-01 和 2015-01-01 范围内的id。
    • 2,select,根据id去聚簇索引把其他所有字段拿出来。
    • 3,order,由于数据和索引的顺序一致不需要再排序。
    • 4,limit,把10100行数据的前10000条数据丢弃。
      问题出在第四步,分页的机制是拿到开始到当前页为止的所有数据,再把前面的数据丢弃。这就导致了第二步的回表操作不是100条而是10100条

2. 优化后的sql

目标:由于10100次回表中有10000此回表的数据是没用的,是多余操作,并且程序的资源消耗就在这里,如果能避免这些操作sql速度将会大大提升。
优化后的sql:
select * from eyas_account t1 right join
(
select id from eyas_account where birthdate between ‘2012-01-01’ and ‘2015-01-01’ order by birthdate desc, id desc limit 10000,100
) t2
on t1.id = t2.id

  • t2的子查询与优化前的查询其实很像,只是select的字段只有id(不需要回表),之后根据这100个id与eyas_account表进行表连接(只需要对100条数据进行回表)。
  • 这里需要考虑的点是1,left join还是right join?由于t2临时表只有100条数据,表连接最好用小表作为驱动表,这样循环的次数比较少。2,被连接的表上的列需要有索引,不然每次循环都是全表扫面。

3.比较两个sql

如何优化mysql分页查询_第1张图片
优化前的sql耗时:7.8秒。

如何优化mysql分页查询_第2张图片
优化后的sql耗时:1秒不到。

你可能感兴趣的:(mysql)