首页 > 数据库 >MySQL查询慢,四种方案帮你解决!

MySQL查询慢,四种方案帮你解决!

时间:2023-12-26 11:00:45浏览次数:45  
标签:10 -- 查询 limit 四种 MySQL id order select

一般分页

在系统中需要进行分页操作时,我们通常会使用 LIMIT 加上偏移量的方式实现,语法格式如下。

SELECT ... FROM ... WHERE ... ORDER BY ... LIMIT ...

在有对应索引的情况下,这种方式一般效率还不错。但它存在一个让人头疼的问题,在偏移量非常大的时候,也就是翻页到很靠后的页面时,查询速度会变得越来越慢。

我们来演示一下。

先创建一个订单表 t_order。

CREATE TABLE `t_order` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `order_no` varchar(32) NOT NULL COMMENT '订单号',
  `user_id` varchar(20) NOT NULL COMMENT '用户ID',
  `amount` decimal(18,2) NOT NULL COMMENT '订单金额',
  `order_status` tinyint(4) NOT NULL COMMENT '订单状态:0新建 1处理中 2成功 3失败',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_order_no` (`order_no`) USING BTREE COMMENT '订单号唯一索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

往表中插入1100w 条数据。( t1 是一个有100条数据的表,这里我利用笛卡尔乘积的方式插入1100w条数据)

set @N=0;
INSERT INTO t_order(`order_no`,`user_id`,`amount`,`order_status`,`create_time`)
select
 CONCAT("APP", @N:=@N+1),
 CONCAT("USER_ID_", @N+1),
 @N%10000,
 @N%4,
 NOW()
 from t1 a, t1 b, t1 c, t1 d
LIMIT 11000000;

我们看下,如下这些查询花费的时间。

select * from t_order order by id limit 0, 10;  
select * from t_order order by id limit 10000, 10; 
select * from t_order order by id limit 100000, 10; 
select * from t_order order by id limit 1000000, 10;
select * from t_order order by id limit 10000000, 10;

执行时间如下:

-- 0.002
-- 0.045
-- 0.069
-- 0.517
-- 4.134

同样是只查询10条数据,最开始的时候查询花费 0.002s,而到最后,查询花费了 4.134s。 这是什么原因呢? 这是因为查询时 MySQL 并不是跳过 OFFSET 行,而是取 OFFSET+N 行,然后放弃前 OFFSET 行,最后返回 N 行,当 OFFSET 特别大的时候,效率就非常的低下。

拿 limit 10000, 10 这条语句来说明一下, MySQL在执行这条查询的时候,需要查询 10010 (10000 + 10) 条记录,然后只返回最后 10 条,并将前面的 10000 条记录抛弃,这样当翻页越靠后时,代价就变得越来越高。

知道问题所在了,那有什么办法可以优化,解决这个问题呢?

1、优化一:记录位置,避免使用 OFFSET

首先获取第一页的结果:

select * from t_order limit 10;

假如上边返回的是 id 为1 ~ 10的记录,我们将 10 这个值记住,下一页查询就可以直接从 10 这个值开始。

select * from t_order where id > 10 limit 10;

这样做,无论翻页到多少页,性能都会很好:

elect * from t_order limit 10;      
select * from t_order where id > 10000 limit 10; 
select * from t_order where id > 100000 limit 10; 
select * from t_order where id > 1000000 limit 10; 
select * from t_order where id > 10000000 limit 10;

执行时间如下:

-- 0.003
-- 0.005
-- 0.002
-- 0.002
-- 0.002

而如果我们当前记录的 id 值为 10000,我们想查上一页怎么办呢?返回去查一下即可:

select * from t_order where id <= 10000 order by id desc limit 10,10;

这种优化方式,可以实现上一页、下一页这种的分页。但如果想要实现跳转到指定页码的话,就需要保证 id 连续不中断,再通过计算找到准确的位置。

2、优化二:计算边界值,转换为已知位置的查询

如果 id 连续不中断,我们就可以计算出每一页的边界值,让 MySQL 根据边界值进行范围扫描,查出数据。

select * from t_order where id between 0 and 10;
select * from t_order where id between 10000 and 10010;
select * from t_order where id between 100000 and 100010;
select * from t_order where id between 1000000 and 1000010;
select * from t_order where id between 10000000 and 10000010;

执行时间如下:

-- 0.001
-- 0.002
-- 0.002
-- 0.001
-- 0.001

3、优化三:使用索引覆盖+子查询优化

先在索引树中找到开始位置的 id 值,再根据找到的 id 值查询行数据。

select * from t_order where id >= (select id from t_order order by id limit 0, 1) order by id limit 10;
select * from t_order where id >= (select id from t_order order by id limit 10000, 1) order by id limit 10;
select * from t_order where id >= (select id from t_order order by id limit 100000, 1) order by id limit 10;
select * from t_order where id >= (select id from t_order order by id limit 1000000, 1) order by id limit 10;
select * from t_order where id >= (select id from t_order order by id limit 10000000, 1) order by id limit 10;

执行时间如下:

-- 0.007
-- 0.009
-- 0.047
-- 0.332
-- 2.822

可以看到,这种优化方式也可以提升查询速度。这其实是利用了索引覆盖的如下好处:

  • 索引文件不包含行数据的所有信息,故其大小远小于数据文件,因此可以减少大量的IO操作。
  • 索引覆盖只需要扫描一次索引树,不需要回表扫描行数据,所以性能比回表查询要高。

4、优化四:使用索引覆盖+连接查询优化

这种优化方式跟 优化三 原理一样。也是先在索引上进行分页查询,当找到 id 后,再统一通过 JOIN 关联查询得到最终需要的数据详情。

select * from t_order a Join (select id from t_order order by id limit 0, 10) b ON a.id = b.id;  
select * from t_order a Join (select id from t_order order by id limit 10000, 10) b ON a.id = b.id; 
select * from t_order a Join (select id from t_order order by id limit 100000, 10) b ON a.id = b.id; 
select * from t_order a Join (select id from t_order order by id limit 1000000, 10) b ON a.id = b.id;
select * from t_order a Join (select id from t_order order by id limit 10000000, 10) b ON a.id = b.id;

执行时间如下:

-- 0.001
-- 0.023
-- 0.028
-- 0.348
-- 2.955

标签:10,--,查询,limit,四种,MySQL,id,order,select
From: https://blog.51cto.com/u_16357126/8980184

相关文章

  • MYSQL 事务
    事务定义事务就是一组数据库操作序列(包含一个或多个sql操作命令),事务会把所有操作看作是一个不可分割的整体向数据库系统提交或撤销操作,所有操作要么都执行,要么都不执行。事务是一个不可分割的工作逻辑单元,在数据库系统上执行并发操作时,事务是最小的控制单元。事务适用于多用户......
  • Power BI - 5分钟学习合并查询
    每天5分钟,今天介绍PowerBI合并查询。什么是合并查询?合并查询操作基于一列或多列的匹配值将两个现有表联接在一起。可以选择使用不同类型的联接,具体取决于所需的输出。举例:导入Sales表和Product表,请看样例内容。(Excel数据源导入请参考每天5分钟第一天)。操作步骤:1,点击【S......
  • Mysql如何查看操作记录
     分为文件记录日志和数据库记录日志两种方式,效果相同一、文件记录数据库操作日志首先进入mysql输入指令showvariableslike'gen%';general_log是开启还是关闭状态,以及这个帐号的general_log文件在哪如果没有开启,请先设置开启setglobalgeneral_log=ON;查看log:cat/目录......
  • MySQL的事务(看看也许有帮助呢)
    MySQL的事务一、事务的概念在MySQL中,只有InnoDB存储引擎才支持事务。事务的处理用来维护数据库数据的完整性,保证同一个事务里的一批SQL语句,要么全部执行,要么全部不执行。事务用来管理DDL、DML、DCL操作,比如insert、update、delete语句,默认自动提交,即执行SQL语句后会马上执行co......
  • MySql之json_extract函数处理json字段
    转自:链接:https://juejin.cn/post/7103482347894358046 MySql之json_extract函数处理json字段在db中存储json格式的数据,相信大家都或多或少的使用过,那么在查询这个json结构中的数据时,有什么好的方法么?取出String之后再代码中进行解析?接下来本文将介绍一下Mysql5.7+之后提供的......
  • 2数据库之Mysql基本操作
                                                                                                      数据库基本类型、基本操作一.......
  • MySQL-索引数据结构
    BTreeB-树即B树。指的是BalanceTree,也就是平衡树,平衡树是一颗查找树,并且所有叶子节点位于同一层。每个结点存储M/2到M个关键字,非叶子结点存储指向关键字范围的子结点。所有关键字在整颗树中出现,且只出现一次,非叶子结点可以命中。B+Tree是B树的一种变形,它是基于B......
  • MySQL索引-索引结构
    索引是什么索引是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查询算法,这种数据结构就是索引。优缺点:优点:提高数据检索效率,降低数据库的IO成本通......
  • mysql主从同步原理
    (1)master服务器将数据的改变记录二进制binlog日志,当master上的数据发生改变时,则将其改变写入二进制日志中(2)slave服务器会在一定时间间隔内对master二进制日志进行探测其是否发生改变,如果发生改变,则开始一个IOThread请求master二进制事件(3)同时主节点大每个O线程启动一个du......
  • 嘿,我使用了mp的自连接+分页查询之后,再使用条件查询居然失效了。
    原因:我想通过自连接查询将一个表的两条数据放在一起,为此我重写了mp的分页查询IPage<Indi>selectIndiShow(IPage<Indi>page,@Param(Constants.WRAPPER)QueryWrapper<Indi>wrapper);我又在xml中这样写mapper<?xmlversion="1.0"encoding="UTF-8"?><!DOCTY......