При реализации древовидной структуры по базе данных сервера SQL 2005 ответ запроса занимает слишком много времени (ниже запросов, говорят больше чем 5 секунд), когда использование оператора LIKE, объединенного с, СУЩЕСТВУЕТ пункт.
Медленные запросы включают две таблицы - [SitePath_T] и [UserSiteRight_T]:
CREATE TABLE [dbo].[UserSiteRight_T](
[UserID_i] [int] NOT NULL
, [SiteID_i] [int] NOT NULL
, CONSTRAINT [PKC_UserSiteRight_UserIDSiteID] PRIMARY KEY CLUSTERED ( [UserID_i] ASC, [SiteID_i] ASC )
, CONSTRAINT [FK_UserSiteRight_UserID] FOREIGN KEY( [UserID_i] ) REFERENCES [dbo].[User_T] ( [ID_i] )
, CONSTRAINT [FK_UserSiteRight_SiteID] FOREIGN KEY( [SiteID_i] ) REFERENCES [dbo].[Site_T] ( [ID_i] )
)
Количество строк (права) для UserID_i = 2484 в таблице [UserSiteRight_T] является довольно небольшим: 545
(UserID_i = 2484 был случайным образом выбран),
Кроме того, база данных является относительно маленькой - только 23 000 строк в таблице [SitePath_T]:
CREATE TABLE [dbo].[SitePath_T] (
[SiteID_i] INT NOT NULL,
[Path_v] VARCHAR(255) NOT NULL,
CONSTRAINT [PK_SitePath_PathSiteID] PRIMARY KEY CLUSTERED ( [Path_v] ASC, [SiteID_i] ASC ),
CONSTRAINT [AK_SitePath_Path] UNIQUE NONCLUSTERED ( [Path_v] ASC ),
CONSTRAINT [FK_SitePath_SiteID] FOREIGN KEY( [SiteID_i] ) REFERENCES [Site_T] ( [ID_i] )
)
Я пытаюсь получить только SiteIDs, которые имеют подсайты, доступные определенным UserID (данный таблицей [UserSiteRight_T]) как:
SELECT sp.SiteID_i
FROM SitePath_t sp
WHERE EXISTS ( SELECT *
FROM [dbo].[SitePath_T] usp
, [dbo].[UserSiteRight_T] uusr
WHERE uusr.SiteID_i = usp.SiteID_i
AND uusr.UserID_i = 2484
AND usp.Path_v LIKE sp.Path_v+'%' )
Ниже Вас может найти часть результата где только SP столбца. SiteID_i нуждается/возвращается - также я добавил связанный соответствующий Path_v, UserSiteRight_T.SiteID_i ГДЕ UserID = 2484 и соответствующий SitePath_T SiteID_i и Path_v, соответствующий ПОДОБНОМУ условию:
sp.SiteID_i sp.Path_v [UserSiteRight_T].SiteID_i usp.SiteID_i usp.Path_v
1 '1.' NULL 10054 '1.10054.'
10054 '1.10054.' 10054 10054 '1.10054.'
10275 '1.10275.' 10275 10275 '1.10275.'
1533 '1.1533.' NULL 2697 '1.1533.2689.2693.2697.'
2689 '1.1533.2689.' NULL 2697 '1.1533.2689.2693.2697.'
2693 '1.1533.2689.2693.' NULL 2697 '1.1533.2689.2693.2697.'
2697 '1.1533.2689.2693.2697.' 2697 2697 '1.1533.2689.2693.2697.'
1580 '1.1580.' NULL 1581 '1.1580.1581.'
1581 '1.1580.1581.' 1581 1581 '1.1580.1581.'
1585 '1.1580.1581.1585.' 1585 1585 '1.1580.1581.1585.'
222 '1.222.' 222 222 '1.222.'
223 '1.222.223.' 223 223 '1.222.223.'
224 '1.222.223.224.' 224 224 '1.222.223.224.'
3103 '1.3103.' NULL 3537 '1.3103.3529.3533.3537.'
3529 '1.3103.3529.' NULL 3537 '1.3103.3529.3533.3537.'
3533 '1.3103.3529.3533.' NULL 3537 '1.3103.3529.3533.3537.'
3537 '1.3103.3529.3533.3537.' 3537 3537 '1.3103.3529.3533.3537.'
План выполнения относительно вышеупомянутого запроса:
|--Nested Loops(Left Semi Join, WHERE:([MyTestDB].[dbo].[SitePath_T].[Path_v] as [usp].[Path_v] like [Expr1007]))
|--Compute Scalar(DEFINE:([Expr1007]=[MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%', [Expr1008]=LikeRangeStart([MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%'), [Expr1009]=LikeRangeEnd([MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%'), [Expr1010]=LikeRangeInfo([MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%')))
| |--Index Scan(OBJECT:([MyTestDB].[dbo].[SitePath_T].[AK_SitePath_Path] AS [sp]))
|--Table Spool
|--Hash Match(Inner Join, HASH:([uusr].[SiteID_i])=([usp].[SiteID_i]))
|--Clustered Index Seek(OBJECT:([MyTestDB].[dbo].[UserSiteRight_T].[PKC_UserSiteRight_UserIDSiteID] AS [uusr]), SEEK:([uusr].[UserID_i]=(2484)) ORDERED FORWARD)
|--Index Scan(OBJECT:([MyTestDB].[dbo].[SitePath_T].[AK_SitePath_Path] AS [usp]))
И переписанный запрос:
SELECT DISTINCT
sp.SiteID_i
FROM [dbo].[SitePath_t] sp
, [dbo].[SitePath_T] usp
, [dbo].[UserSiteRight_T] uusr
WHERE ( uusr.SiteID_i = usp.SiteID_i
AND uusr.UserID_i = 2484
AND usp.Path_v LIKE sp.Path_v+'%' )
ORDER BY SiteID_i ASC
План выполнения:
|--Hash Match(Aggregate, HASH:([sp].[SiteID_i]))
|--Nested Loops(Inner Join, WHERE:([MyTestDB].[dbo].[SitePath_T].[Path_v] as [usp].[Path_v] like [Expr1006]))
|--Hash Match(Inner Join, HASH:([uusr].[SiteID_i])=([usp].[SiteID_i]))
| |--Clustered Index Seek(OBJECT:([MyTestDB].[dbo].[UserSiteRight_T].[PKC_UserSiteRight_UserIDSiteID] AS [uusr]), SEEK:([uusr].[UserID_i]=(2484)) ORDERED FORWARD)
| |--Index Scan(OBJECT:([MyTestDB].[dbo].[SitePath_T].[AK_SitePath_Path] AS [usp]))
|--Table Spool
|--Compute Scalar(DEFINE:([Expr1006]=[MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%', [Expr1007]=LikeRangeStart([MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%'), [Expr1008]=LikeRangeEnd([MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%'), [Expr1009]=LikeRangeInfo([MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%')))
|--Index Scan(OBJECT:([MyTestDB].[dbo].[SitePath_T].[AK_SitePath_Path] AS [sp]))
Все индексы существуют - Настраивающий Советник по вопросам Механизма базы данных не предлагает новую модификацию схемы - но оба запроса возвращают корректный результат в больше, что 5 секунд - и, поскольку это - ответ Ajax reques - чувства (и), очень медленный при обновлении дерева навигации
Какие-либо предложения для оптимизации / изменяют схему базы данных / индексы / запросы для получения более быстрого ответа?
Спасибо
Основываясь на:
SELECT sp.SiteID_i
FROM SitePath_t sp
WHERE EXISTS ( SELECT *
FROM [dbo].[SitePath_T] usp
, [dbo].[UserSiteRight_T] uusr
WHERE uusr.SiteID_i = usp.SiteID_i
AND uusr.UserID_i = 2484
AND usp.Path_v LIKE sp.Path_v+'%' )
(что просто прекрасно, исходя из того, что вы делаете Semi Join).
Сначала (правильно) фокусируемся на таблице uusr, чтобы найти записи для этого пользователя. Он уже делает CIX Seek на этом, что хорошо. Оттуда он находит соответствующие записи в usp по полям SiteID_i.
Так что далее рассмотрим тот факт, что он хочет найти Сайты по SiteID_i, и что это за присоединение.
Как насчет объединения? Это было бы неплохо, но требует сортировки данных с обеих сторон. Это нормально, если индексы находятся в правильном порядке...
... и после этого, ты хочешь найти что-то, основанное на Пути. Итак, как насчет:
CREATE INDEX ix_UUSR on [dbo].[UserSiteRight_T] (UserID_i, SiteID_i);
CREATE INDEX ix_usp on [dbo].[SitePath_T] (SiteID_i) INCLUDE (Path_v);
А затем еще один индекс на SitePath_T, который находит нужные вам идентификаторы сайтов:
CREATE INDEX ix_sp on [dbo].[SitePath_T] (Path_v) INCLUDE (SiteID_i);
Может быть, на этом последнем используется вложенный цикл, но это, надеюсь, не так уж и плохо. Вещь, которая повлияет на вашу систему, будет первые два индекса, которые должны позволить вам увидеть Слияние между двумя таблицами в вашем пункте EXISTS.
.Попробую добавить индекс по посторонним ключам в таблицу UserSiteRight_T
- они еще не проиндексированы, и индекс по этим полям должен ускорить поиск:
CREATE NONCLUSTERED INDEX IX01_UserSiteRight
ON UserSiteRight_T(UserID_i)
CREATE NONCLUSTERED INDEX IX02_UserSiteRight
ON UserSiteRight_T(SiteID_i)
а также в таблицу SitePath_T:
CREATE NONCLUSTERED INDEX IX01_SitePath
ON dbo.SitePath_T(SiteID_i)
Попробуйте поставить их на место, затем снова запустите ваши запросы, и сравните время выполнения и план выполнения - видите ли вы улучшения? ?
Это распространенное заблуждение, но SQL Server не автоматически помещает индекс в столбец с посторонним ключом (как SiteID_i
в SitePath_T
), хотя общее мнение заключается в том, что посторонний ключ полезен и потенциально ускоряет как обеспечение целостности ссылки, так и JOIN по этим посторонним ключам.
Самостоятельное присоединение к SitePath_T для поиска родителей убивает вас. Возможно, вам следует добавить столбец для ParentSiteID_i и использовать обычный рекурсивный CTE?
Тогда это будет:
WITH Recurse_CTE AS (
SELECT
us.SiteID_i
, us.ParentSiteID_i
, 0 AS RecurseDepth_i
FROM dbo.SitePath_T us
JOIN dbo.UserSiteRight_T uusr ON us.SiteID_i = uusr.SiteID_i
WHERE uusr.UserID_i = 2484
UNION ALL
SELECT
us.SiteID_i
, us.ParentSiteID_i
, rcs.RecurseDepth_i+1 AS RecurseDepth_i
FROM dbo.SitePath_T us
JOIN Recurse_CTE rcs ON us.SiteID_i = rcs.ParentSiteID_i
)
SELECT * FROM Recurse_CTE
Добавьте индекс на SitePath_T (ParentSiteID_i), и производительность должна быть быстрой.
Мэтт Геммелл имеет очень хорошую запись в блоге об этом: http://mattgemmell.com/2008/10/29/favorites-ui-design
-121--4667824-Вот решение - используйте поставщика комет SaaS, таких как WebSync On-Demand . Нет серверных ресурсов для беспокойства, общего хостинга или нет, так как все это выгружено, и вы можете вытолкнуть информацию по мере необходимости.
Поскольку это SaaS, он будет работать с любым языком сервера. Для PHP уже есть издатель, написанный и готовый идти.
-121--2907580-Я также хотел бы отдать должное Робу Фарли за понимание схемы/алгоритма и предоставление схемы индексирования.
Подробнее об этом в следующей публикации: Проблема SQL Server 2005 T-SQL: можете ли вы доверять оптимизатору запросов? Я знаю, что не могу!