避免全表扫描 - 仅提取第一行

Posted

技术标签:

【中文标题】避免全表扫描 - 仅提取第一行【英文标题】:Avoid Full Table Scan - Extract First Row Only 【发布时间】:2015-11-10 13:03:39 【问题描述】:

我正在尝试编写一个仅在满足条件时提取第一(随机)行的查询。

-- Create table
create table TRANSACTIONS_SAMPLE
(
  institution_id               NUMBER(5) not null,
  id                           NUMBER(10) not null,
  partitionkey                 NUMBER(10) default 0 not null,
  cardid                       NUMBER(10),
  accountid                    NUMBER(10),
  batchid                      NUMBER(10) not null,
  amt_bill                     NUMBER(16,3),
  load_date                    DATE not null,
  trxn_date                    DATE not null,
  single_msg_flag              NUMBER(5),
  authaccounttype              VARCHAR2(2 BYTE),
  originator                   VARCHAR2(50),
  amount                       NUMBER(16,3) default 0.000 not null,
  embeddedfee                  NUMBER(16,3) default 0.000 not null,,
  valuedate                    DATE,
  startofinterest              DATE,
  minduevaluedate              DATE,
  postdate                     DATE,
  posttimestamp                DATE,
  Status                       CHAR(4 BYTE) default 'NEW' not null,
)
partition by list (PARTITIONKEY)
(
  partition 0002913151 values (1234567)
    tablespace LIVE
    pctfree 10
    initrans 16
    maxtrans 255
    storage
    (
      initial 8M
      next 1M
      minextents 1
      maxextents unlimited
    )
);

-- Create/Recreate indexes 
create index TRANSACTIONS_SAMPLEI01 on TRANSACTIONS_SAMPLE (ACCOUNTID)
  local;
create index TRANSACTIONS_SAMPLEI02 on TRANSACTIONS_SAMPLE (LOAD_DATE)
  local;
create index TRANSACTIONS_SAMPLEI03 on TRANSACTIONS_SAMPLE (BATCHID)
  local;
create index TRANSACTIONS_SAMPLEI04 on TRANSACTIONS_SAMPLE (POSTDATE)
  local;
create index TRANSACTIONS_SAMPLEI05 on TRANSACTIONS_SAMPLE (POSTTIMESTAMP)
  local;
create index TRANSACTIONS_SAMPLEI06 on TRANSACTIONS_SAMPLE (STATUS, PARTITIONKEY)
  local;
create index TRANSACTIONS_SAMPLEI07 on TRANSACTIONS_SAMPLE (CARDID, TRXN_DATE)
  local;
create unique index TRANSACTIONS_SAMPLEUI01 on TRANSACTIONS_SAMPLE (ID, PARTITIONKEY)
  local;
-- Create/Recreate primary, unique and foreign key constraints 
alter table TRANSACTIONS_SAMPLE
  add constraint TRANSACTIONS_SAMPLEPK primary key (ID, PARTITIONKEY);

--QUERY
Select * From (
Select t.AccountId From Transactions_sample t Group by t.Accountid Having Count(t.AccountId) > 10 order by dbms_random.random)
Where Rownum = 1

这个查询的问题是全表扫描。我想在不必完全访问表的情况下获得相同的结果。有什么想法吗?

谢谢

【问题讨论】:

在减少到单个随机行之前,您肯定必须查看每一行以获得计数?您是否期望进行全索引扫描(TRANSACTIONS_SAMPLEI01)而不是全表扫描?如果您的统计信息是最新的,则该列的可空性可能会阻止它 - 因为它可能必须计算值。 【参考方案1】:

如果您为where AccountId is not null 添加过滤器,则可以使用TRANSACTIONS_SAMPLEI01 将其归结为全索引扫描。但前提是您当然不想计算空值。

该列可以为空,但索引不包含空值。要包括空值计数,它必须进行全表扫描,因为它无法从索引中获取该计数。如果你有那个过滤器,优化器知道所有的账户 ID 值都必须在索引中,所以它只需要引用那个,而不是表本身。

explain plan for
Select * From (
Select t.AccountId From Transactions_sample t where AccountId is not null Group by t.Accountid Having Count(t.AccountId) > 10 order by dbms_random.random)
Where Rownum = 1;

select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT                                                                                                                                                                                      
---------------------------------------------------------------------------------------------------------------------
Plan hash value: 381125580                                                                                                                                                                              

---------------------------------------------------------------------------------------------------------------------                                                                                   
| Id  | Operation                  | Name                   | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |                                                                                   
---------------------------------------------------------------------------------------------------------------------                                                                                   
|   0 | SELECT STATEMENT           |                        |     1 |    13 |     0   (0)| 00:00:01 |       |       |                                                                                   
|*  1 |  COUNT STOPKEY             |                        |       |       |            |          |       |       |                                                                                   
|   2 |   VIEW                     |                        |     1 |    13 |     0   (0)| 00:00:01 |       |       |                                                                                   
|*  3 |    SORT ORDER BY STOPKEY   |                        |     1 |    13 |     0   (0)| 00:00:01 |       |       |                                                                                   
|*  4 |     FILTER                 |                        |       |       |            |          |       |       |                                                                                   
|   5 |      SORT GROUP BY NOSORT  |                        |     1 |    13 |     0   (0)| 00:00:01 |       |       |                                                                                   
|   6 |       PARTITION LIST SINGLE|                        |     1 |    13 |     0   (0)| 00:00:01 |     1 |     1 |                                                                                   
|*  7 |        INDEX FULL SCAN     | TRANSACTIONS_SAMPLEI01 |     1 |    13 |     0   (0)| 00:00:01 |     1 |     1 |                                                                                   
---------------------------------------------------------------------------------------------------------------------                                                                                   

Predicate Information (identified by operation id):                                                                                                                                                     
---------------------------------------------------                                                                                                                                                     

   1 - filter(ROWNUM=1)                                                                                                                                                                                 
   3 - filter(ROWNUM=1)                                                                                                                                                                                 
   4 - filter(COUNT("T"."ACCOUNTID")>10)                                                                                                                                                                
   7 - filter("ACCOUNTID" IS NOT NULL)                                                                                                                                                                  

Note                                                                                                                                                                                                    
-----                                                                                                                                                                                                   
   - dynamic sampling used for this statement (level=2)                                                                                                                                                 

或者,如果可以使列不可为空,则不需要过滤器。

【讨论】:

感谢您的澄清。我还有一个后续问题。假设我是用一个辅助表(Accounts)加入事务表。 Table Account 包含 3 列 ID、状态和余额。列 ID(唯一)和 Status 可以为空并被索引。如何避免账户表全表扫描? Select * From ( Select t.AccountId From Transactions_sample t, accounts a where t.accountid is not null and t.accountid=a.id and a.status ='OPEN' Group by t.Accountid 有 Count(t.AccountId) > 10 order by dbms_random.random) where Rownum = 1 @user3651825 - 除非status 被编入索引,否则你真的不能;好吧,您可以基于t.accountid 作为连接条件进行索引查找,但无论如何您都在查看t 中的每一行,这样会比FTS 慢。除非您有一小部分未结账户(例如 5%,这似乎不太可能),否则无论如何早点过滤它可能不会更快。 @user3651825 - 这取决于它是否真的会对您的时间安排产生影响。 FTS 有时是最好的选择,看起来它就在这里。您可以获取包含超过 10 行的所有帐户 ID 的子集;将其加入您的account 表;过滤状态;并将 order-by 和 rownum 应用于 that 减少的结果集。但无论如何,匹配的账户比例可能会使 FTS 成为正确的选择。感觉就像您现在正进入过早优化领域 - 解决您认为可能遇到的问题,但这是不真实的。很难说。 状态已编入索引。上面的查询仍然运行全表扫描而不是索引扫描 但是有多少比例的账户记录处于打开状态?如果超过一小部分匹配,则完全扫描通常仍然更快。您必须重写以使其尝试使用该索引,但我真的不确定它会改进它。

以上是关于避免全表扫描 - 仅提取第一行的主要内容,如果未能解决你的问题,请参考以下文章

避免全表扫描的sql优化

优化 SQL 查询以避免全表扫描

避免全表扫描的sql优化

SQL 数据优化索引建suo避免全表扫描

SQL 数据优化索引建suo避免全表扫描

避免对表 Oracle 进行全表扫描