其实mybatis-plus
提供的优化器做得非常好,它不仅仅只是在优化后的sql
之上加一层select count(1) from (原sql ) as total
,而是直接优化为select count(1) from... where...
,优化后的查询总数的sql
性能更优。
但是limit
的问题,mybatis-plus
优化器是解决不的。如果表的数据量非常大,我们除了优化查询总数的sql
之外,还是需要优化limit
的。
我看到过很多文章介绍说通过id
优化提升limit
性能,他们所举的例子都是单表查询。之所以能只查单表,是因为表的设计上已经通过冗余字段去掉多表连接查询了,这也是值得推荐且主流的优化大表查询性能的做法。
通过在设计表的时候加上冗余字段,去掉多表链接查询,使用id
优化提升limit
性能的例子如下:
SELECT a.字段
FROM table a
RIGHT JOIN
(
SELECT id -- 只查id列
FROM table
WHERE .....(非聚簇索引的条件查询)
LIMIT 1000000,20
) as b ON b.id = a.id
原SQL
是:
SELECT 需要获取的字段
FROM table
WHERE .....(非聚簇索引的条件查询)
LIMIT 1000000,20
由于非聚簇索引的叶子节点上面存储的是主键的id
,因此,如果select
只查主键,那么就不需要根据主键id
再到聚簇索引上面获取记录信息,而如果select
需要查询除主键外的其它字段信息,就必须要到聚簇索引上面取记录信息。
limit
是在查询结果基础上跳过多少条记录,也就是说,跳过多少条记录也是需要查询这些记录的信息的。如limit 10000,20
,那么就需要10020
次根据主键id
到聚簇索引上面取记录信息,而select id
就能减少这10020
次查询,因为非聚簇索引上面存的就是主键id
。前提是where
后面的条件必须确保都是走索引,在全表扫描下,任何优化都是徒劳。
根据这个思路,我优化了下最近做的一个需求的分页查询。虽然是需要多表连接查询,但where
部分条件是在主表上面筛选的,或者是通过优化手段,转为只在主表上面进行条件筛选的,因此也适合使用这种通过id
优化limit
性能的方案。
改造就是将原来的sql
拆分为两部分。第一部分是去掉select
选项,只保留ID
,然后去掉多表连接。第一部分主要就是保留where
后面的条件查询。根据条件查询,获取当前分页的记录的主键id
。在这一部完成分页。
先根据筛选条件完成分页查询获取当前页的id
:
select `ID` from 主表 where 查询条件 LIMIT 90,10
分页还是通过mybatis-plus
的分页插件完成。在查询ID
的mapper
方法上完成分页,获取总数信息。下面是myatis-plus
分页插件优化后的查询总数的sql
。
-- myatis-plus分页插件打印的sql
SELECT COUNT(1) FROM 主表 WHERE 查询条件
第二部分再是根据获取到的当前页的记录id
去查询需要的字段。包括子查询、连接查询等。是原sql
去掉where
条件查询之后,替换为ID in ()
的查询语句。
select 字段、子查询 from 主表、连接表 where 主表.ID in (上一次查询获取到的id)
这样优化之后,分页的性能就能提升了。
还有一些文章是介绍说通过id
多优化的,如:
..... where id>=(page * pageSize) limit pageSize;
我认为这种方案是不实用的,即使表的记录的id
是连续的,且中间没有记录被删除,但是根据条件筛选出来的记录的id
也是连续的吗?这种方案实现的分页查询结果一定是不准确的。
如果是遇到多表连接查询,且查询条件也需要根据Join
的表的某个字段做过滤的情况下,如何优化分页查询limit
带来的性能开销?
select a.*,b.* from a left join b on a.b_id=b.id where a.xx=? and b.xx=?
这真的不是一个好的表结构设计,实在想不出好的优化方案。
我想到的一种是,因为分页查询点击下一页时,要求查询条件是不能变的,且正常情况下也是不会变的,如果变了页码就应该重新从1
开始,可以使用内存缓存上一次查询的最大的id
,根据用户+
接口的维度去缓存,在获取下一页时,跳过上一次查询的最大id
,然后取多少条记录就行了。但是缺点就是不能跳页查询,前端只能是下一页、下一页,而且还需要浪费内存去缓存查询状态,在查询条件发生变化的情况下,还需要清掉缓存。