Одна таблица 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 ]

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

Добавить один индекс со столбцами k1-k6 в нем; это должно быть лучшим.

Также, если вы можете запускать sp_updatestats перед каждым запросом.

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

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

Однако, если бы это был я, я бы попробовал индекс для K3, K4 (в том порядке, в котором вы чаще всего запрашиваете) (у вас уже есть индексы K1 и K2) и отдельный индекс для K7. Я не верю, что добавление логических полей улучшит производительность индекса.

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

0 голосов
/ 20 июня 2009

Вы можете попробовать:

alter table MyTable
    add constraint PK_MyTable
        primary key nonclustered (k1, k2)
create clustered index IX_MyTable
    on MyTable(k4, k1, k3, k5, k6, k7)
    --decreasing order of cardinality of the filter columns

Это гарантирует, что ваши дубликаты вставок будут продолжать выдавать ошибки.

Это также может указывать SQL Server фильтровать по (k1, k3, k4, k5, k6) и заказывать по (k7 asc) за один проход, позволяя SQL Server выполнять потоковую передачу результатов запроса без промежуточного этапа предварительной сортировки миллиона результатов. Как только SQL Server обнаружит, что первая строка соответствует (k1, k3, k4, k5, k6), все последующие миллионы строк или около того будут соответствовать одному и тому же фильтру и уже будут отсортированы в порядке (k7 asc). Вся фильтрация и упорядочение будут выполняться вместе на основе кластерного индекса.

При условии, что страницы хранятся последовательно, и если SQL Server знает, как оптимизировать, это несколько дисков, пытающихся пройтись по индексу, чтобы найти первую подходящую строку, за которой следует одно большое последовательное чтение диска из десяти тысяч страниц. Это должно быть быстрее, чем запрашивать у SQL Server повсеместный поиск строк, а затем просить SQL Server отсортировать их в базе данных tempdb!

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

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

Я бы сказал, что 8 ГБ недостаточно для таблицы 400 ГБ. Сервер не имеет возможности хранить соответствующие данные в памяти, если один индекс занимает 5-8 ГБ. Так что есть много и много операций чтения с жесткого диска, которые замедляют запрос.

По моему мнению, увеличение объема оперативной памяти и наличие базы данных на быстром RAID (возможно, разделенном на несколько RAID?) Помогло бы больше всего.

РЕДАКТИРОВАТЬ: Чтобы убедиться, что ваше реальное узкое место, запустите Windows ' Performance Monitor .

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