Нам нужно каждый вечер составлять отчеты на нашем SQL Server 2008 R2. Расчет отчетов занимает несколько часов. Чтобы сократить время, мы пересчитываем таблицу. Эта таблица создана на основе JOINining 12 довольно больших (десятки миллионов строк) таблиц.
Расчет этой таблицы агрегации занял несколько дней назад около 4 часов. Наш администратор баз данных разделил это большое объединение на 3 меньших объединения (каждое объединяет 4 таблицы). Временный результат каждый раз сохраняется во временной таблице, которая используется при следующем соединении.
Результатом улучшения DBA является то, что таблица агрегации рассчитывается за 15 минут. Я задавался вопросом, как это возможно. DBA сказал мне, что это потому, что количество данных, которые сервер должен обработать, меньше. Другими словами, что при большом оригинальном объединении сервер должен работать с большим количеством данных, чем при суммированных меньших объединениях. Однако я бы предположил, что оптимизатор позаботится о том, чтобы сделать это эффективно с оригинальным большим соединением, разделив объединения самостоятельно и отправив только количество столбцов, необходимое для следующих объединений.
Другая вещь, которую он сделал, - он создал индекс для одной из временных таблиц. Тем не менее, еще раз я думаю, что оптимизатор создаст соответствующие хеш-таблицы, если это необходимо, и в целом лучше оптимизирует вычисления.
Я говорил об этом с нашим администратором базы данных, но он сам не знал, что привело к улучшению времени обработки. Он только что упомянул, что не будет винить сервер, так как он может быть слишком сложным для вычисления таких больших данных, и что вполне возможно, что оптимизатору будет сложно предсказать лучший план выполнения .... Это я понимаю, но мне хотелось бы получить более четкий ответ, почему именно.
Итак, вопросы:
Что может вызвать большое улучшение?
Это стандартная процедура для разделения больших объединений на более мелкие?
Действительно ли объем данных, который должен обрабатывать сервер, будет меньше в случае нескольких меньших объединений?
Вот оригинальный запрос:
Insert Into FinalResult_Base
SELECT
TC.TestCampaignContainerId,
TC.CategoryId As TestCampaignCategoryId,
TC.Grade,
TC.TestCampaignId,
T.TestSetId
,TL.TestId
,TSK.CategoryId
,TT.[TestletId]
,TL.SectionNo
,TL.Difficulty
,TestletName = Char(65+TL.SectionNo) + CONVERT(varchar(4),6 - TL.Difficulty)
,TQ.[QuestionId]
,TS.StudentId
,TS.ClassId
,RA.SubjectId
,TQ.[QuestionPoints]
,GoodAnswer = Case When TQ.[QuestionPoints] Is null Then 0
When TQ.[QuestionPoints] > 0 Then 1
Else 0 End
,WrongAnswer = Case When TQ.[QuestionPoints] = 0 Then 1
When TQ.[QuestionPoints] Is null Then 1
Else 0 End
,NoAnswer = Case When TQ.[QuestionPoints] Is null Then 1 Else 0 End
,TS.Redizo
,TT.ViewCount
,TT.SpentTime
,TQ.[Position]
,RA.SpecialNeeds
,[Version] = 1
,TestAdaptationId = TA.Id
,TaskId = TSK.TaskId
,TaskPosition = TT.Position
,QuestionRate = Q.Rate
,TestQuestionId = TQ.Guid
,AnswerType = TT.TestletAnswerTypeId
FROM
[TestQuestion] TQ WITH (NOLOCK)
Join [TestTask] TT WITH (NOLOCK) On TT.Guid = TQ.TestTaskId
Join [Question] Q WITH (NOLOCK) On TQ.QuestionId = Q.QuestionId
Join [Testlet] TL WITH (NOLOCK) On TT.TestletId = TL.Guid
Join [Test] T WITH (NOLOCK) On TL.TestId = T.Guid
Join [TestSet] TS WITH (NOLOCK) On T.TestSetId = TS.Guid
Join [RoleAssignment] RA WITH (NOLOCK) On TS.StudentId = RA.PersonId And RA.RoleId = 1
Join [Task] TSK WITH (NOLOCK) On TSK.TaskId = TT.TaskId
Join [Category] C WITH (NOLOCK) On C.CategoryId = TSK.CategoryId
Join [TimeWindow] TW WITH (NOLOCK) On TW.Id = TS.TimeWindowId
Join [TestAdaptation] TA WITH (NOLOCK) On TA.Id = TW.TestAdaptationId
Join [TestCampaign] TC WITH (NOLOCK) On TC.TestCampaignId = TA.TestCampaignId
WHERE
T.TestTypeId = 1 -- eliminuji ankety
And t.ProcessedOn is not null -- ne vsechny, jen dokoncene
And TL.ShownOn is not null
And TS.Redizo not in (999999999, 111111119)
END;
Новые разделенные соединения после отличной работы DBA:
SELECT
TC.TestCampaignContainerId,
TC.CategoryId As TestCampaignCategoryId,
TC.Grade,
TC.TestCampaignId,
T.TestSetId
,TL.TestId
,TL.SectionNo
,TL.Difficulty
,TestletName = Char(65+TL.SectionNo) + CONVERT(varchar(4),6 - TL.Difficulty) -- prevod na A5, B4, B5 ...
,TS.StudentId
,TS.ClassId
,TS.Redizo
,[Version] = 1 -- ?
,TestAdaptationId = TA.Id
,TL.Guid AS TLGuid
,TS.TimeWindowId
INTO
[#FinalResult_Base_1]
FROM
[TestSet] [TS] WITH (NOLOCK)
JOIN [Test] [T] WITH (NOLOCK)
ON [T].[TestSetId] = [TS].[Guid] AND [TS].[Redizo] NOT IN (999999999, 111111119) AND [T].[TestTypeId] = 1 AND [T].[ProcessedOn] IS NOT NULL
JOIN [Testlet] [TL] WITH (NOLOCK)
ON [TL].[TestId] = [T].[Guid] AND [TL].[ShownOn] IS NOT NULL
JOIN [TimeWindow] [TW] WITH (NOLOCK)
ON [TW].[Id] = [TS].[TimeWindowId] AND [TW].[IsActive] = 1
JOIN [TestAdaptation] [TA] WITH (NOLOCK)
ON [TA].[Id] = [TW].[TestAdaptationId] AND [TA].[IsActive] = 1
JOIN [TestCampaign] [TC] WITH (NOLOCK)
ON [TC].[TestCampaignId] = [TA].[TestCampaignId] AND [TC].[IsActive] = 1
JOIN [TestCampaignContainer] [TCC] WITH (NOLOCK)
ON [TCC].[TestCampaignContainerId] = [TC].[TestCampaignContainerId] AND [TCC].[IsActive] = 1
;
SELECT
FR1.TestCampaignContainerId,
FR1.TestCampaignCategoryId,
FR1.Grade,
FR1.TestCampaignId,
FR1.TestSetId
,FR1.TestId
,TSK.CategoryId AS [TaskCategoryId]
,TT.[TestletId]
,FR1.SectionNo
,FR1.Difficulty
,TestletName = Char(65+FR1.SectionNo) + CONVERT(varchar(4),6 - FR1.Difficulty) -- prevod na A5, B4, B5 ...
,FR1.StudentId
,FR1.ClassId
,FR1.Redizo
,TT.ViewCount
,TT.SpentTime
,[Version] = 1 -- ?
,FR1.TestAdaptationId
,TaskId = TSK.TaskId
,TaskPosition = TT.Position
,AnswerType = TT.TestletAnswerTypeId
,TT.Guid AS TTGuid
INTO
[#FinalResult_Base_2]
FROM
#FinalResult_Base_1 FR1
JOIN [TestTask] [TT] WITH (NOLOCK)
ON [TT].[TestletId] = [FR1].[TLGuid]
JOIN [Task] [TSK] WITH (NOLOCK)
ON [TSK].[TaskId] = [TT].[TaskId] AND [TSK].[IsActive] = 1
JOIN [Category] [C] WITH (NOLOCK)
ON [C].[CategoryId] = [TSK].[CategoryId]AND [C].[IsActive] = 1
;
DROP TABLE [#FinalResult_Base_1]
CREATE NONCLUSTERED INDEX [#IX_FR_Student_Class]
ON [dbo].[#FinalResult_Base_2] ([StudentId],[ClassId])
INCLUDE ([TTGuid])
SELECT
FR2.TestCampaignContainerId,
FR2.TestCampaignCategoryId,
FR2.Grade,
FR2.TestCampaignId,
FR2.TestSetId
,FR2.TestId
,FR2.[TaskCategoryId]
,FR2.[TestletId]
,FR2.SectionNo
,FR2.Difficulty
,FR2.TestletName
,TQ.[QuestionId]
,FR2.StudentId
,FR2.ClassId
,RA.SubjectId
,TQ.[QuestionPoints] -- 1+ good, 0 wrong, null no answer
,GoodAnswer = Case When TQ.[QuestionPoints] Is null Then 0
When TQ.[QuestionPoints] > 0 Then 1 -- cookie
Else 0 End
,WrongAnswer = Case When TQ.[QuestionPoints] = 0 Then 1
When TQ.[QuestionPoints] Is null Then 1
Else 0 End
,NoAnswer = Case When TQ.[QuestionPoints] Is null Then 1 Else 0 End
,FR2.Redizo
,FR2.ViewCount
,FR2.SpentTime
,TQ.[Position] AS [QuestionPosition]
,RA.SpecialNeeds -- identifikace SVP
,[Version] = 1 -- ?
,FR2.TestAdaptationId
,FR2.TaskId
,FR2.TaskPosition
,QuestionRate = Q.Rate
,TestQuestionId = TQ.Guid
,FR2.AnswerType
INTO
[#FinalResult_Base]
FROM
[#FinalResult_Base_2] FR2
JOIN [TestQuestion] [TQ] WITH (NOLOCK)
ON [TQ].[TestTaskId] = [FR2].[TTGuid]
JOIN [Question] [Q] WITH (NOLOCK)
ON [Q].[QuestionId] = [TQ].[QuestionId] AND [Q].[IsActive] = 1
JOIN [RoleAssignment] [RA] WITH (NOLOCK)
ON [RA].[PersonId] = [FR2].[StudentId]
AND [RA].[ClassId] = [FR2].[ClassId] AND [RA].[IsActive] = 1 AND [RA].[RoleId] = 1
drop table #FinalResult_Base_2;
truncate table [dbo].[FinalResult_Base];
insert into [dbo].[FinalResult_Base] select * from #FinalResult_Base;
drop table #FinalResult_Base;
источник
READCOMMITTED
? Я никогда не видел ROWCOMMITTED раньше.Ответы:
1 Сокращение «пространства поиска» в сочетании с улучшенной статистикой для промежуточных / поздних объединений.
Мне приходилось иметь дело с объединениями из 90 таблиц (дизайн микки-мауса), когда обработчик запросов отказывался даже создавать план. Разбиение такого объединения на 10 подсоединений по 9 таблиц в каждой значительно снижает сложность каждого объединения, которое растет экспоненциально с каждой дополнительной таблицей. Плюс Оптимизатор запросов теперь рассматривает их как 10 планов, тратя (потенциально) больше времени в целом (у Пола Уайта даже могут быть метрики!).
Таблицы промежуточных результатов теперь будут иметь свою собственную свежую статистику, таким образом, объединяясь намного лучше, чем статистика глубокого дерева, которое рано смещается и вскоре становится научной фантастикой.
Кроме того, вы можете вначале принудительно выполнять самые выборочные объединения, сокращая объемы данных, перемещаясь вверх по дереву. Если вы можете оценить селективность своих предикатов намного лучше, чем Оптимизатор, почему бы не форсировать порядок соединения. Возможно, стоит поискать «Кустовые планы».
2 На мой взгляд, следует учитывать, важны ли эффективность и производительность
3 Не обязательно, но это может произойти, если самые селективные объединения выполняются рано
источник
источник
Хорошо, позвольте мне начать с того, что вы работаете с небольшими данными - 10 нс миллионов невелики. В последнем проекте DWH, который у меня был, 400 миллионов строк были добавлены в таблицу фактов. В ДЕНЬ. Хранение 5 лет.
Проблема аппаратная, частично. Поскольку большие объединения могут использовать много временного пространства, а оперативной памяти очень много, в тот момент, когда вы переполняете диск, все становится намного медленнее. Таким образом, может иметь смысл разделить работу на более мелкие части просто потому, что, хотя SQL живет в мире наборов и не заботится о размере, сервер, на котором вы работаете, не бесконечен. Я довольно привык выходить из-за ошибок пространства в 64 ГБ tempdb во время некоторых операций.
В противном случае, если все в порядке, оптимизатор запросов не перегружен. На самом деле неважно, насколько велика таблица - она работает по статистике, которая на самом деле не растет. ЭТО СКАЗАЛ: Если у вас действительно есть БОЛЬШАЯ таблица (двузначное число миллиардов строк), то они могут быть немного грубыми.
Существует также проблема блокировки - если вы не запрограммируете, что большое соединение может заблокировать таблицу на несколько часов. В настоящее время я выполняю операции копирования по 200 ГБ и делю их на smllerparty с помощью бизнес-ключа (эффективно зацикливающегося), который делает блокировки намного короче.
В конце мы работаем с ограниченным оборудованием.
источник