暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MogDB一次分页语句优化

原创 由迪 2023-11-27
102

原作者:师庆栋

  • 版本
    • OS
  • 版本
    • OS版本
    • DB版本
  • 背景
  • 案例模拟
    • 所用数据
    • 原始语句
      • 解析
    • 尝试1
      • 解析
    • 尝试2

版本

OS

  • 版本
    • OS
  • 版本
    • OS版本
    • DB版本
  • 背景
  • 案例模拟
    • 所用数据
    • 原始语句
      • 解析
    • 尝试1
      • 解析
    • 尝试2

版本

OS版本

[omm@mogdb-01 ~]$ lsb_release -a
LSB Version:	:core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch
Distributor ID:	CentOS
Description:	CentOS Linux release 7.6.1810 (Core) 
Release:	7.6.1810
Codename:	Core

DB版本

stone=> select version();
                                                                     version                                                                      
--------------------------------------------------------------------------------------------------------------------------------------------------
 (MogDB 3.0.1 build eb4f387f) compiled at 2022-08-19 18:14:41 commit 0 last mr   on x86_64-unknown-linux-gnu, compiled by g++ (GCC) 7.3.0, 64-bit
(1 row)

背景

为客户优化Oracle查询语句时,遇到一个分页语句。想测试在MogDB中的场景,因此在MogDB中做了模拟优化

案例模拟

所用数据

模拟数据来自Oracle中的字典表 dba_objects dba_users

原始语句

explain analyze
select * From
(
select rownum as rn,t.*
  from
(
select u.username,u.user_id,u.account_status,o.object_name,o.object_type
  from t_users u
  left join 
  (
  select o.owner,
         o.object_name,
         o.object_type,
         row_number() over (partition by o.owner order by o.object_id) as rn
    from t_objects o
  )o on (o.rn = 1 and o.owner = u.username)
 order by (case when u.account_status= 'LOCKED' then 1 else 0 end),u.username
)t
where rownum <= 10
)
where rn >= 6;

                                                                            QUERY PLAN                                                                            
------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Subquery Scan on __unnamed_subquery__  (cost=10615.72..10615.97 rows=3 width=439) (actual time=823.558..823.563 rows=5 loops=1)
   Filter: (__unnamed_subquery__.rn >= 6)
   Rows Removed by Filter: 5
   ->  Limit  (cost=10615.72..10615.84 rows=10 width=431) (actual time=823.539..823.548 rows=10 loops=1)
         ->  Subquery Scan on t  (cost=10615.72..10620.27 rows=364 width=431) (actual time=823.537..823.544 rows=10 loops=1)
               ->  Sort  (cost=10615.72..10616.63 rows=364 width=431) (actual time=823.536..823.536 rows=10 loops=1)
                     Sort Key: (CASE WHEN ((u.account_status)::text = 'LOCKED'::text) THEN 1 ELSE 0 END), u.username
                     Sort Method: quicksort  Memory: 29kB
                     ->  Hash Right Join  (cost=8226.75..10600.23 rows=364 width=431) (actual time=700.380..823.409 rows=42 loops=1)
                           Hash Cond: ((o.owner)::text = (u.username)::text)
                           ->  Subquery Scan on o  (cost=8216.08..10584.03 rows=364 width=47) (actual time=700.154..821.876 rows=30 loops=1)
                                 Filter: (o.rn = 1)
                                 Rows Removed by Filter: 72830
                                 ->  WindowAgg  (cost=8216.08..9673.28 rows=72860 width=53) (actual time=700.140..800.309 rows=72860 loops=1)
                                       ->  Sort  (cost=8216.08..8398.23 rows=72860 width=53) (actual time=700.123..711.265 rows=72860 loops=1)
                                             Sort Key: o.owner, o.object_id
                                             Sort Method: quicksort  Memory: 12523kB
                                             ->  Seq Scan on t_objects o  (cost=0.00..2331.60 rows=72860 width=53) (actual time=0.006..33.842 rows=72860 loops=1)
                           ->  Hash  (cost=10.30..10.30 rows=30 width=388) (actual time=0.057..0.057 rows=42 loops=1)
                                  Buckets: 32768  Batches: 1  Memory Usage: 3kB
                                 ->  Seq Scan on t_users u  (cost=0.00..10.30 rows=30 width=388) (actual time=0.010..0.019 rows=42 loops=1)
 Total runtime: 823.756 ms
(22 rows)

Time: 825.316 ms

解析

可以看到该查询中t_users为主数据,返回行数与t_objects无关,而t_objects占用了大量的成本,如果能减少对t_objects的访问,肯定可以大大提高性能。

尝试1

尝试把t_objects放到标量子查询中

explain analyze
select t.* From
(
select rownum as rn,username,user_id,account_status,
       trim((row_o->'object_name')::varchar,'"') as object_name,
       trim((row_o->'object_type')::varchar,'"') as object_type
  from
(
select u.username,u.user_id,u.account_status,
       (
       select row_to_json(o)
         from (select owner,object_name,object_type,object_id from t_objects) o
        where o.owner = u.username
        order by o.object_id 
        limit 1
       )as row_o
  from t_users u
 order by (case when u.account_status= 'LOCKED' then 1 else 0 end),u.username
)t
where rownum <= 10
)t
where rn >= 6;
                                                                                  QUERY PLAN                                                                            
      
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------
 Subquery Scan on t  (cost=53140.77..53141.22 rows=3 width=460) (actual time=649.656..649.709 rows=5 loops=1)
   Filter: (t.rn >= 6)
   Rows Removed by Filter: 5
   ->  Limit  (cost=53140.77..53141.10 rows=10 width=420) (actual time=649.591..649.702 rows=10 loops=1)
         ->  Subquery Scan on t  (cost=53140.77..53141.75 rows=30 width=420) (actual time=649.590..649.698 rows=10 loops=1)
               ->  Sort  (cost=53140.77..53140.85 rows=30 width=388) (actual time=649.576..649.579 rows=10 loops=1)
                     Sort Key: (CASE WHEN ((u.account_status)::text = 'LOCKED'::text) THEN 1 ELSE 0 END), u.username
                     Sort Method: quicksort  Memory: 32kB
                     ->  Seq Scan on t_users u  (cost=0.00..53140.04 rows=30 width=388) (actual time=554.276..649.433 rows=42 loops=1)
                           SubPlan 1
                             ->  Limit  (cost=1770.99..1770.99 rows=1 width=53) (actual time=649.265..649.275 rows=29 loops=42)
                                   ->  Sort  (cost=1770.99..1777.27 rows=2512 width=53) (actual time=649.192..649.192 rows=29 loops=42)
                                         Sort Key: t_objects.object_id
                                         Sort Method: top-N heapsort  Memory: 25kB
                                         ->  Bitmap Heap Scan on t_objects  (cost=47.72..1758.43 rows=2512 width=53) (actual time=19.525..591.155 rows=61340 loops=42)
                                               Recheck Cond: ((owner)::text = (u.username)::text)
                                               Heap Blocks: exact=1643
                                               ->  Bitmap Index Scan on t_ojects_owner  (cost=0.00..47.09 rows=2512 width=0) (actual time=18.345..18.345 rows=61340 loop
s=42)
                                                     Index Cond: ((owner)::text = (u.username)::text)
 Total runtime: 650.267 ms
(20 rows)

Time: 652.380 ms

解析

可以看到使用标量子查询对性能提升很有限,MogDB对于标量子查询中的Limit解析不友好。
我们可以单独看下Limit语句

stone->  select * into v_objects from t_objects o where owner = 'SYS' order by object_id limit 1;
                                                                  QUERY PLAN                                                                   
-----------------------------------------------------------------------------------------------------------------------------------------------
 Insert on v_objects  (cost=0.00..0.16 rows=1 width=566) (actual time=0.147..0.149 rows=1 loops=1)
   ->  Limit  (cost=0.00..0.15 rows=1 width=566) (actual time=0.040..0.040 rows=1 loops=1)
         ->  Index Scan using t_objects_n1 on t_objects o  (cost=0.00..8051.15 rows=52054 width=566) (actual time=0.038..0.038 rows=1 loops=1)
               Index Cond: ((owner)::text = 'SYS'::text)
 Total runtime: 0.286 ms
(5 rows)

Time: 18.062 ms

可以看到,单独的Limit语句中t_objects只访问了一行,前面的 rows=72860 显然无法发会limit的优势

尝试2

既然标量子查询中的Limit解析不好,我们尝试把标量子查询改用函数来代替

CREATE OR REPLACE FUNCTION f_get_object(p_owner scott.t_objects.owner%type)
  RETURN scott.t_objects%rowtype is
  v_objects scott.t_objects%rowtype;
begin
  select * into v_objects
    from t_objects o
   where owner = p_owner
   order by object_id 
   limit 1;
  RETURN v_objects;
  exception when others then
  RETURN null;
END;
explain analyze
select t.*     
  From
(
select rownum as rn,username,user_id,account_status,(o::t_objects).object_name,(o::t_objects).object_type
  from
(
select u.username,u.user_id,u.account_status,f_get_object(u.username) as o
  from t_users u
 order by (case when u.account_status= 'LOCKED' then 1 else 0 end),u.username
)t
where rownum <= 10
)t
where rn >= 6;
                                                           QUERY PLAN                                                           
--------------------------------------------------------------------------------------------------------------------------------
 Subquery Scan on t  (cost=18.61..18.86 rows=3 width=734) (actual time=9.564..9.568 rows=5 loops=1)
   Filter: (t.rn >= 6)
   Rows Removed by Filter: 5
   ->  Limit  (cost=18.61..18.74 rows=10 width=420) (actual time=9.548..9.559 rows=10 loops=1)
         ->  Subquery Scan on t  (cost=18.61..18.99 rows=30 width=420) (actual time=9.547..9.558 rows=10 loops=1)
               ->  Sort  (cost=18.61..18.69 rows=30 width=388) (actual time=9.544..9.549 rows=10 loops=1)
                     Sort Key: (CASE WHEN ((u.account_status)::text = 'LOCKED'::text) THEN 1 ELSE 0 END), u.username
                     Sort Method: quicksort  Memory: 34kB
                     ->  Seq Scan on t_users u  (cost=0.00..17.88 rows=30 width=388) (actual time=1.745..9.372 rows=42 loops=1)
 Total runtime: 9.648 ms
(10 rows)

Time: 10.959 ms

优化成功,希望MogDB新版本对标量子查询的解析能有提升,不再需要借助自定义函数

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

文章被以下合辑收录

评论