完成需求,不只在于速度,更要考虑质量。在规定的时间内完成需求的情况下,如果还有时间,应当想一下还能怎么优化,从性能、代码可读性、可扩展性层面考虑。进步就是这样一点点积累的。
昨天写的那篇有点小失误,在此修正一下。其实
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
,然后取多少条记录就行了。但是缺点就是不能跳页查询,前端只能是下一页、下一页,而且还需要浪费内存去缓存查询状态,在查询条件发生变化的情况下,还需要清掉缓存。
关于分页
limit
的问题,你们项目中是如何解决的呢?欢迎留言讨论。