Эквивалент составного индекса для нескольких таблиц? - PullRequest
8 голосов
/ 30 июля 2010

У меня есть структура таблицы, подобная следующей:

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 ]

7 голосов
/ 30 июля 2010

Oracle может использовать оба индекса.У вас просто нет правильных двух индексов.

Подумайте: если план запроса сначала использует ваш индекс на mail.sent_date, что он получает от mail?Он получает все mail.id с, где mail.sent_date находится в пределах диапазона, который вы указали в предложении where, да?

Итак, он переходит к mail_to со списком mail.id с иmail.name вы дали в своем where предложении.На этом этапе Oracle решает, что лучше сканировать таблицу на соответствие mail_to.mail_id s, а не использовать индекс для 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.

4 голосов
/ 30 июля 2010

A материализованное представление позволит индексировать значения при условии соблюдения строгих критериев материализованного представления.

0 голосов
/ 07 декабря 2018

В ситуациях, когда для материализованного представления не выполняются требования, существуют следующие два варианта:

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) Если вам не нужны служебные данные триггера по производительности / управлению, и вам это нужно только для отчетов на следующий день, вы можете создать представление и ночной процесс, который усекает таблицу и выбираетполное представление в материализованную таблицу.

Я успешно использовал это для индексации сглаженных реляционных данных, требующих объединений по дюжине или около того таблиц ... сокращая время отчетов с часов до секунд.Несмотря на то, что это дорогостоящий запрос, вы можете настроить часы на работу в нерабочее время, если у вас есть периоды ограниченного использования.

0 голосов
/ 30 июля 2010

Если ваши запросы обычно относятся к определенному месяцу, то вы можете разделить данные по месяцам.

0 голосов
/ 30 июля 2010

Какой критерий является более избирательным?Диапазон дат или адресат?Я бы догадался адресат.И если это очень избирательно, не обращайте внимания на индекс даты, просто дайте базе данных выполнить поиск на основе найденных почтовых идентификаторов.Но индексная таблица MAIL для идентификатора, если она еще не существует.

С другой стороны, некоторые современные оптимизаторы даже используют оба индекса, сканируя обе таблицы и создавая хеш-значение из столбцов соединения.объединить результаты обоих.Я не совсем уверен, выберет ли Oracle эту стратегию и когда.Я только что понял, что SQL Server имеет тенденцию делать хэш-соединения довольно часто, по сравнению с другими механизмами.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...