2013-05-29 4 views
1

Любые идеи о том, как улучшить производительность этого запроса?Медленный запрос TSQL

[ftsIndex] PK is sID, wordPos.
И есть индекс для wordID, sID, wordPos.
Все они инт.

В конце используйте отдельный.
У большинства sID всего несколько матчей.
Некоторые sID могут иметь более 10 000 совпадений и убивать запрос.

Имейте запрос, где первые 27 749 строк возвращаются через 11 секунд.
Никакой сингл sID не имеет более 500 матчей.
Сумма индивидуальных матчей: 65,615.

Только 27 750-й ряд занимает более 2 минут и имеет 15 000 матчей.

Не удивительно, поскольку соединение в конце находится на [sID].

Поскольку в конце концов использовать отчетливый есть путь к нему, чтобы посмотреть на первый утвердительный

on [wXright].[sID] = [wXleft].[sID] 
    and [wXright].[wordPos] > [wXleft].[wordPos] 
    and [wXright].[wordPos] <= [wXleft].[wordPos] + 10 

затем перейти к следующему Sid?

Я знаю, что это многого требует от оптимизатора запросов, но это было бы действительно круто.

В реальной жизни проблемный документ является списком деталей, и поставщик повторяется много раз.

select distinct [wXleft].[sID] 
FROM 
(-- begin [wXleft] 
    (-- start term 
     select [ftsIndex].[sID], [ftsIndex].[wordPos] 
     from [ftsIndex] with (nolock) 
     where [ftsIndex].[wordID] in 
       (select [id] from [FTSwordDef] with (nolock) 
          where [word] like 'Brown') 
    ) -- end term 
) [wXleft] 
join 
(-- begin [wRight] 
    (-- start term 
     select [ftsIndex].[sID], [ftsIndex].[wordPos] 
     from [ftsIndex] with (nolock) 
     where [ftsIndex].[wordID] in 
       (select [id] from [FTSwordDef] with (nolock) 
          where [word] like 'Fox') 
    ) -- end term 
) [wXright] 
on [wXright].[sID] = [wXleft].[sID] 
and [wXright].[wordPos] > [wXleft].[wordPos] 
and [wXright].[wordPos] <= [wXleft].[wordPos] + 10 

Это приводит его до 1:40

inner loop join 

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

Это не исправить это

select distinct [ft1].[sID] 
    from [ftsIndex] as [ft1] with (nolock) 
    join [ftsIndex] as [ft2] with (nolock) 
    on [ft2].[sID] = [ft1].[sID] 
    and [ft1].[wordID] in (select [id] from [FTSwordDef] with (nolock) where [word] like 'brown') 
    and [ft2].[wordID] in (select [id] from [FTSwordDef] with (nolock) where [word] like 'fox') 
    and [ft2].[wordPos] > [ft1].[wordPos] 
    and [ft2].[wordPos] <= [ft1].[wordPos] + 10 

также поддерживают запросы, как «шустрая» 10 слов «лиса» или «койот», так соединяется с псевдонимами не хороший путь.

Это занимает 14 минут (но, по крайней мере, он работает).
Снова этот формат не способствует более продвинутым запросам.

IF OBJECT_ID(N'tempdb..#tempMatch1', N'U') IS NOT NULL DROP TABLE #tempMatch1 
CREATE TABLE #tempMatch1(
    [sID] [int] NOT NULL, 
    [wordPos] [int] NOT NULL, 
CONSTRAINT [PK1] PRIMARY KEY CLUSTERED 
(
    [sID] ASC, 
    [wordPos] ASC 
)) 
IF OBJECT_ID(N'tempdb..#tempMatch2', N'U') IS NOT NULL DROP TABLE #tempMatch2 
CREATE TABLE #tempMatch2(
    [sID] [int] NOT NULL, 
    [wordPos] [int] NOT NULL, 
CONSTRAINT [PK2] PRIMARY KEY CLUSTERED 
(
    [sID] ASC, 
    [wordPos] ASC 
)) 
insert into #tempMatch1 
select [ftsIndex].[sID], [ftsIndex].[wordPos] 
     from [ftsIndex] with (nolock) 
     where [ftsIndex].[wordID] in 
       (select [id] from [FTSwordDef] with (nolock) 
          where [word] like 'Brown') 
     --and [wordPos] < 100000; 
    order by [ftsIndex].[sID], [ftsIndex].[wordPos]      
insert into #tempMatch2 
select [ftsIndex].[sID], [ftsIndex].[wordPos] 
     from [ftsIndex] with (nolock) 
     where [ftsIndex].[wordID] in 
       (select [id] from [FTSwordDef] with (nolock) 
          where [word] like 'Fox') 
     --and [wordPos] < 100000; 
    order by [ftsIndex].[sID], [ftsIndex].[wordPos] 
select count(distinct(#tempMatch1.[sID])) 
from #tempMatch1 
join #tempMatch2 
    on #tempMatch2.[sID] = #tempMatch1.[sID] 
and #tempMatch2.[wordPos] > #tempMatch1.[wordPos] 
and #tempMatch2.[wordPos] <= #tempMatch1.[wordPos] + 10 

Немного другое соединение проходит через 5 секунд (и имеет другой план запроса).
Но я не могу исправить это с помощью намеков, когда он перемещается туда, где он соединяется.
И даже у +1 есть более 10 документов, имеющих более 7000 матчей.

on [wXright].[sID] = [wXleft].[sID] 
and [wXright].[wordPos] = [wXleft].[wordPos] + 1 

Полной таблица Защита

CREATE TABLE [dbo].[FTSindex](
    [sID] [int] NOT NULL, 
    [wordPos] [int] NOT NULL, 
    [wordID] [int] NOT NULL, 
    [charPos] [int] NOT NULL, 
CONSTRAINT [PK_FTSindex] PRIMARY KEY CLUSTERED 
(
    [sID] ASC, 
    [wordPos] ASC 
)WITH (PAD_INDEX = ON, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 100) ON [PRIMARY] 
) ON [PRIMARY] 

GO 

ALTER TABLE [dbo].[FTSindex] WITH CHECK ADD CONSTRAINT [FK_FTSindex_FTSwordDef] FOREIGN KEY([wordID]) 
REFERENCES [dbo].[FTSwordDef] ([ID]) 
GO 

ALTER TABLE [dbo].[FTSindex] CHECK CONSTRAINT [FK_FTSindex_FTSwordDef] 
GO 
+0

Я не знаю со всеми вашими данными, но подумал ли вы, что может быть вставлен в временные таблицы, а затем создать кластеризованные индексы на них? Вставьте сначала, затем создайте индекс. Обычно это быстрее, чем создание индекса самостоятельно. Это может вам помочь, возможно, это не так, я думал добавить его в качестве комментария. – djangojazz

+0

@djangojazz Эта вставка занимает всего 5 секунд.Если я добавлю сортировку, записи будут вставлены в порядке PK, а все равно 5 секунд. – Paparazzi

+0

Нам понадобятся определения таблицы/ключа/индекса и план запроса (фактический). Кроме того, есть ли какие-либо основания для этого дизайна/подхода, а не только для полнотекстового поиска SQL Server? – RBarryYoung

ответ

1

UPDATE:

Вы все еще можете использовать union all, который помогает оптимизатору сохраняет заказ от индекса, если вы отложите фильтрации 'L' и стороны 'R' до последней части процесса. К сожалению, вам необходимо заранее восстановить все словари и использовать их в состоянии equals. На моей машине это сокращает время выполнения до 2/3:

; with o as (
    select sID, wordPos, wordID 
     from FTSindex 
    where wordID = 1 
    union all 
    select sID, wordPos, wordID 
     from FTSindex 
    where wordID = 4 
    union all 
    select sID, wordPos, wordID 
     from FTSindex 
    where wordID = 2 
), 
g as (
    select sID, wordPos, wordID, 
      ROW_NUMBER() over (partition by [sID] order by wordPos) rn 
     from o 
) 
select count(distinct(g1.sID)) -- 26919 00:02 
     from g g1 
     join g g2 
     on g1.sID = g2.sID 
     and g1.rn = g2.rn - 1 
     and g1.wordPos >= g2.wordPos - 10 
    -- Now is the time to repartition the stream 
     and g1.wordID in (1, 4) 
     and g2.wordID = 2 

О, это действительно занимает две секунды сейчас?

UPDATE - 2:

; with o as (
-- Union all resolves costly sort 
    select sid, wordpos, wordid 
     from FTSindex 
    where wordID = 1 
    union all 
    select sid, wordpos, wordID 
     from FTSindex 
    where wordID = 2 
), 
g as (
    select sid, wordid, wordpos, 
      ROW_NUMBER() over(order by sid, wordpos) rn 
     from o 
) 
select count(distinct g1.sid) 
    from g g1 
inner join g g2 
    on g1.sID = g2.sID 
    and g1.rn = g2.rn - 1 
where g1.wordID = 1 
    and g2.wordID = 2 
    and g1.wordPos >= g2.wordpos - 10 

1 и 2 стойки для выбранных слов ид. Результаты отличаются от результатов, полученных по оригинальному запросу в отношении нескольких ударов в пределах 10 слов; исходный запрос будет сообщать обо всех них, но этот будет показывать только ближайший.

Идея состоит в том, чтобы извлечь только слова, которые искали и сравнить расстояние между двумя соседними словами, где первыйIDID 1 и wordID 2 секунды.

ОБНОВЛЕНИЕ - 1:

Я записал этот пост, потому что это не так хорошо, как я думал. Но он обеспечивает потребности OP лучше, чем оптимизированный запрос, поскольку он позволяет одновременно искать несколько слов (список слов, найденных в непосредственной близости от другого слова (ов), которое может быть указано в разделе where).

; with g as (
    select sid, wordid, wordpos, 
      ROW_NUMBER() over(order by sid, wordpos) rn 
     from FTSindex 
    where wordID in (1, 2) 
) 
select count(distinct g1.sid) 
    from g g1 
inner join g g2 
    on g1.sID = g2.sID 
    and g1.rn = g2.rn - 1 
where g1.wordID = 1 
    and g2.wordID = 2 
    and g1.wordPos >= g2.wordpos - 10 

ПЕРВАЯ ПОПЫТКА:

Там может быть способом с использованием cross apply в сочетании с top 1.

select [wXleft].[sID], [wXleft].[wordPos] 
    from [ftsIndex] wXleft with (nolock) 
cross apply 
(
    select top 1 r.sID 
     from [ftsIndex] r 
    where r.sID = wXleft.sID 
     and r.wordPos > wxLeft.wordPos 
     and r.wordPos <= wxLeft.wordPos + 10 
     and r.wordID in 
      (select [id] 
       from [FTSwordDef] with (nolock) 
      where [word] like 'Fox') 
) wXright 
where [wXleft].[wordID] in 
     (select [id] 
      from [FTSwordDef] with (nolock) 
     where [word] like 'Brown') 

БОНУС PIVOT ПОПЫТКА:

; with o as (
    select sid, wordpos, wordid 
     from FTSindex 
    where wordID = 1 
    union all 
    select sid, wordpos, wordID 
     from FTSindex 
    where wordID = 2 
), 
g as (
    select sid, wordid, wordpos, 
      ROW_NUMBER() over(order by sid, wordpos) rn 
    from o 
) 
select sid, rn, [1], [2] 
from 
(
-- Collapse rns belonging to wordid 2 to ones belonging to wordid 1 
-- so they appear in the same row 
    select sid, wordpos, wordid, rn - case when wordid = 1 then 0 else 1 end rn 
    from g 
) g1 
pivot (max(wordpos) for wordid in ([1], [2])) u 
where [2] - [1] <= 10 
+0

Возвращает тот же ответ, что и внутренний цикл 2/3 время. Подождите пару дней для чудо-ответа, прежде чем принять это. Спасибо. – Paparazzi

+0

Почему вы выбрали этот другой вариант? Это быстрее. Я пытаюсь настроить его, чтобы попытаться получить еще больше. Что странно, так это то, что выходит из этого CTE, что доминирует в цене. – Paparazzi

+0

@Blam, потому что мое время было неправильным, это заняло столько же, сколько и моя первоначальная попытка. Между тем я решил часть сортировки но я обеспокоен необходимостью Sql Server выполнять CTE один раз за ссылку, и есть две ссылки. Я отправлю новую версию через минуту. –

1

Ну, если бы я имел больше информации или способ проверить, но если это невозможно, это то, что я бы, наверное, попробовать:

IF OBJECT_ID(N'tempdb..#tempMatch', N'U') IS NOT NULL DROP TABLE #tempMatch 
CREATE TABLE #tempMatch(
    [sID] [int] NOT NULL, 
    [wordPos] [int] NOT NULL, 
    [wordID] [int] NOT NULL, 
CONSTRAINT [PK2] PRIMARY KEY CLUSTERED 
(
    [sID] ASC, 
    [wordPos] ASC 
)) 

-- 
;WITH cteWords As 
(
      SELECT 'Brown' as [word] 
    UNION ALL SELECT 'Fox' 
) 
INSERT INTO #tempMatch ([sID],[wordPos],[wordID]) 
SELECT sID, wordPos, wordID 
FROM ftsIndex 
WHERE EXISTS 
     (Select * From FTSWordDef s1 
     inner join cteWords s2 ON s1.word = s2.word 
     Where ftsIndex.wordID = s1.id) 
; 

select count(distinct(s1.[sID])) 
    from #tempMatch s1 
    join #tempMatch s2 
     on s2.[sID] = s1.[sID] 
     and s2.[wordPos] > s1.[wordPos] 
     and s2.[wordPos] <= s1.[wordPos] + 10 
    where s1.wordID = (select id from FTSWordDef w where w.word = 'Brown') 
     and s2.wordID = (select id from FTSWordDef w where w.word = 'Fox') 

Вчера я придумал одну альтернативную версию. Это одни и те же вопросы, что и выше, но CREATE заявление изменено на:

IF OBJECT_ID(N'tempdb..#tempMatch', N'U') IS NOT NULL DROP TABLE #tempMatch 
CREATE TABLE #tempMatch(
    [sID] [int] NOT NULL, 
    [wordID] [int] NOT NULL, 
    [wordPos] [int] NOT NULL, 
CONSTRAINT [PK0] PRIMARY KEY CLUSTERED 
(
    [wordID] ASC, 
    [sID] ASC, 
    [wordPos] ASC 
)) 

Пожалуйста, дайте мне знать, если это помогает вообще.

+0

Должен был добавить wordID к ограничению на первом и оба выбросить ошибку в join cteWords. – Paparazzi

+0

@Blam Что за ошибка? Я не могу проверить компиляцию, так как у нас нет определений таблиц. – RBarryYoung

+0

@Blam Почему вы должны добавить wordID к первому ограничению? Согласно вашему сообщению, '(sID, wordPos)' должно быть достаточным, поскольку они являются первичным ключом для единственной таблицы, которую я рисую из 'INSERT..SELECT..'. (Фактически, теперь, когда я смотрю на это, я понимаю, что 'DISTINCT' является избыточным и его не должно быть) – RBarryYoung

Смежные вопросы