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

【磐维数据库】分页语句优化修正及优化案例

原创 磐维数据库 2025-08-28
234

前言

分页语句的需求是要按指定的顺序返回数据,分页才有价值,不然不同页面可能会出现重复数据或返回无序数据引起可读性降低

分页语句优化的目标是尽早返回所需数据,所以分页语句中要以nest loop 及索引访问为主

因要返回有序数据,所以要尽量通过索引直接返回已经排序过的数据来提升性能

案例模拟

同事发来一个慢查询,虽然客户咨询的第一目标不是性能问题,但通过PLAN及语句本身仍然发现了两个问题。首先脱敏案例环境模拟如下

TRUNCATE TABLE organizations;TRUNCATE TABLE stores;TRUNCATE TABLE phone_grades;TRUNCATE TABLE phone_inventory;

-- 1. 创建表结构(保持脱敏命名)CREATE TABLE organizations (

    org_id VARCHAR(20) PRIMARY KEY,

    org_name VARCHAR(100));

CREATE TABLE stores (

    store_id VARCHAR(20),

    region_code VARCHAR(10),

    store_name VARCHAR(100),

    PRIMARY KEY (region_code, store_id));

CREATE TABLE phone_grades (

    region_code VARCHAR(10),

    grade_code VARCHAR(20),

    resource_type VARCHAR(20),

    grade_name VARCHAR(100),

    PRIMARY KEY (region_code, grade_code, resource_type));

CREATE TABLE phone_inventory (

    phone_id VARCHAR(50) PRIMARY KEY,

    resource_type VARCHAR(20),

    grade_code VARCHAR(20),

    region_code VARCHAR(10),

    org_id VARCHAR(20),

    store_id VARCHAR(20),

    group_id VARCHAR(20),

    hlr_id VARCHAR(20),

    imsi_value VARCHAR(50),

    status VARCHAR(20),

    business_status VARCHAR(20),

    hlr_status VARCHAR(20),

    base_price NUMERIC(18,2),

    status_date TIMESTAMP,

    created_at TIMESTAMP,

    usage_count INTEGER,

    recycle_date TIMESTAMP,

    brand_id VARCHAR(20),

    notes TEXT,

    phone_class VARCHAR(20),

    class_name VARCHAR(50));

-- 2. 生成关联数据(关键修正点)-- 组织数据(确保有ORG.01前缀)INSERT INTO organizationsSELECT 'ORG.01' || LPAD(i::text, 3, '0'), '组织_' || iFROM generate_series(1, 50) i;

-- 门店数据(确保有R951区域)INSERT INTO storesSELECT 

    'STORE_' || LPAD(s::text, 4, '0'),

    CASE WHEN s <= 30 THEN 'R951' ELSE 'R'||(800+s%10) END,

    '门店_' || sFROM generate_series(1, 100) s;

-- 电话等级数据(确保有R951和R999区域)INSERT INTO phone_gradesSELECT 

    CASE WHEN g%5 = 0 THEN 'R999' ELSE 'R951' END,

    'GRADE_' || LPAD((g%20 + 1)::text, 2, '0'),

    'RS_TGSM',

    '等级_' || gFROM generate_series(1, 100) g;

-- 主表数据(强制关联)INSERT INTO phone_inventorySELECT

    'PHONE_' || LPAD(i::text, 7, '0'),

    'RS_TGSM',

    'GRADE_' || LPAD((i%20 + 1)::text, 2, '0'),

    'R951',

    'ORG.01' || LPAD((i%50 + 1)::text, 3, '0'),

    'STORE_' || LPAD((i%30 + 1)::text, 4, '0'),

    'GROUP_' || (i%5 + 1),

    'HLR_' || (i%3 + 1),

    'IMSI_' || i,

    CASE WHEN i%10 > 0 THEN 'ACTIVE' ELSE 'INACTIVE' END,

    CASE WHEN i%20 = 0 THEN 'WAITING_UNBIND' ELSE 'NORMAL' END,

    'STATUS_' || (i%3 + 1),

    (i%1000)*10,

    NOW() - (i%365)::int * '1 day'::interval,

    NOW() - (i%730)::int * '1 day'::interval,

    i%100,

    CASE WHEN i%5 = 0 THEN NOW() - (i%180)::int * '1 day'::interval ELSE NULL END,

    'BRAND_' || (i%5 + 1),

    '备注内容_' || i,

    'CLASS_' || (i%5 + 1),

    '类别_' || (i%5 + 1)FROM generate_series(1, 10000) i;  -- 生成1万条数据

-- 3. 创建索引CREATE INDEX idx_inv_region ON phone_inventory(region_code);CREATE INDEX idx_inv_org ON phone_inventory(org_id);CREATE INDEX idx_inv_grade ON phone_inventory(grade_code);CREATE INDEX phone_inventory_n1 on phone_inventory(region_code,resource_type,phone_id);

explain analyzeselect * from (

  select  row_.phone_id, 

  row_.resource_type, 

  row_.grade_code,  

  nvl((select a.grade_name from phone_grades a

  where a.region_code in  

  (select min(region_code) from phone_grades tr where tr.region_code in (row_.region_code,'R999')  and row_.grade_code = tr.grade_code and row_.resource_type = tr.resource_type)  

  and row_.grade_code = a.grade_code

  and row_.resource_type = a.resource_type) , 'N/A') grade_name,  

  nvl((select tc.class_name from  phone_inventory  tc

  where tc.region_code = row_.region_code and tc.phone_id = row_.phone_id), '') phone_class,  

  to_char(row_.region_code) region_code,  

  row_.org_id,  

  ( select org_name from organizations where org_id = row_.org_id ) org_name,  

  row_.store_id,   

  ( select store_name from stores where region_code = row_.region_code and store_id = row_.store_id ) store_name , 

  row_.group_id,  row_.hlr_id,  row_.imsi_value,  row_.status,  row_.business_status,  row_.hlr_status, 

   row_.base_price,  row_.status_date,  row_.created_at,  row_.usage_count,  row_.recycle_date, row_.brand_id , row_.notes , row_.phone_class , rownum rownum_

   

   from (

    select  t.phone_id, t.resource_type, t.grade_code,  t.region_code,  t.org_id, t.store_id,  t.group_id,  t.hlr_id,  t.imsi_value,  t.status, 

    t.business_status,  t.hlr_status,  to_char(nvl(t.base_price,0)/100) base_price,  to_char(t.status_date, 'yyyy-mm-dd hh24:mi:ss') status_date, 

    to_char(t.created_at, 'yyyy-mm-dd hh24:mi:ss') created_at,  to_char(t.usage_count) usage_count,  to_char(t.recycle_date, 'yyyy-mm-dd hh24:mi:ss') recycle_date,

    t.brand_id,  t.notes , t.phone_class  from phone_inventory t    

    where t.region_code = 'R951'  and t.org_id like 'ORG.01'||'%'  AND business_status <> 'WAITING_UNBIND'    and t.resource_type = 'RS_TGSM'   

    and t.phone_id like '%'   and rownum <= 100 order by t.phone_id  

    ) row_ where rownum <= 100) where rownum_ > 0;


模拟语句PLAN如下

QUERY PLAN

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Subquery Scan on "390573141__unnamed_subquery__" (cost=33.43..60.35 rows=1 width=1606) (actual time=7.332..7.332 rows=0 loops=1)

Filter: ("390573141__unnamed_subquery__".rownum_ > 0)

-> Limit (cost=33.43..60.34 rows=1 width=1104) (actual time=7.332..7.332 rows=0 loops=1)

-> Subquery Scan on row_ (cost=33.43..60.34 rows=1 width=1104) (actual time=7.330..7.330 rows=0 loops=1)

-> Sort (cost=33.43..33.44 rows=1 width=992) (actual time=7.329..7.329 rows=0 loops=1)

Sort Key: t.phone_id

Sort Method: quicksort Memory: 25kB

-> Bitmap Heap Scan on phone_inventory t (cost=25.50..33.42 rows=1 width=992) (actual time=7.320..7.320 rows=0 loops=1)

Recheck Cond: ((region_code)::text = 'R951'::text)

Filter: (((org_id)::text ~~ 'ORG.01%'::text) AND ((business_status)::text <> 'WAITING_UNBIND'::text) AND ((phone_id)::text ~~ '%'::text) AND (ROWNUM <= 100) AND ((resource_type)::text = 'RS_TGSM'::text))

-> BitmapAnd (cost=25.50..25.50 rows=2 width=0) (actual time=7.316..7.316 rows=0 loops=1)

-> Bitmap Index Scan on idx_inv_region (cost=0.00..12.00 rows=500 width=0) (actual time=7.164..7.164 rows=100000 loops=1)

Index Cond: ((region_code)::text = 'R951'::text)

-> Bitmap Index Scan on idx_inv_org (cost=0.00..13.25 rows=500 width=0) (actual time=0.011..0.011 rows=0 loops=1)

Index Cond: (((org_id)::text >= 'ORG.01'::text) AND ((org_id)::text < 'ORG.02'::text))

SubPlan 1

-> Hash Join (cost=1.05..2.08 rows=1 width=218) (Actual time: never executed)

Hash Cond: ((a.region_code)::text = (min((tr.region_code)::text)))

-> Seq Scan on phone_grades a (cost=0.00..1.01 rows=1 width=256) (Actual time: never executed)

Filter: (((row_.grade_code)::text = (grade_code)::text) AND ((row_.resource_type)::text = (resource_type)::text))

-> Hash (cost=1.04..1.04 rows=1 width=32) (Actual time: never executed)

Buckets: 0 Batches: 0 Memory Usage: 0kB

-> Aggregate (cost=1.02..1.03 rows=1 width=70) (Actual time: never executed)

-> Seq Scan on phone_grades tr (cost=0.00..1.02 rows=1 width=38) (Actual time: never executed)

Filter: (((region_code)::text = ANY ((ARRAY[row_.region_code, 'R999'::varchar])::text[])) AND ((row_.grade_code)::text = (grade_code)::text) AND ((row_.resource_type)::text = (resource_type)::text))

SubPlan 2

-> Index Scan using phone_inventory_pkey on phone_inventory tc (cost=0.00..8.27 rows=1 width=118) (Actual time: never executed)

Index Cond: ((phone_id)::text = (row_.phone_id)::text)

Filter: ((region_code)::text = (row_.region_code)::text)

SubPlan 3

-> Index Scan using organizations_pkey on organizations (cost=0.00..8.27 rows=1 width=218) (Actual time: never executed)

Index Cond: ((org_id)::text = (row_.org_id)::text)

SubPlan 4

-> Index Scan using idx_stores_composite on stores (cost=0.00..8.27 rows=1 width=218) (Actual time: never executed)

Index Cond: (((region_code)::text = (row_.region_code)::text) AND ((store_id)::text = (row_.store_id)::text))

Total runtime: 7.613 ms(36 rows)


写法谬误

在看性能问题之前,我们先纠正写法中的一个问题:语句中用了 order by t.phone_id 来排序,也用了分页模式,但未完全用对, 在order by 前多了个 rownum <= 100。我们用简单的表来模拟下看会发生什么情况。

有序数据如下:

orcl=> select * from emp order by ename;

empno | ename | job | mgr | hiredate | sal | comm | deptno 

-------+--------+-----------+------+---------------------+------+------+--------

7876 | ADAMS | CLERK | 7788 | 1987-05-23 00:00:00 | 1100 | | 20

7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 00:00:00 | 1600 | 300 | 30

7698 | BLAKE | MANAGER | 7839 | 1981-05-01 00:00:00 | 2850 | | 30

7782 | CLARK | MANAGER | 7839 | 1981-06-09 00:00:00 | 2450 | | 10

7902 | FORD | ANALYST | 7566 | 1981-12-03 00:00:00 | 3000 | | 20

7900 | JAMES | CLERK | 7698 | 1981-12-03 00:00:00 | 950 | | 30

7566 | JONES | MANAGER | 7839 | 1981-04-02 00:00:00 | 2975 | | 20

7839 | KING | PRESIDENT | | 1981-11-17 00:00:00 | 5000 | | 10

7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 00:00:00 | 1250 | 1400 | 30

7934 | MILLER | CLERK | 7782 | 1982-01-23 00:00:00 | 1300 | | 10

7788 | SCOTT | ANALYST | 7566 | 1987-04-19 00:00:00 | 3000 | | 20

7369 | SMITH | CLERK | 7902 | 1980-12-17 00:00:00 | 800 | | 20

7844 | TURNER | SALESMAN | 7698 | 1981-09-08 00:00:00 | 1500 | 0 | 30

7521 | WARD | SALESMAN | 7698 | 1981-02-22 00:00:00 | 1250 | 500 | 30(14 rows)


我们取前两行,预期应行到 7876 与 7499 两行

orcl=> select * from (select rownum as rn,t.* from (select * from emp where rownum <= 2 order by ename)t) where rn >=0 and rn <= 2;

rn | empno | ename | job | mgr | hiredate | sal | comm | deptno ----+-------+-------+----------+------+---------------------+------+------+--------

1 | 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 00:00:00 | 1600 | 300 | 30

2 | 7369 | SMITH | CLERK | 7902 | 1980-12-17 00:00:00 | 800 | | 20(2 rows)


以上结果不对,问题就在于 order by 前不该有 rownum <= 2,正确的方式应该嵌套后再加这个 rownum <= 2

orcl=> select * from (select rownum as rn,t.* from (select * from emp order by ename)t where rownum <= 2) where rn >=0 and rn <= 2;

rn | empno | ename | job | mgr | hiredate | sal | comm | deptno

 ----+-------+-------+----------+------+---------------------+------+------+--------

1 | 7876 | ADAMS | CLERK | 7788 | 1987-05-23 00:00:00 | 1100 | | 20

2 | 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 00:00:00 | 1600 | 300 | 30(2 rows)


使用limit 也可以

orcl=> select * from emp order by ename limit 2;

empno | ename | job | mgr | hiredate | sal | comm | deptno 

-------+-------+----------+------+---------------------+------+------+--------

7876 | ADAMS | CLERK | 7788 | 1987-05-23 00:00:00 | 1100 | | 20

7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 00:00:00 | 1600 | 300 | 30


原语句中是先取100行再排序,修正后会先排序再取100行,性能正常会因要排序这个动作降低。

explain analyzeselect * from (

select row_.phone_id,

row_.resource_type,

row_.grade_code,

nvl((select a.grade_name from phone_grades a

where a.region_code in

(select min(region_code) from phone_grades tr where tr.region_code in (row_.region_code,'R999') and row_.grade_code = tr.grade_code and row_.resource_type = tr.resource_type)

and row_.grade_code = a.grade_code

and row_.resource_type = a.resource_type) , 'N/A') grade_name,

nvl((select tc.class_name from phone_inventory tc

where tc.region_code = row_.region_code and tc.phone_id = row_.phone_id), '') phone_class,

to_char(row_.region_code) region_code,

row_.org_id,

( select org_name from organizations where org_id = row_.org_id ) org_name,

row_.store_id,

( select store_name from stores where region_code = row_.region_code and store_id = row_.store_id ) store_name ,

row_.group_id, row_.hlr_id, row_.imsi_value, row_.status, row_.business_status, row_.hlr_status,

row_.base_price, row_.status_date, row_.created_at, row_.usage_count, row_.recycle_date, row_.brand_id , row_.notes , row_.phone_class , rownum rownum_

from (

select t.phone_id, t.resource_type, t.grade_code, t.region_code, t.org_id, t.store_id, t.group_id, t.hlr_id, t.imsi_value, t.status,

t.business_status, t.hlr_status, to_char(nvl(t.base_price,0)/100) base_price, to_char(t.status_date, 'yyyy-mm-dd hh24:mi:ss') status_date,

to_char(t.created_at, 'yyyy-mm-dd hh24:mi:ss') created_at, to_char(t.usage_count) usage_count, to_char(t.recycle_date, 'yyyy-mm-dd hh24:mi:ss') recycle_date,

t.brand_id, t.notes , t.phone_class from phone_inventory t

where t.region_code = 'R951' and t.org_id like 'ORG.01'||'%' AND business_status <> 'WAITING_UNBIND' and t.resource_type = 'RS_TGSM'

and t.phone_id like '%' order by t.phone_id

) row_ where rownum <= 100) where rownum_ > 0;

QUERY PLAN

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Subquery Scan on "1342460730__unnamed_subquery__" (cost=8.32..54.00 rows=1 width=809) (actual time=52.377..53.623 rows=100 loops=1)

Filter: ("1342460730__unnamed_subquery__".rownum_ > 0)

-> Limit (cost=8.32..53.99 rows=1 width=274) (actual time=52.372..53.592 rows=100 loops=1)

-> Subquery Scan on row_ (cost=8.32..53.99 rows=1 width=274) (actual time=52.371..53.579 rows=100 loops=1)

-> Sort (cost=8.32..8.32 rows=1 width=148) (actual time=52.287..52.295 rows=100 loops=1)

Sort Key: t.phone_id

Sort Method: quicksort Memory: 3056kB

-> Index Scan using idx_inv_org on phone_inventory t (cost=0.00..8.31 rows=1 width=148) (actual time=0.099..44.632 rows=9500 loops=1)

Index Cond: (((org_id)::text >= 'ORG.01'::text) AND ((org_id)::text < 'ORG.02'::text))

Filter: (((org_id)::text ~~ 'ORG.01%'::text) AND ((business_status)::text <> 'WAITING_UNBIND'::text) AND ((phone_id)::text ~~ '%'::text) AND ((region_code)::text = 'R951'::text) AND ((resource_type)::text = 'RS_TGSM'::text))

Rows Removed by Filter: 500

SubPlan 2

-> Nested Loop (cost=12.54..20.84 rows=1 width=218) (actual time=0.466..0.466 rows=0 loops=100)

-> Result (cost=12.54..12.55 rows=1 width=0) (actual time=0.383..0.385 rows=100 loops=100)

InitPlan 1 (returns $3)

-> Limit (cost=0.00..12.54 rows=1 width=38) (actual time=0.343..0.343 rows=0 loops=100)

-> Index Only Scan using phone_grades_pkey on phone_grades tr (cost=0.00..12.54 rows=1 width=38) (actual time=0.221..0.221 rows=0 loops=100)

Index Cond: ((region_code IS NOT NULL) AND (region_code = ANY ((ARRAY[row_.region_code, 'R999'::varchar])::text[])) AND (grade_code = (row_.grade_code)::text) AND (res

ource_type = (row_.resource_type)::text))

Heap Fetches: 18

-> Index Scan using phone_grades_pkey on phone_grades a (cost=0.00..8.27 rows=1 width=256) (actual time=0.008..0.008 rows=0 loops=100)

Index Cond: (((region_code)::text = ($3)) AND ((row_.grade_code)::text = (grade_code)::text) AND ((row_.resource_type)::text = (resource_type)::text))

SubPlan 3

-> Index Scan using phone_inventory_pkey on phone_inventory tc (cost=0.00..8.27 rows=1 width=8) (actual time=0.233..0.241 rows=100 loops=100)

Index Cond: ((phone_id)::text = (row_.phone_id)::text)

Filter: ((region_code)::text = (row_.region_code)::text)

SubPlan 4

-> Index Scan using organizations_pkey on organizations (cost=0.00..8.27 rows=1 width=15) (actual time=0.147..0.153 rows=100 loops=100)

Index Cond: ((org_id)::text = (row_.org_id)::text)

SubPlan 5

-> Index Scan using idx_stores_composite on stores (cost=0.00..8.27 rows=1 width=9) (actual time=0.166..0.173 rows=100 loops=100)

Index Cond: (((region_code)::text = (row_.region_code)::text) AND ((store_id)::text = (row_.store_id)::text))

Total runtime: 53.957 ms(32 rows)


优化

我们前面讲过,要尽量返回有序数据来提升性能,既然修正后因排序降低了性能,那我们增加一排引,通过索引直接返回有序数据。

CREATE INDEX phone_inventory_n1 on phone_inventory(region_code,resource_type,phone_id);


加索引后优化器无法自动使用该索引

QUERY PLAN

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Subquery Scan on "1248933315__unnamed_subquery__" (cost=8.32..54.00 rows=1 width=809) (actual time=53.121..54.341 rows=100 loops=1)

Filter: ("1248933315__unnamed_subquery__".rownum_ > 0)

-> Limit (cost=8.32..53.99 rows=1 width=274) (actual time=53.115..54.307 rows=100 loops=1)

-> Subquery Scan on row_ (cost=8.32..53.99 rows=1 width=274) (actual time=53.114..54.298 rows=100 loops=1)

-> Sort (cost=8.32..8.32 rows=1 width=148) (actual time=53.032..53.036 rows=100 loops=1)

Sort Key: t.phone_id

Sort Method: quicksort Memory: 3056kB

-> Index Scan using idx_inv_org on phone_inventory t (cost=0.00..8.31 rows=1 width=148) (actual time=0.046..45.251 rows=9500 loops=1)

Index Cond: (((org_id)::text >= 'ORG.01'::text) AND ((org_id)::text < 'ORG.02'::text))

Filter: (((org_id)::text ~~ 'ORG.01%'::text) AND ((business_status)::text <> 'WAITING_UNBIND'::text) AND ((phone_id)::text ~~ '%'::text) AND ((region_code)::text = 'R951'::text) AND ((resource_type)::text = 'RS_TGSM'::text))

Rows Removed by Filter: 500

SubPlan 2

-> Nested Loop (cost=12.54..20.84 rows=1 width=218) (actual time=0.452..0.452 rows=0 loops=100)

-> Result (cost=12.54..12.55 rows=1 width=0) (actual time=0.367..0.370 rows=100 loops=100)

InitPlan 1 (returns $3)

-> Limit (cost=0.00..12.54 rows=1 width=38) (actual time=0.317..0.317 rows=0 loops=100)

-> Index Only Scan using phone_grades_pkey on phone_grades tr (cost=0.00..12.54 rows=1 width=38) (actual time=0.195..0.195 rows=0 loops=100)

Index Cond: ((region_code IS NOT NULL) AND (region_code = ANY ((ARRAY[row_.region_code, 'R999'::varchar])::text[])) AND (grade_code = (row_.grade_code)::text) AND (res

ource_type = (row_.resource_type)::text))

Heap Fetches: 0

-> Index Scan using phone_grades_pkey on phone_grades a (cost=0.00..8.27 rows=1 width=256) (actual time=0.008..0.008 rows=0 loops=100)

Index Cond: (((region_code)::text = ($3)) AND ((row_.grade_code)::text = (grade_code)::text) AND ((row_.resource_type)::text = (resource_type)::text))

SubPlan 3

-> Index Scan using phone_inventory_pkey on phone_inventory tc (cost=0.00..8.27 rows=1 width=8) (actual time=0.236..0.245 rows=100 loops=100)

Index Cond: ((phone_id)::text = (row_.phone_id)::text)

Filter: ((region_code)::text = (row_.region_code)::text)

SubPlan 4

-> Index Scan using organizations_pkey on organizations (cost=0.00..8.27 rows=1 width=15) (actual time=0.126..0.137 rows=100 loops=100)

Index Cond: ((org_id)::text = (row_.org_id)::text)

SubPlan 5

-> Index Scan using idx_stores_composite on stores (cost=0.00..8.27 rows=1 width=9) (actual time=0.152..0.161 rows=100 loops=100)

Index Cond: (((region_code)::text = (row_.region_code)::text) AND ((store_id)::text = (row_.store_id)::text))

Total runtime: 54.679 ms(32 rows)


我们通过hint来应用索引

/*+ indexscan(t phone_inventory_n1) */

explain analyzeselect * from (

select row_.phone_id,

row_.resource_type,

row_.grade_code,

nvl((select a.grade_name from phone_grades a

where a.region_code in

(select min(region_code) from phone_grades tr where tr.region_code in (row_.region_code,'R999') and row_.grade_code = tr.grade_code and row_.resource_type = tr.resource_type)

and row_.grade_code = a.grade_code

and row_.resource_type = a.resource_type) , 'N/A') grade_name,

nvl((select tc.class_name from phone_inventory tc

where tc.region_code = row_.region_code and tc.phone_id = row_.phone_id), '') phone_class,

to_char(row_.region_code) region_code,

row_.org_id,

( select org_name from organizations where org_id = row_.org_id ) org_name,

row_.store_id,

( select store_name from stores where region_code = row_.region_code and store_id = row_.store_id ) store_name ,

row_.group_id, row_.hlr_id, row_.imsi_value, row_.status, row_.business_status, row_.hlr_status,

row_.base_price, row_.status_date, row_.created_at, row_.usage_count, row_.recycle_date, row_.brand_id , row_.notes , row_.phone_class , rownum rownum_

from (

select /*+ indexscan(t phone_inventory_n1) */ t.phone_id, t.resource_type, t.grade_code, t.region_code, t.org_id, t.store_id, t.group_id, t.hlr_id, t.imsi_value, t.status,

t.business_status, t.hlr_status, to_char(nvl(t.base_price,0)/100) base_price, to_char(t.status_date, 'yyyy-mm-dd hh24:mi:ss') status_date,

to_char(t.created_at, 'yyyy-mm-dd hh24:mi:ss') created_at, to_char(t.usage_count) usage_count, to_char(t.recycle_date, 'yyyy-mm-dd hh24:mi:ss') recycle_date,

t.brand_id, t.notes , t.phone_class from phone_inventory t

where t.region_code = 'R951' and t.org_id like 'ORG.01'||'%' AND business_status <> 'WAITING_UNBIND' and t.resource_type = 'RS_TGSM'

and t.phone_id like '%' order by t.phone_id

) row_ where rownum <= 100) where rownum_ > 0;


得到plan如下:

QUERY PLAN

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Subquery Scan on "1716438901__unnamed_subquery__" (cost=0.00..1347.02 rows=1 width=809) (actual time=0.175..1.988 rows=100 loops=1)

Filter: ("1716438901__unnamed_subquery__".rownum_ > 0)

-> Limit (cost=0.00..1347.00 rows=1 width=274) (actual time=0.172..1.961 rows=100 loops=1)

-> Subquery Scan on row_ (cost=0.00..1347.00 rows=1 width=274) (actual time=0.171..1.948 rows=100 loops=1)

-> Index Scan using phone_inventory_n1 on phone_inventory t (cost=0.00..1301.34 rows=1 width=148) (actual time=0.122..0.599 rows=100 loops=1)

Index Cond: (((region_code)::text = 'R951'::text) AND ((resource_type)::text = 'RS_TGSM'::text))

Filter: (((org_id)::text ~~ 'ORG.01%'::text) AND ((business_status)::text <> 'WAITING_UNBIND'::text) AND ((phone_id)::text ~~ '%'::text))

Rows Removed by Filter: 5

SubPlan 2

-> Nested Loop (cost=12.54..20.84 rows=1 width=218) (actual time=0.510..0.510 rows=0 loops=100)

-> Result (cost=12.54..12.55 rows=1 width=0) (actual time=0.413..0.419 rows=100 loops=100)

InitPlan 1 (returns $3)

-> Limit (cost=0.00..12.54 rows=1 width=38) (actual time=0.372..0.372 rows=0 loops=100)

-> Index Only Scan using phone_grades_pkey on phone_grades tr (cost=0.00..12.54 rows=1 width=38) (actual time=0.238..0.238 rows=0 loops=100)

Index Cond: ((region_code IS NOT NULL) AND (region_code = ANY ((ARRAY[row_.region_code, 'R999'::varchar])::text[])) AND (grade_code = (row_.grade_code)::text) AND (res

ource_type = (row_.resource_type)::text))

Heap Fetches: 0

-> Index Scan using phone_grades_pkey on phone_grades a (cost=0.00..8.27 rows=1 width=256) (actual time=0.009..0.009 rows=0 loops=100)

Index Cond: (((region_code)::text = ($3)) AND ((row_.grade_code)::text = (grade_code)::text) AND ((row_.resource_type)::text = (resource_type)::text))

SubPlan 3

-> Index Scan using phone_inventory_pkey on phone_inventory tc (cost=0.00..8.27 rows=1 width=8) (actual time=0.244..0.258 rows=100 loops=100)

Index Cond: ((phone_id)::text = (row_.phone_id)::text)

Filter: ((region_code)::text = (row_.region_code)::text)

SubPlan 4

-> Index Scan using organizations_pkey on organizations (cost=0.00..8.27 rows=1 width=15) (actual time=0.132..0.142 rows=100 loops=100)

Index Cond: ((org_id)::text = (row_.org_id)::text)

SubPlan 5

-> Index Scan using idx_stores_composite on stores (cost=0.00..8.27 rows=1 width=9) (actual time=0.163..0.171 rows=100 loops=100)

Index Cond: (((region_code)::text = (row_.region_code)::text) AND ((store_id)::text = (row_.store_id)::text))

Total runtime: 2.181 ms(29 rows)


结论

优化问题可以找人咨询,但语句本身返回的数据是可以自己测试的,语句写完后要细心测试,注意返回结果是否与预期一致。

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

评论