跨多个表的复合索引的等价物?

时间:2010-07-30 17:23:37

标签: sql oracle

我有一个类似于以下的表结构:

create table MAIL (
  ID        int,
  FROM      varchar,
  SENT_DATE date
);

create table MAIL_TO (
  ID      int,
  MAIL_ID int,
  NAME      varchar
);

我需要运行以下查询:

select m.ID 
from MAIL m 
  inner join MAIL_TO t on t.MAIL_ID = m.ID
where m.SENT_DATE between '07/01/2010' and '07/30/2010'
  and t.NAME = 'someone@example.com'

有没有办法设计索引,以便两个条件都可以使用索引?如果我在MAIL.SENT_DATE上放置索引并在MAIL_TO.NAME上放置索引,则数据库将选择使用其中一个索引或另一个索引,而不是两者。在按第一个条件过滤后,数据库始终必须对第二个条件的结果进行全面扫描。

5 个答案:

答案 0 :(得分:7)

Oracle可以使用这两个索引。你只是没有正确的两个索引。

考虑一下:如果查询计划首先使用mail.sent_date上的索引,它会从mail得到什么?它会得到mail.id所在的mail.sent_date所在的where条款范围内,是吗?

所以它会转到mail_to,其中列出了mail.id和您mail.name子句中提供的where。此时,Oracle决定最好扫描表以匹配mail_to.mail_id,而不是使用mail_to.name上的索引。

varchars上的索引始终存在问题,Oracle确实更喜欢全表扫描。但是,如果我们给Oracle一个包含它真正想要使用的列的索引,并且根据总表行和统计信息,我们可以让它使用它。这是索引:

 create index mail_to_pid_name on mail_to( mail_id, name ) ; 

这适用于仅name上的索引没有的情况,因为Oracle不只是寻找名称,而是mail_id a name

相反,如果基于成本的分析器确定首先转到表mail_to并使用mail_to.name上的索引会更便宜,那么你会得到什么?要在mail_to_.mail_id中查找一堆mail个。它需要找到包含某些sent_dates的行,所以:

 create index mail_id_sentdate on mail( sent_date, id ) ; 

请注意,在这种情况下,我首先将sent_date放在索引中,然后id放在第二位。 (这更直观。)

同样,回家点是:在创建索引时,您不仅要考虑where子句中的列,还要考虑连接条件中的列。


更新

jthg:是的,它总是取决于数据的分布方式。并且表中有多少行:如果非常多,Oracle将执行表扫描和散列连接,如果很少,它将执行表扫描。您可以颠倒两个索引中任何一个的顺序。通过将sent_date放在第二个索引中,我们仅在sent_date上消除了对索引的大部分需求。

答案 1 :(得分:4)

假设符合严格的物化视图标准,materialized view将允许您对值进行索引。

答案 2 :(得分:0)

哪个标准更具选择性?日期范围或收件人?我猜想收件人。如果这是高度选择性的,不关心日期索引,只需让数据库根据找到的邮件ID进行搜索。但是,如果id尚未存在,请将索引表MAIL添加到其中。

另一方面,一些现代优化器甚至会使用两个索引,扫描两个表,然后构建连接列的哈希值以合并两者的结果。我不确定Oracle是否以及何时会选择此策略。我刚刚意识到,与其他引擎相比,SQL Server倾向于经常进行散列连接。

答案 3 :(得分:0)

如果您的查询通常是特定月份,那么您可以按月partition数据。

答案 4 :(得分:0)

在无法满足实体化视图要求的情况下,有以下两种选择:

1)您可以创建一个交叉引用表,并使用触发器对其进行更新。

Oracle的概念相同,但是目前我仅安装SQL Server来运行测试,请参阅以下设置:

create table MAIL (
  ID        INT IDENTITY(1,1),
  [FROM]      VARCHAR(200),
  SENT_DATE DATE,
  CONSTRAINT PK_MAIL PRIMARY KEY (ID)
);

create table MAIL_TO (
  ID      INT IDENTITY(1,1),
  MAIL_ID INT,
  [NAME]     VARCHAR (200),
  CONSTRAINT PK_MAIL_TO PRIMARY KEY (ID)
);

ALTER TABLE [dbo].[MAIL_TO]  WITH CHECK ADD  CONSTRAINT [FK_MAILTO_MAIL] FOREIGN KEY([MAIL_ID])
REFERENCES [dbo].[MAIL] ([ID])
GO

ALTER TABLE [dbo].[MAIL_TO] CHECK CONSTRAINT [FK_MAILTO_MAIL]
GO


CREATE TABLE CompositeIndex_MailSentDate_MailToName ( 
[MAIL_ID] INT,
[MAILTO_ID] INT,
SENT_DATE DATE,
MAILTO_NAME VARCHAR(200),
CONSTRAINT PK_CompositeIndex_MailSentDate_MailToName PRIMARY KEY (MAILTO_ID,MAIL_ID)
)

GO

CREATE NONCLUSTERED INDEX IX_MailSent_MailTo ON dbo.CompositeIndex_MailSentDate_MailToName (SENT_DATE,MAILTO_NAME)
CREATE NONCLUSTERED INDEX IX_MailTo_MailSent ON dbo.CompositeIndex_MailSentDate_MailToName (MAILTO_NAME,SENT_DATE)
GO

CREATE TRIGGER dbo.trg_MAILTO_Insert
ON dbo.MAIL_TO  
AFTER INSERT AS  
BEGIN 
 INSERT INTO dbo.CompositeIndex_MailSentDate_MailToName ( MAIL_ID, MAILTO_ID, SENT_DATE, MAILTO_NAME )
 SELECT mailTo.MAIL_ID,mailTo.ID,m.SENT_DATE,mailTo.NAME
 FROM
 inserted mailTo
 INNER JOIN dbo.MAIL m ON m.ID = mailTo.MAIL_ID
END
GO


CREATE TRIGGER dbo.trg_MAILTO_Delete
ON dbo.MAIL_TO  
AFTER DELETE AS  
BEGIN 
 DELETE mailToDelete
 FROM
 dbo.MAIL_TO mailToDelete
 INNER JOIN deleted ON mailToDelete.ID = deleted.ID
END
GO

CREATE TRIGGER dbo.trg_MAILTO_Update
ON dbo.MAIL_TO  
AFTER UPDATE AS  
BEGIN 
 UPDATE compositeIndex
 SET
 compositeIndex.MAILTO_NAME = updates.NAME
 FROM
 dbo.CompositeIndex_MailSentDate_MailToName compositeIndex
 INNER JOIN inserted updates ON updates.ID = compositeIndex.MAILTO_ID
END
GO

CREATE TRIGGER dbo.trg_MAIL_Update
ON dbo.MAIL  
AFTER UPDATE AS  
BEGIN 
 UPDATE compositeIndex
 SET
 compositeIndex.SENT_DATE = updates.SENT_DATE
 FROM
 dbo.CompositeIndex_MailSentDate_MailToName compositeIndex
 INNER JOIN inserted updates ON updates.ID = compositeIndex.MAIL_ID
END
GO


INSERT INTO dbo.MAIL ( [FROM], SENT_DATE )
SELECT 'SenderA','2018-10-01'
UNION ALL SELECT 'SenderA','2018-10-02'

INSERT INTO dbo.MAIL_TO ( MAIL_ID, NAME )
SELECT 1,'CustomerA'
UNION ALL SELECT 1,'CustomerB'
UNION ALL SELECT 2,'CustomerC'
UNION ALL SELECT 2,'CustomerD'
UNION ALL SELECT 2,'CustomerE'


SELECT * FROM dbo.MAIL
SELECT * FROM dbo.MAIL_TO
SELECT * FROM dbo.CompositeIndex_MailSentDate_MailToName

然后可以使用dbo.CompositeIndex_MailSentDate_MailToName表将其余数据联接。这在插入和更新率较低但查询需求较高的环境中很有用。因此,实现触发器的相对开销很小。

这具有实时进行事务更新的优势。

2)如果您不希望触发器的性能/管理开销,并且只需要在第二天的报告中使用,则可以创建一个视图,以及一个夜间过程,该过程将截断表并将整个视图选择为物化表。

我已经成功地使用它来索引需要在十几个表之间进行联接的扁平化关系数据的索引。将报告时间从几小时减少到几秒。虽然查询费用很高,但如果使用期减少,您可以将作业设置为数小时。