Одна таблица 400 ГБ, один запрос - нужны идеи настройки (SQL2005) - PullRequest
13 голосов
/ 08 декабря 2008

У меня есть одна большая таблица, которую я хотел бы оптимизировать. Я использую сервер MS-SQL 2005. Я постараюсь описать, как он используется, и если у кого-то есть какие-либо предложения, я был бы очень признателен.

Таблица имеет размер около 400 ГБ, имеет 100 миллионов строк, и каждый день вставляется 1 миллион строк. Таблица имеет 8 столбцов, 1 столбец данных и 7 столбцов, используемых для поиска / упорядочения.

 k1 k2 k3 k4 k5 k6 k7 d1

, где

 k1: varchar(3), primary key - clustered index, 10 possible values
 k2: bigint, primary key - clustered index, total rows/10 possible values
 k3: int, 10 possible values
 k4: money, 100 possible values
 k5: bool
 k6: bool
 k7: DateTime

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

 SELECT TOP(g) d1 FROM table WITH(NOLOCK)
  WHERE k1 = a
  AND k3 = c
  AND k4 = d
  AND k5 = e
  AND k6 = f
  ORDER BY k7

где г = около 1 миллиона Этот запрос выполнялся около 10 раз в день (часто во время вставок) и занимает около 5-30 минут.

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

Будут ли хорошим выбором отдельные индексы для каждого столбца? Я думаю, что один индекс будет занимать около 5-8 ГБ. Всего на сервере БД 8 ГБ ОЗУ.

Пожалуйста, не говорите, что лучше всего экспериментировать. Это похоже на «я не знаю, разберись сам»:)

Любые советы очень ценятся!


РЕДАКТИРОВАТЬ doofledorfer -

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

- doofledorfer


РЕДАКТИРОВАТЬ: комментарии к сообщениям на сегодняшний день размещены ниже вместе с планом запроса - Мистер Флиббл

Вы, вероятно, связаны с вводом / выводом

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

Вы говорите, что не можете разделить данные, потому что используются все данные: НЕВОЗМОЖНО

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

Почему вы выбрали эти типы VARCHAR, вероятно, должен был быть INT, поскольку это может быть только несколько значений. Остальные достаточно разумны, деньги представляют собой денежную ценность в реальной жизни, а bigint - это удостоверение личности, а bool - вещи типа «однодневка»:)

Мы можем случайно взглянуть на оператор вставки или TSQL или bulkinsert

TSQL. В основном это INSERT INTO Table VALUES (k1, k2, k3, k4, k5, k6, d1). Единственное, что в любом случае интересно, это то, что делается много попыток вставки дубликатов, а ограничение PK k1 & k2 используется для предотвращения попадания дубликатов данных в базу данных. Во время разработки (и сейчас) я полагал, что это был такой же быстрый способ, как и любой другой, найти повторяющиеся данные.

Можете ли вы сказать, как часто происходит вставка Каждые 10 минут или около того запуска вставок (ADO.NET), может быть, 10 КБ за раз и занимает несколько минут. По моим оценкам, в настоящее время вставки полного дня занимают 40% времени дня.

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

Как ты пришел к этому Больше одного дня человек думает.

если вас интересуют только последние данные, удаление / архивирование ненужных данных может иметь смысл (начинайте каждое утро с нуля)

Меня не интересуют только последние данные. Запрос может выбрать некоторые из самых первых данных, которые были вставлены в таблицу, вплоть до данных, вставленных несколько минут назад. Но поскольку данные фильтруются, это не означает, что в этом запросе запрашиваются все данные в БД.

если есть только один «инсертор» и только один «ридер», вы можете переключиться на специализированный тип (hashmap / list / deque / stack) или что-то более сложное на языке программирования.

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

liggett78, вы предлагаете кластерный индекс для столбцов k1, k4, k5, k6, k3 или некластеризованный индекс для этих столбцов?


Мой главный вопрос сейчас заключается в том, должен ли я расширить текущий кластеризованный индекс, чтобы он также содержал k4 (это столбец со следующими наиболее возможными значениями), или мне просто добавить некластеризованный индекс в k4.

Можно ли добавить все k1-k6 в кластерный индекс? Тогда есть отдельный некластеризованный индекс в столбце DateTime для ORDER BY? Правильно ли я считаю, что это не приведет к значительному увеличению размера БД, но повлияет только на время вставки. Кто-нибудь может предположить, как это повлияет на вставки?

Я думаю, что если добавление индексов ко всем столбцам удвоит размер БД, то это невозможно без больших (т. Е. Аппаратных) изменений.


Следующий план был запущен с индексом (не кластеризованным) для столбца DATE.

EDIT: Не уверен, что вы можете увидеть XML ниже, поэтому вот ссылка на него: http://conormccarthy.com/box/queryplan.sqlplan.txt

<?xml version="1.0" encoding="utf-16"?>
<ShowPlanXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Version="1.0" Build="9.00.1399.06" xmlns="http://schemas.microsoft.com/sqlserver/2004/07/showplan">
  <BatchSequence>
    <Batch>
      <Statements>
        <StmtSimple StatementCompId="1" StatementEstRows="11111" StatementId="1" StatementOptmLevel="FULL" StatementSubTreeCost="625.754" StatementText="SELECT TOP(11111) d1 FROM hands WITH (NOLOCK) &#xD;&#xA;                                WHERE k4 = '10' &#xD;&#xA;                                AND k6 = 1 &#xD;&#xA;                                AND k5 = 1  &#xD;&#xA;                                AND k1 = 'IPN'  &#xD;&#xA;                                AND k3 BETWEEN 2 AND 10  &#xD;&#xA;                                ORDER BY k7 DESC&#xD;&#xA;&#xD;&#xA;" StatementType="SELECT">
          <StatementSetOptions ANSI_NULLS="false" ANSI_PADDING="false" ANSI_WARNINGS="false" ARITHABORT="true" CONCAT_NULL_YIELDS_NULL="false" NUMERIC_ROUNDABORT="false" QUOTED_IDENTIFIER="false" />
          <QueryPlan DegreeOfParallelism="1" CachedPlanSize="36">
            <MissingIndexes>
              <MissingIndexGroup Impact="81.7837">
                <MissingIndex Database="[MYDB]" Schema="[dbo]" Table="[Hands]">
                  <ColumnGroup Usage="EQUALITY">
                    <Column Name="[k1]" ColumnId="1" />
                    <Column Name="[k4]" ColumnId="7" />
                    <Column Name="[k5]" ColumnId="9" />
                    <Column Name="[k6]" ColumnId="10" />
                  </ColumnGroup>
                  <ColumnGroup Usage="INEQUALITY">
                    <Column Name="[k3]" ColumnId="6" />
                  </ColumnGroup>
                  <ColumnGroup Usage="INCLUDE">
                    <Column Name="[d1]" ColumnId="3" />
                    <Column Name="[k7]" ColumnId="4" />
                  </ColumnGroup>
                </MissingIndex>
              </MissingIndexGroup>
            </MissingIndexes>
            <RelOp AvgRowSize="75" EstimateCPU="0.0011111" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="11111" LogicalOp="Top" NodeId="0" Parallel="false" PhysicalOp="Top" EstimatedTotalSubtreeCost="625.754">
              <OutputList>
                <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="d1" />
              </OutputList>
              <RunTimeInformation>
                <RunTimeCountersPerThread Thread="0" ActualRows="11111" ActualEndOfScans="1" ActualExecutions="1" />
              </RunTimeInformation>
              <Top RowCount="false" IsPercent="false" WithTies="false">
                <TopExpression>
                  <ScalarOperator ScalarString="(11111)">
                    <Const ConstValue="(11111)" />
                  </ScalarOperator>
                </TopExpression>
                <RelOp AvgRowSize="83" EstimateCPU="135.557" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="11111" LogicalOp="Filter" NodeId="1" Parallel="false" PhysicalOp="Filter" EstimatedTotalSubtreeCost="625.753">
                  <OutputList>
                    <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="d1" />
                    <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k7" />
                  </OutputList>
                  <RunTimeInformation>
                    <RunTimeCountersPerThread Thread="0" ActualRows="11111" ActualEndOfScans="0" ActualExecutions="1" />
                  </RunTimeInformation>
                  <Filter StartupExpression="false">
                    <RelOp AvgRowSize="96" EstimateCPU="318.331" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="195691" LogicalOp="Inner Join" NodeId="2" Parallel="false" PhysicalOp="Nested Loops" EstimatedTotalSubtreeCost="625.404">
                      <OutputList>
                        <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="d1" />
                        <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k7" />
                        <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k3" />
                        <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k4" />
                        <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k5" />
                        <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k6" />
                      </OutputList>
                      <RunTimeInformation>
                        <RunTimeCountersPerThread Thread="0" ActualRows="341958" ActualEndOfScans="0" ActualExecutions="1" />
                      </RunTimeInformation>
                      <NestedLoops Optimized="false" WithOrderedPrefetch="true">
                        <OuterReferences>
                          <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k1" />
                          <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="HandId" />
                          <ColumnReference Column="Expr1003" />
                        </OuterReferences>
                        <RelOp AvgRowSize="32" EstimateCPU="330.366" EstimateIO="790.88" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="195691" LogicalOp="Index Scan" NodeId="4" Parallel="false" PhysicalOp="Index Scan" EstimatedTotalSubtreeCost="2.88444">
                          <OutputList>
                            <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k1" />
                            <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="HandId" />
                            <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k7" />
                          </OutputList>
                          <RunTimeInformation>
                            <RunTimeCountersPerThread Thread="0" ActualRows="341958" ActualEndOfScans="0" ActualExecutions="1" />
                          </RunTimeInformation>
                          <IndexScan Ordered="true" ScanDirection="BACKWARD" ForcedIndex="false" NoExpandHint="false">
                            <DefinedValues>
                              <DefinedValue>
                                <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k1" />
                              </DefinedValue>
                              <DefinedValue>
                                <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="HandId" />
                              </DefinedValue>
                              <DefinedValue>
                                <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k7" />
                              </DefinedValue>
                            </DefinedValues>
                            <Object Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Index="[ix_dateplayed]" />
                            <Predicate>
                              <ScalarOperator ScalarString="[MYDB].[dbo].[Hands].[k1]=N'IPN'">
                                <Compare CompareOp="EQ">
                                  <ScalarOperator>
                                    <Identifier>
                                      <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k1" />
                                    </Identifier>
                                  </ScalarOperator>
                                  <ScalarOperator>
                                    <Const ConstValue="N'IPN'" />
                                  </ScalarOperator>
                                </Compare>
                              </ScalarOperator>
                            </Predicate>
                          </IndexScan>
                        </RelOp>
                        <RelOp AvgRowSize="88" EstimateCPU="0.0001581" EstimateIO="0.003125" EstimateRebinds="195691" EstimateRewinds="0" EstimateRows="1" LogicalOp="Clustered Index Seek" NodeId="6" Parallel="false" PhysicalOp="Clustered Index Seek" EstimatedTotalSubtreeCost="621.331">
                          <OutputList>
                            <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="d1" />
                            <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k3" />
                            <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k4" />
                            <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k5" />
                            <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k6" />
                          </OutputList>
                          <RunTimeInformation>
                            <RunTimeCountersPerThread Thread="0" ActualRows="341958" ActualEndOfScans="0" ActualExecutions="341958" />
                          </RunTimeInformation>
                          <IndexScan Lookup="true" Ordered="true" ScanDirection="FORWARD" ForcedIndex="false" NoExpandHint="false">
                            <DefinedValues>
                              <DefinedValue>
                                <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="d1" />
                              </DefinedValue>
                              <DefinedValue>
                                <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k3" />
                              </DefinedValue>
                              <DefinedValue>
                                <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k4" />
                              </DefinedValue>
                              <DefinedValue>
                                <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k5" />
                              </DefinedValue>
                              <DefinedValue>
                                <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k6" />
                              </DefinedValue>
                            </DefinedValues>
                            <Object Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Index="[PK_Hands]" TableReferenceId="-1" />
                            <SeekPredicates>
                              <SeekPredicate>
                                <Prefix ScanType="EQ">
                                  <RangeColumns>
                                    <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k1" />
                                    <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="HandId" />
                                  </RangeColumns>
                                  <RangeExpressions>
                                    <ScalarOperator ScalarString="[MYDB].[dbo].[Hands].[k1]">
                                      <Identifier>
                                        <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k1" />
                                      </Identifier>
                                    </ScalarOperator>
                                    <ScalarOperator ScalarString="[MYDB].[dbo].[Hands].[HandId]">
                                      <Identifier>
                                        <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="HandId" />
                                      </Identifier>
                                    </ScalarOperator>
                                  </RangeExpressions>
                                </Prefix>
                              </SeekPredicate>
                            </SeekPredicates>
                          </IndexScan>
                        </RelOp>
                      </NestedLoops>
                    </RelOp>
                    <Predicate>
                      <ScalarOperator ScalarString="[MYDB].[dbo].[Hands].[k4]=($10.0000) AND [MYDB].[dbo].[Hands].[k6]=(1) AND [MYDB].[dbo].[Hands].[k5]=(1) AND [MYDB].[dbo].[Hands].[k3]&gt;=(2) AND [MYDB].[dbo].[Hands].[k3]&lt;=(10)">
                        <Logical Operation="AND">
                          <ScalarOperator>
                            <Compare CompareOp="EQ">
                              <ScalarOperator>
                                <Identifier>
                                  <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k4" />
                                </Identifier>
                              </ScalarOperator>
                              <ScalarOperator>
                                <Const ConstValue="($10.0000)" />
                              </ScalarOperator>
                            </Compare>
                          </ScalarOperator>
                          <ScalarOperator>
                            <Compare CompareOp="EQ">
                              <ScalarOperator>
                                <Identifier>
                                  <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k6" />
                                </Identifier>
                              </ScalarOperator>
                              <ScalarOperator>
                                <Const ConstValue="(1)" />
                              </ScalarOperator>
                            </Compare>
                          </ScalarOperator>
                          <ScalarOperator>
                            <Compare CompareOp="EQ">
                              <ScalarOperator>
                                <Identifier>
                                  <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k5" />
                                </Identifier>
                              </ScalarOperator>
                              <ScalarOperator>
                                <Const ConstValue="(1)" />
                              </ScalarOperator>
                            </Compare>
                          </ScalarOperator>
                          <ScalarOperator>
                            <Compare CompareOp="GE">
                              <ScalarOperator>
                                <Identifier>
                                  <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k3" />
                                </Identifier>
                              </ScalarOperator>
                              <ScalarOperator>
                                <Const ConstValue="(2)" />
                              </ScalarOperator>
                            </Compare>
                          </ScalarOperator>
                          <ScalarOperator>
                            <Compare CompareOp="LE">
                              <ScalarOperator>
                                <Identifier>
                                  <ColumnReference Database="[MYDB]" Schema="[dbo]" Table="[Hands]" Column="k3" />
                                </Identifier>
                              </ScalarOperator>
                              <ScalarOperator>
                                <Const ConstValue="(10)" />
                              </ScalarOperator>
                            </Compare>
                          </ScalarOperator>
                        </Logical>
                      </ScalarOperator>
                    </Predicate>
                  </Filter>
                </RelOp>
              </Top>
            </RelOp>
          </QueryPlan>
        </StmtSimple>
      </Statements>
    </Batch>
  </BatchSequence>
</ShowPlanXML>

Ответы [ 24 ]

17 голосов
/ 09 декабря 2008

Как я намекал в комментарии, я сделал это с помощью одной таблицы Oracle, которая приближается к 8 ТБ и состоит из более чем двух миллиардов строк, растущих со скоростью сорок миллионов строк в день. Однако, в моем случае, пользователями были два миллиона (и растущих) клиентов, которые обращались к этим данным через Интернет круглосуточно, и буквально ЛЮБОЙ из строк требовался доступ. О, и новые строки должны были быть добавлены в течение двух минут реального времени.

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

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

Вы говорите, что не можете разделить данные, потому что используются все данные: НЕВОЗМОЖНО! Невозможно, чтобы ваши пользователи просматривали миллион строк в день или сто миллионов строк. Итак, узнайте, как ваши пользователи на самом деле используют данные - посмотрите на каждый запрос в этом случае.

Что еще более важно, мы не говорим, что вы должны УДАЛИТЬ данные, мы говорим, чтобы РАЗДЕЛИТЬ данные. Клонируйте структуру таблицы в несколько таблиц с одинаковыми именами, вероятно, на основе времени (возможно, один месяц на таблицу). Скопируйте данные в соответствующие таблицы и удалите исходную таблицу. Создайте представление, которое выполняет объединение новых таблиц с тем же именем, что и исходная таблица. Измените обработку вставки так, чтобы она предназначалась для самой новой таблицы (при условии, что она уместна), и ваши запросы все равно должны работать с новым представлением.

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

Теперь у вас есть стратегия управления данными в форме архивации самой старой таблицы и ее удаления (конечно, обновите определение представления). Кроме того, вам нужно будет периодически создавать новую таблицу и обновлять определение представления для этого конца данных.

Ожидайте, что вы не сможете использовать уникальные индексы: они не масштабируются больше чем от одного до двух миллионов строк. Вам также может понадобиться изменить некоторые другие тактики / советы. Имея сто миллионов строк и 400 ГБ, вы вступили в другую область обработки.

Помимо этого, воспользуйтесь другими предложениями - проанализируйте фактическую производительность, используя множество инструментов, уже доступных в SQL Server и ОС. Применяйте многие известные приемы настройки, которые легко доступны в Интернете или в книгах.

Однако НЕ экспериментируйте! С таким большим количеством данных у вас нет времени на эксперименты, и риск слишком велик. Внимательно изучите доступные методы и ваши фактические данные о производительности, затем выберите один шаг за раз и дайте каждому несколько часов или дней, чтобы показать его влияние.

8 голосов
/ 08 декабря 2008

Помогите мне больше узнать о столе. если ваш PK k1, k2, вам не нужно выбирать ни по одному другому столбцу, чтобы получить совершенно уникальную запись.

Вы хотите сказать, что с k1 по 7 это ПК? Если так, объявите это как таковой, и это будет кластеризованный индекс. Производительность запросов должна значительно улучшиться.

В заказе добавлено много накладных расходов. Попробуйте найти лучший вариант, который может вернуть меньший набор данных. Знание того, почему вам нужно вернуть около миллиона записей, может помочь мне найти лучшее решение.

Редактировать: у меня возникает ощущение, что я не одинок в своем подозрении, что лучшее место для начала оптимизации - это дизайн вашей физической таблицы. Есть ли у вас контроль над этим? Не зная, что хранит каждый столбец, я не могу предложить очень конкретные идеи, но следует очень общий подход: поместите K1,3,4,5 & 6 (k2, по-видимому, напрямую связано со значениями в вашей таблице) в своей собственной таблице с единственным уникальным int как PK. Затем создайте отношение FK обратно к этой таблице. Ваш PK на главной таблице будет включать это поле, k2 и k7. Теперь ваш запрос оптимизатор выполнит довольно недорогой поиск в новой таблице, вернет одну запись и затем выполнит поиск индекса в вашей основной таблице только по PK.

5 голосов
/ 08 декабря 2008

Похоже, вы хотите только самые ранние записи "g"? Может быть, только самые последние записи "g"?

По сути, вы хотите, чтобы ваш запрос читал только самые последние / самые старые записи. Вы не хотите запросить все 400 Гб? Если это так, вы можете рассмотреть возможность архивирования большей части 400 ГБ или сохранения последних вставленных записей в «текущей» таблице, которую вы можете запросить. Вы можете сохранять записи в текущей таблице текущими через двойные вставки или через триггер на столе (дрожь). Но основная предпосылка заключается в том, что вы выполняете запрос к как можно меньшей таблице. Это в основном разделение таблиц бедняков.

5 голосов
/ 09 декабря 2008

OK

Давайте попробуем решить эту проблему с помощью статистики. Прежде чем пытаться создать какой-либо индекс, вы должны спросить, какая комбинация клавиш дает мне лучшую избирательность:

  1. K1: 10 различных значений
  2. K3: 100 различных значений
  3. k4: 10 различных значений
  4. k5: 2 разных значения
  5. k6: 2 разных значения

Если мы сделаем ключ компоновки из k1, k3, k4, k5 и k6, это означает, что ключ будет иметь только 40000 различных комбинаций (10 * 100 * 10 * 2 * 2). Это означает, что если у нас есть 100 000 000 записей, деленных на 40 000, то по статистике у нас будет подмножество 2500 различных записей, при котором будет применен последовательный поиск для выполнения других ограничений предложения WHERE.

Если мы экстраполируем этот результат и сравниваем его с текущим временем выполнения (30 минут) с помощью ключа (k1), который статистически генерирует подмножество из 10 миллионов различных записей, мы получаем:

10000000 записей * X сек = 30 * 60 секунд * 2500 записей

=> X сек = 0,45 сек

Неплохо, а? Еще лучше. Как насчет того, чтобы исключить k5 и k6 из индекса compund? По статистике у нас будет подмножество 10 000 различных записей, где будет выполняться последовательный поиск. Теоретически, сколько времени это займет? давайте посмотрим:

10 000 000 записей * X сек = 30 * 60 * 10 000 записей

=> X сек = 1,8 сек

Поскольку мы хотим, чтобы наименьшая площадь индекса была обменена с максимально возможной производительностью, я бы сказал, что индекс k1 + K3 + K4 так же хорош, как и он.

Надеюсь, это поможет,

5 голосов
/ 08 декабря 2008

Вот что я бы сделал:

  • Не создавайте отдельные индексы для каждого столбца. Вы будете тратить впустую пространство, и они не помогут вам много (если вообще)
  • Оставьте свой первичный ключ в покое, но создайте кластеризованный индекс в столбце даты, поскольку именно это вы используете в ORDER BY. Таким образом ядро ​​базы данных начнет сканировать кластеризованный ключ, сравнивать столбцы с предоставленными значениями и выходными строками, которые удовлетворяют условиям.
  • Вам не нужны никакие другие индексы для этого. Я считаю, что даже 100 значений из 100 миллионов для k4 будут считаться оптимизатором плохой селективностью (хотя вы можете попробовать это по крайней мере).
  • , если вы выбираете на основе некоторых диапазонов дат, например, только данные за последний месяц, неделю, год и т. д., возможно, вы захотите разбить большую таблицу на «меньшие» на основе столбца даты. Эти 10-значные столбцы также были бы хорошими кандидатами на ключи разделов.

Кстати, в запросе вы указываете весь PK - при условии AND'ing в WHERE - который выберет ровно 1 строку.

4 голосов
/ 08 декабря 2008

Прежде всего, проведите день с SQL Profiler, работающим в фоновом режиме. В конце дня сохраните данные трассировки в файл, а мастер оптимизации оптимизирует их и оценит текущий индекс. Это должно сказать вам, может ли изменение индексированных полей, порядка сортировки и т. Д. Принести вам какие-либо существенные выгоды. Не позволяйте мастеру вносить изменения. Если прирост производительности в процентах выглядит значительным (> 30% ИМХО), сделайте это самостоятельно.

Ваш индекс должен быть на большой стороне. Вы можете запланировать работу (на ночь, пару раз в неделю) для выполнения следующих действий:

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

Это сохранит скорость, как только вы настроите индексы.

3 голосов
/ 08 декабря 2008

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

У вас есть внешние ключи на k3, k4?

Попробуйте превратить k1, k2 в целые и сделать их внешними ключами, для одного из них будет гораздо меньше памяти, я бы подумал, и думаю, что это должно быть быстрее (хотя я могу ошибаться, думаю, SQL Сервер кеширует эти значения). Более того, проще, если вам когда-либо понадобится обновить значение. Вы просто меняете имя строки внешнего ключа - вам не нужно обновлять 100 миллионов первичных ключей или что-то в этом роде.

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

В

SELECT TOP(g) d1 
FROM table WITH(NOLOCK)  
WHERE k1 = a  WHERE k2 = b  WHERE k3 = c  WHERE k4 = d  WHERE k5 = e  WHERE k6 = f  
ORDER BY k7

Который, я полагаю, должен быть

SELECT TOP(g) d1 
FROM table WITH(NOLOCK)  
WHERE k1 = a AND k2 = b  AND k3 = c AND k4 = d AND k5 = e AND k6 = f 
ORDER BY k7

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

например.

SELECT TOP(g) d1 
FROM (SELECT * 
      FROM table k1=a AND k2=a WITH(NOLOCK)) 
WHERE AND k3 = c AND k4 = d AND k5 = e AND k6 = f 
ORDER BY k7

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

Администраторы баз данных, вероятно, имеют больше лучших решений!

3 голосов
/ 08 декабря 2008

Трудно дать вам очень значимый ответ. Вы смотрели на стоимость дискового ввода-вывода? Где вы храните файлы базы данных - возможно, это остановка ввода-вывода? Здесь так много переменных, которые могут повлиять на производительность. Возможно, это время, затраченное вашим пользовательским интерфейсом или что-то еще, чтобы отобразить данные, возможно, это время, затраченное Сетью?

Пожалуй, самый простой способ - где вы увидите наибольшую выгоду - разделить таблицу - если вы используете Enterprise Edition SQL Server 2005.

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

3 голосов
/ 08 декабря 2008

Почему вы сгруппированы по первичному ключу?
Какие столбцы могут быть NULL?
Каковы длины VARCHAR?
Что план запроса дает вам сейчас?

Вы препятствуете нам, давая бессмысленные названия столбцов.

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

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

1 голос
/ 12 декабря 2008

Рассматривали ли вы создание столбца суррогатной идентификации (типа bigint) и использование его в качестве кластеризованного индекса? Затем создайте свой первичный ключ как некластеризованный уникальный индекс.

С таблицей такого размера вполне возможно, что индекс и фрагментация страницы являются большой проблемой производительности. Суррогатный кластеризованный индекс гарантирует, что все вставки находятся в конце таблицы, что может почти полностью устранить фрагментацию страницы (если строки не будут удалены). Меньше фрагментации страницы == больше страниц на ввод-вывод, что очень хорошо.

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

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

После того, как вы это реализовали, подумайте (иначе, попробуйте и измерите ;-) добавив некластеризованный индекс в столбец k7.

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