千家信息网

排序,分组和集合操作

发表于:2025-02-05 作者:千家信息网编辑
千家信息网最后更新 2025年02月05日,排序操作排序是计算机承担的最基本操作之一,尤其是在数据库处理领域,oracle也不例外。可能需要oracle排序数据的操作包括以下几种;(1)创建一个索引(2)通过group by,unique或di
千家信息网最后更新 2025年02月05日排序,分组和集合操作
  1. 排序操作

    排序是计算机承担的最基本操作之一,尤其是在数据库处理领域,oracle也不例外。可能需要oracle排序数据的操作包括以下几种;

    (1)创建一个索引

(2)通过group by,unique或distinct关键字对数据进行分组或聚合

(3)因为使用order by子句使得数据按照排好的顺序返回

(4)使用排序合并方法联结表或结果集

(5)使用集合操作union,intersect或minus

(6)执行特定的子查询

查询可能需要可观的资源,具体说明如下:

CPU总是要消耗的。需要CPU的数量和需要排序的结果集大小成正比。

oracle分配一块内存用来排序。这块内存来自于程序全局区(PGA).可用的PGA内存总量通常取决于MEMORY_TARGET或PGA_AGGREGATE_TARGET的值

如果内存区域不足以完成排序,oracle会分配一个或多个临时段到临时表空间。这个叫作磁盘排序。磁盘排序会增加额外的开销,包括在临时段中分配空间和把数据写入临时表空间以及磁盘读出的IO消耗。

1.1 最优,一次或多次排序

可供执行排序操作的内存量是决定排序性能的最重要的因数。如果供排序使用的内存量足以使排序在内存中完成,则性能将是最佳的,实际上oracle将这种类型的排序操作称为最优排序。

如果没有足够的内存,当执行排序操作时,oracle必须读写临时段。在一次排序中,orale仅需要写入(然后读回)一个短。在多次排序中,oracle需要写并读回多个排序段。需要的排序次数越多。排序中所包含的IO就越多,排序性能也就越差。

排序所需要的IO随着排序次数的增加而急剧增长,排序次数最终成为影响sql性能的主要因素。

1.2 衡量排序活动

现在让我们来看看如何能够衡量排序活动

优化器计算它认为将需要的内存量,并且你可以通过检查dbms_xplan输出的tempSpc字段看到这些(即使在你执行sql之前)

SQL> explain plan for select * from customers order by cust_last_name,cust_first_name,cust_year_of_birth;

Explained.

SQL> select * from table(dbms_xplan.display());

PLAN_TABLE_OUTPUT

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

Plan hash value: 2792773903

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

| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |

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

| 0 | SELECT STATEMENT | | 55500 | 9810K| | 2609 (1)| 00:00:02 |

| 1 | SORT ORDER BY | | 55500 | 9810K| 12M| 2609 (1)| 00:00:02 |

| 2 | TABLE ACCESS FULL| CUSTOMERS | 55500 | 9810K| | 405 (1)| 00:00:01 |

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

9 rows selected.

DBMS_XPLAN产生的值是一个估计值,但是它可以给你一个对于所需内存的大体感受。

V$SQL_WORKAREA视图包含于数据库中已发生的内存使用及排序行为的相关的详细统计信息。

WITH sql_workarea AS

(SELECT sql_id || '-' || child_number sql_id_child,

operation_type operation,

last_execution last_exec,

round(active_time / 1000000, 2) seconds,

optimal_executions || '/' || onepass_executions || '/' ||

multipasses_executions olm,

substr(sql_text, 1, 155) sql_text,

rank() over(ORDER BY active_time DESC) ranking

FROM v$sql_workarea

JOIN v$sql

USING (sql_id, child_number))

SELECT sql_id_child "SQL_ID -CHILD",

seconds,

operation,

last_exec,

olm "O/1/M",

sql_text

FROM sql_workarea

WHERE ranking <= 3

ORDER BY ranking;

1.3 跟踪排序活动

SQL> oradebug event 10032 trace name context forever,level 12;

---- Sort Parameters ------------------------------

sort_area_size 12691456

sort_area_retained_size 12691456

sort_multiblock_read_count 1

max intermediate merge width 773

*** 2014-12-15 13:34:36.765

sorcls: sorp 0x7fd8df3ea1c8

---- Sort Statistics ------------------------------

Input records 55500

Output records 55500

Total number of comparisons performed 656484

Comparisons performed by in-memory sort 656484

Total amount of memory used 12691456

Uses version 2 sort

---- End of Sort Statistics -----------------------

1.4 使用索引规避排序

如果再order by子句中的部分或全部列上存在索引,oracle有可能使用索引来按照要求的顺序获取记录,因此也避免了排序操作。

假如索引是出现于order by子句里的列相同的列上,oracle可以直接从索引中按照索引排序的顺序读取记录。然而,按键的顺序读取记录需要一块接一块地全扫描索引叶子块。虽然快速全扫描比常规的全索引扫描高效得多,但是快速全扫描无法按索引顺序返回记录。因此也不能用来避免排序操作。

SQL> select * from customers order by cust_last_name,cust_first_name,cust_year_of_birth;

55500 rows selected.

Execution Plan

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

Plan hash value: 2792773903

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

| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |

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

| 0 | SELECT STATEMENT | | 55500 | 9810K| | 2609 (1)| 00:00:02 |

| 1 | SORT ORDER BY | | 55500 | 9810K| 12M| 2609 (1)| 00:00:02 |

| 2 | TABLE ACCESS FULL| CUSTOMERS | 55500 | 9810K| | 405 (1)| 00:00:01 |

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

Statistics

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

1 recursive calls

0 db block gets

1456 consistent gets

1454 physical reads

0 redo size

6366832 bytes sent via SQL*Net to client

41213 bytes received via SQL*Net from client

3701 SQL*Net roundtrips to/from client

1 sorts (memory)

0 sorts (disk)

55500 rows processed

然而,如果我们在order by的列上创建一个索引,并使用first_rows提示(或index提示),oracle将使用索引替代排序:

SQL> create index cust_member_i on customers(cust_last_name,cust_first_name,cust_year_of_birth);

Index created.

SQL> select /*+ index(customers cust_member_i) */* from customers order by cust_last_name,cust_first_name,cust_year_of_birth;

55500 rows selected.

Execution Plan

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

Plan hash value: 3023887059

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

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

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

| 0 | SELECT STATEMENT | | 55500 | 9810K| 20550 (1)| 00:00:15 |

| 1 | TABLE ACCESS BY INDEX ROWID| CUSTOMERS | 55500 | 9810K| 20550 (1)| 00:00:15 |

| 2 | INDEX FULL SCAN | CUST_MEMBER_I | 55500 | | 225 (0)| 00:00:01 |

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

Statistics

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

1 recursive calls

0 db block gets

26557 consistent gets

1701 physical reads

0 redo size

11063057 bytes sent via SQL*Net to client

41213 bytes received via SQL*Net from client

3701 SQL*Net roundtrips to/from client

0 sorts (memory)

0 sorts (disk)

55500 rows processed

虽然使用索引就不再需要排序了,但是同时读取索引和表块,已经按块顺次读取这种并不高效的扫描方式所带来的开销,比仅使用全表扫描读取表块的方式要欠佳很多。通常,这意味着了为了避免排序而使用索引,实际上会到导致更差的性能。然而,使用索引的再检索第一行记录时速度更快。因为一旦需要的记录被检索到,它就立即返回。相比之下排序的方法要求再任一记录返回之前,全部记录都必须被检索并完成排序。因此,在优化器目标为first_rows_n时,优化器倾向于使用索引,而在目标是all_rows时,则会使用全表扫描。

2 分组与聚合

聚合(aggregate)操作基于多行记录返回数据,平均,求和,最大和最小值等。分组(grouping)操作允许我们对共享相同的group by值的记录集返回它的集合。

2.1 聚合操作

聚合操作(sum和average)必须处理输入数据的每一行记录。因此,它们通常和全表扫描联系在一起:

SQL> select sum(quantity_sold) from sales;

Execution Plan

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

Plan hash value: 3519235612

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

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |

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

| 0 | SELECT STATEMENT | | 1 | 3 | 525 (2)| 00:00:01 | | |

| 1 | SORT AGGREGATE | | 1 | 3 | | | | |

| 2 | PARTITION RANGE ALL| | 918K| 2691K| 525 (2)| 00:00:01 | 1 | 28 |

| 3 | TABLE ACCESS FULL | SALES | 918K| 2691K| 525 (2)| 00:00:01 | 1 | 28 |

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

如果被聚合上的列存在索引,对于这个索引的快速全扫描通常会更高效:

2.2 最大值和最小值

与大多数其他聚合操作不同,如果在相关列上存在索引,max和min操作并不需要读取每一行记录。如果存在B树索引,我们可以通过检查第一个或最后一个索引项来确定最大值或最小值,这仅需要3-5个逻辑读的开销

SQL> select max(amount_sold) from sales;(没有索引)

Execution Plan

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

Plan hash value: 3519235612

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

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |

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

| 0 | SELECT STATEMENT | | 1 | 5 | 525 (2)| 00:00:01 | | |

| 1 | SORT AGGREGATE | | 1 | 5 | | | | |

| 2 | PARTITION RANGE ALL| | 918K| 4486K| 525 (2)| 00:00:01 | 1 | 28 |

| 3 | TABLE ACCESS FULL | SALES | 918K| 4486K| 525 (2)| 00:00:01 | 1 | 28 |

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

Statistics

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

0 recursive calls

0 db block gets

1635 consistent gets

1619 physical reads

0 redo size

536 bytes sent via SQL*Net to client

524 bytes received via SQL*Net from client

2 SQL*Net roundtrips to/from client

0 sorts (memory)

0 sorts (disk)

1 rows processed

有索引的情况:

SQL> select max(amount_sold) from sales;

Execution Plan

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

Plan hash value: 781264156

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

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

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

| 0 | SELECT STATEMENT | | 1 | 5 | 3 (0)| 00:00:01 |

| 1 | SORT AGGREGATE | | 1 | 5 | | |

| 2 | INDEX FULL SCAN (MIN/MAX)| AMOUNT_SOLD_IDX | 1 | 5 | 3 (0)| 00:00:01 |

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

Statistics

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

1 recursive calls

0 db block gets

3 consistent gets

2 physical reads

0 redo size

536 bytes sent via SQL*Net to client

524 bytes received via SQL*Net from client

2 SQL*Net roundtrips to/from client

0 sorts (memory)

0 sorts (disk)

1 rows processed

然而,如果我们要同时查找最大值和最小值,oracle需要花费很多的逻辑读,此时全扫描索引代价会更高,oracle选择全表扫描。

SQL> select max(amount_sold),min(amount_sold) from sales;

Execution Plan

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

Plan hash value: 3519235612

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

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |

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

| 0 | SELECT STATEMENT | | 1 | 5 | 525 (2)| 00:00:01 | | |

| 1 | SORT AGGREGATE | | 1 | 5 | | | | |

| 2 | PARTITION RANGE ALL| | 918K| 4486K| 525 (2)| 00:00:01 | 1 | 28 |

| 3 | TABLE ACCESS FULL | SALES | 918K| 4486K| 525 (2)| 00:00:01 | 1 | 28 |

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

Statistics

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

1 recursive calls

0 db block gets

1635 consistent gets

1619 physical reads

0 redo size

618 bytes sent via SQL*Net to client

524 bytes received via SQL*Net from client

2 SQL*Net roundtrips to/from client

0 sorts (memory)

0 sorts (disk)

1 rows processed

实际上分别提交MAX和MIN查询然后将结果合并到一起是一种更好的办法:

SQL> select max_sold,min_sold from(select max(amount_sold)max_sold from sales)maxt,(select min(amount_sold) min_sold from sales)mint;

Execution Plan

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

Plan hash value: 3650580342

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

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

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

| 0 | SELECT STATEMENT | | 1 | 26 | 6 (0)| 00:00:01 |

| 1 | NESTED LOOPS | | 1 | 26 | 6 (0)| 00:00:01 |

| 2 | VIEW | | 1 | 13 | 3 (0)| 00:00:01 |

| 3 | SORT AGGREGATE | | 1 | 5 | | |

| 4 | INDEX FULL SCAN (MIN/MAX)| AMOUNT_SOLD_IDX | 1 | 5 | 3 (0)| 00:00:01 |

| 5 | VIEW | | 1 | 13 | 3 (0)| 00:00:01 |

| 6 | SORT AGGREGATE | | 1 | 5 | | |

| 7 | INDEX FULL SCAN (MIN/MAX)| AMOUNT_SOLD_IDX | 1 | 5 | 3 (0)| 00:00:01 |

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

Statistics

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

0 recursive calls

0 db block gets

6 consistent gets

0 physical reads

0 redo size

602 bytes sent via SQL*Net to client

524 bytes received via SQL*Net from client

2 SQL*Net roundtrips to/from client

0 sorts (memory)

0 sorts (disk)

1 rows processed

2.3 "前N"查询

在sales表中按美元计销量最好的前10种商品,以下查询肯定是不对的:

SQL> select * from sales where rownum<=10 order by amount_sold desc;

PROD_ID CUST_ID TIME_ID CHANNEL_ID PROMO_ID QUANTITY_SOLD AMOUNT_SOLD

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

13 987 10-JAN-98 3 999 1 1232.16

13 1660 10-JAN-98 3 999 1 1232.16

13 1762 10-JAN-98 3 999 1 1232.16

13 1843 10-JAN-98 3 999 1 1232.16

13 4663 10-JAN-98 3 999 1 1232.16

13 2273 10-JAN-98 3 999 1 1232.16

13 2380 10-JAN-98 3 999 1 1232.16

13 2683 10-JAN-98 3 999 1 1232.16

13 2865 10-JAN-98 3 999 1 1232.16

13 1948 10-JAN-98 3 999 1 1232.16

10 rows selected.

这是因为对where的处理会先于order by,因此这个查询将获取它最先发现的的10条记录,然后对它们进行排序。这样的结果不是真正的前10.

下面的查询更合理:

SQL> select /* top10 subquery */ * from(select cust_id,prod_id,time_id,amount_sold from sales order by amount_sold desc) where rownum<=10;

CUST_ID PROD_ID TIME_ID AMOUNT_SOLD

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

3948 18 26-APR-99 1782.72

4150 18 26-JUN-99 1782.72

40 18 26-JUN-99 1782.72

33724 18 21-JUN-99 1782.72

32863 18 21-JUN-99 1782.72

31364 18 21-JUN-99 1782.72

10864 18 21-JUN-99 1782.72

10620 18 21-JUN-99 1782.72

6490 18 21-JUN-99 1782.72

4788 18 21-JUN-99 1782.72

Execution Plan

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

Plan hash value: 443584055

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

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |

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

| 0 | SELECT STATEMENT | | 10 | 480 | 4 (0)| 00:00:01 | | |

|* 1 | COUNT STOPKEY | | | | | | | |

| 2 | VIEW | | 10 | 480 | 4 (0)| 00:00:01 | | |

| 3 | TABLE ACCESS BY GLOBAL INDEX ROWID| SALES | 918K| 19M| 4 (0)| 00:00:01 | ROWID | ROWID |

| 4 | INDEX FULL SCAN DESCENDING | AMOUNT_SOLD_IDX | 10 | | 3 (0)| 00:00:01 | | |

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

Predicate Information (identified by operation id):

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

1 - filter(ROWNUM<=10)

Statistics

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

0 recursive calls

0 db block gets

6 consistent gets

0 physical reads

0 redo size

908 bytes sent via SQL*Net to client

524 bytes received via SQL*Net from client

2 SQL*Net roundtrips to/from client

0 sorts (memory)

0 sorts (disk)

10 rows processed

STOPKEY表示不用将排序的结果全部返回。

使用分析函数:

SQL> set lines 200

SELECT /* top10 dense_rank */

*

FROM (SELECT cust_id,

prod_id,

time_id,

amount_sold,

dense_rank() over(ORDER BY amount_sold DESC) ranking

FROM sales)

9 WHERE ranking <= 10;

1355 rows selected.

Execution Plan

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

Plan hash value: 2275521554

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

| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | Pstart| Pstop |

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

| 0 | SELECT STATEMENT | | 918K| 53M| | 6623 (1)| 00:00:05 | | |

|* 1 | VIEW | | 918K| 53M| | 6623 (1)| 00:00:05 | | |

|* 2 | WINDOW SORT PUSHED RANK| | 918K| 19M| 31M| 6623 (1)| 00:00:05 | | |

| 3 | PARTITION RANGE ALL | | 918K| 19M| | 525 (2)| 00:00:01 | 1 | 28 |

| 4 | TABLE ACCESS FULL | SALES | 918K| 19M| | 525 (2)| 00:00:01 | 1 | 28 |

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

Predicate Information (identified by operation id):

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

1 - filter("RANKING"<=10)

2 - filter(DENSE_RANK() OVER ( ORDER BY INTERNAL_FUNCTION("AMOUNT_SOLD") DESC )<=10)

Statistics

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

0 recursive calls

0 db block gets

1635 consistent gets

1619 physical reads

0 redo size

31018 bytes sent via SQL*Net to client

1513 bytes received via SQL*Net from client

92 SQL*Net roundtrips to/from client

1 sorts (memory)

0 sorts (disk)

1355 rows processed

尽管我对于子查询得到前10的方法有所保留,但是它更高效。dense_rank方法比排序的子查询方法消耗更多的内存和CPU。

2.4 分组操作

分组(group by)操作允许sql语句对一批列中的每个唯一值返回一行记录,并且对每个一个这样的组计算聚合。例如,下面的语句对每一个国家代码返回平均信用限额:

SQL> select country_id,avg(cust_credit_limit) from customers group by country_id;

19 rows selected.

Execution Plan

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

Plan hash value: 1577413243

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

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

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

| 0 | SELECT STATEMENT | | 19 | 171 | 407 (1)| 00:00:01 |

| 1 | HASH GROUP BY | | 19 | 171 | 407 (1)| 00:00:01 |

| 2 | TABLE ACCESS FULL| CUSTOMERS | 55500 | 487K| 405 (1)| 00:00:01 |

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


排序 索引 内存 查询 数据 分组 性能 方法 结果 顺序 最大 最小 一行 子句 实际 实际上 开销 时段 最大值 次数 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 软件开发与维护岗位的理解 网络安全教育知识海报 考研人数 数据库 国家网络安全活动创新点及亮点 我国网络安全专业人才数量 增城高校网络安全建设 松江区管理软件开发批发价格 中国网络安全调查 战地4服务器灰色 关于购买网络安全设备的申请 编译后的socks代理服务器 sqlaccess数据库设置 四川语音网络技术分类基础 软件开发人员是做什么的呢 监控系统登录显示服务器无响应 战神引擎怎么启动数据库 服务器系统什么好 第四代软件开发工具 广西浦北软件开发公司 行星边际2阵营服务器 专业软件开发大概多少钱 目前潜在的网络安全危机 服务器是中央处理器吗 放映服务器GDC版本783 win11rpc服务器锁住桌面 虹口区口碑好的软件开发诚信推荐 网络技术开发售后服务 苏州正规服务器优质推荐 职校读计算机网络技术怎么样 石家庄哪个学校能学软件开发
0