Запрос к базе данных медленнее с представлением - PullRequest
0 голосов
/ 18 сентября 2009

Я вроде чувствую, почему представление медленнее: предложение where, вероятно, не применяется одновременно. Результаты, похоже, одинаковы. Я не уверен, что я могу с этим поделать, если не использовать представление ... что не идеально, так как я добавил представление, чтобы избежать повторения кода, и я не хочу удалять его, если в этом нет необходимости ,

Любые предложения по изменению способа, которым я делаю это, чтобы я мог использовать представление, как в Команде 1, но все еще мой запрос должен выполняться так же быстро, как он выполняется в команде 2?

declare @foo varchar(50)
set @foo = 'be%'

ALTER VIEW [dbo].[wpvw_v]
AS
select distinct [name]
from kvgs kvg left join cdes cde
on kvg.kvgi = cde.kgi
group by [name], cde.kgi, kvg.mU
having count(cde.kgi) >= 2 or kvg.mU = 1 or 
   exists (select [name] from FP x where x.name = kvg.name) 

--Command 1: Takes 7 seconds
select [name] from wpvw_v where name like @foo

--Command 2: Takes 1 second
SELECT DISTINCT kvg.name
FROM         dbo.kvgs AS kvg LEFT JOIN
                      dbo.cdes AS cde ON kvg.kvgi = cde.kgi
where name like @foo
GROUP BY kvg.name, cde.kgi, kvg.mU
HAVING      (COUNT(cde.kgi) >= 2) OR
                      (kvg.mU = 1) OR
                      EXISTS
                          (SELECT     Name
                            FROM          dbo.FP AS x
                            WHERE      (Name = kvg.name))

Ответы [ 7 ]

1 голос
/ 18 сентября 2009

Я не думал, что предложение HAVING может вместить то, что вы опубликовали, но я считаю, что ваше мнение должно быть написано так, чтобы вместо него использовались UNION. Вот мой взгляд на это:

ALTER VIEW [dbo].[wpvw_v] AS
WITH names AS(
  SELECT k.name
    FROM KVGS k 
   WHERE EXISTS(SELECT NULL
                  FROM CDES c
                 WHERE c.kgi = k.kvgi
              GROUP BY c.kgi
                HAVING COUNT(c.kgi) > 1)
  UNION ALL
  SELECT k.name
    FROM KVGS k 
   WHERE k.mu = 1
GROUP BY k.name
  UNION ALL
  SELECT k.name
    FROM KVGS k 
    JOIN FP x ON x.name = k.name
GROUP BY k.name)
SELECT n.name
  FROM names n

Если вы хотите отфильтровать дубликаты между 3 операторами SQL, измените UNION ALL на UNION. Тогда вы можете использовать:

SELECT n.name
  FROM wpvw_v n
 WHERE CHARINDEX(@name, n.name) > 0
1 голос
/ 18 сентября 2009

Ваш запрос из вида выглядит так:

SELECT name FROM (SELECT DISTINCT name FROM ...) WHERE name = @name;

в то время как второй:

SELECT DISTINCT name FROM ... WHERE name = @name;

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

Суть проблемы в том, что наличие DISTINCT ставит барьер, который не позволяет предикату фильтрации перемещаться вниз по дереву запросов в место, где он эффективен.

Обновление

Даже если DISTINCT не является барьером, при втором взгляде на второй взгляд появляется еще более мощный барьер: предложение GROUP BY / HAVING. Один запрос фильтруется после применения условия GROUP и HAVING, другой - до. И условие HAVING имеет подзапросы, которые снова ссылаются на name. Я сомневаюсь, что QO может доказать эквивалентность фильтрации до агрегата и фильтрации после агрегата.

0 голосов
/ 19 сентября 2009

Табличные функции, как правило, работают быстрее, чем представления, при условии, что ваши условия WHERE известны и могут быть указаны в параметрах.

Одним из преимуществ табличных функций является то, что у вас может быть несколько операторов, поэтому вы можете преобразовывать ВНЕШНИЕ СОЕДИНЕНИЯ в более быстрые ВНУТРЕННИЕ СОЕДИНЕНИЯ в последующих операторах. Так что вместо этого:

INSERT INTO @resultTable
    table1_id,
    table1_column,
    table2_column,
    table3_column
SELECT
    table1.id,
    table1.column,
    table2.column,
    table3.column
FROM
    table1
    INNER JOIN table2 ON table2.table1_id = table1.id
    LEFT OUTER JOIN table3 ON table3.table1_id = table1.id

return @resultTable

... вы можете сделать это, что я считаю всегда быстрее:

INSERT INTO @resultTable
    table1_id,
    table1_column,
    table2_column,
SELECT
    table1.id,
    table1.column,
    table2.column,
FROM
    table1
    INNER JOIN table2 ON table2.table1_id = table1.id

UPDATE @resultTable SET
    table3_column = table3.column
FROM @resultTable AS result
    INNER JOIN table3 ON table3.table1_id = result.table1_id

return @resultTable
0 голосов
/ 19 сентября 2009

Я полагаю, что ваша проблема воспроизводится следующим образом:

create table tbl (idx int identity(1,1), name varchar(50), val float)

declare @cnt int
set @cnt=0
while @cnt < 10000
begin
insert tbl select char(CAST(rand()*256 AS INT)), rand()
set @cnt = @cnt + 1
end
go
create view tbl_view as select distinct name from tbl group by name having sum(val) > 1

Тогда, если вы выполните следующий запрос:

SET STATISTICS IO ON
declare @n varchar(50)
set @n='w%'
select * from tbl_view where name like @n
SET STATISTICS IO OFF
GO
SET STATISTICS IO ON
declare @n varchar(50)
set @n='w%'
select distinct name from tbl where name like @n group by name having sum(val) > 1
SET STATISTICS IO OFF

Вы получаете следующее:

(1 row(s) affected)
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tbl'. Scan count 1, logical reads 338, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)
Table 'tbl'. Scan count 1, logical reads 338, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Представление вынуждает его сначала работать с вложенной таблицей и только затем применять фильтр. Теперь, если вы измените представление и удалите DISTINCT , это не изменится. Но если вы измените представление, чтобы удалить группу:

create view tbl_view as select name from tbl where val > 0.8 group by name 
go
SET STATISTICS IO ON
declare @n varchar(50)
set @n='w%'
select * from tbl_view where name like @n
SET STATISTICS IO OFF
GO
SET STATISTICS IO ON
declare @n varchar(50)
set @n='w%'
select name from tbl where val > 0.8 and name like @n group by name
SET STATISTICS IO OFF

Тогда вы получите одинаковые результаты для обоих запросов:

(1 row(s) affected)
Table 'tbl'. Scan count 1, logical reads 34, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)
Table 'tbl'. Scan count 1, logical reads 34, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Так что, похоже, ИМЕЕТ барьер.

0 голосов
/ 18 сентября 2009

Не рассматривая больше (например, операторы CREATE TABLE, INDEX и CONSTRAINT для каждой таблицы) и предпочтительно рассматривая планы запросов как некоторые примерные данные, представляющие также количество элементов объединения, трудно сказать. *

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

Однако здесь, вероятно, достаточно места для настройки запросов. Кажется маловероятным, что вам нужно полностью объединить все COUNT (). У вас есть три довольно разных условия, при которых вы хотите видеть «имя» в вашем результате. С помощью UNION вы можете упростить вычисление одного или нескольких из них, а если параллелизм не является проблемой, вы даже можете написать это как многошаговую определяемую пользователем табличную функцию, которая собирает имена в отдельных шагах .

0 голосов
/ 18 сентября 2009

Вы можете попробовать встроенную табличную функцию (http://www.sqlhacks.com/index.php/Retrieve/Parameterized-View), но я вижу это как хак.

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

0 голосов
/ 18 сентября 2009

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

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