Tenho algumas dúvidas sobre o layout físico das tabelas quando elas são particionadas. Eu tenho pesquisado isso, mas ainda estou um pouco inseguro.
Digamos que eu tenha uma tabela existente: -
CREATE TABLE dbo.[ExampleTable]
(ID INT IDENTITY(1,1),
Col1 SYSNAME,
Col2 SYSNAME,
CreatedDATE DATE) ON [DATA];
ALTER TABLE dbo.[ExampleData] ADD CONSTRAINT [PK_ExampleTable] PRIMARY KEY CLUSTERED
( [ID] ASC )
GO
Desejo particionar esta tabela na coluna CreatedDate (todas as partições no mesmo grupo de arquivos para este exemplo), mas não posso ter a coluna como uma chave primária por conta própria. Então eu adiciono a coluna CreatedDate à chave primária: -
ALTER TABLE dbo.[ExampleTable] DROP CONSTRAINT PRIMARY KEY
ALTER TABLE dbo.[ExampleTable] ADD CONSTRAINT [PK_ExampleTable] PRIMARY KEY CLUSTERED
( [ID] ASC, [CreatedDate] ASC ) ON PartitionScheme(CreatedDate)
GO
Minha pergunta é como os dados serão classificados? Os dados serão fisicamente divididos em partições pela coluna CreatedDate e depois ordenados pela coluna ID? Ou as partições são lógicas e os dados permanecem ordenados pela coluna ID?
Além disso, o que aconteceria se a coluna ID fosse um GUID? Os dados estariam em partições e então terrivelmente fragmentados dentro dessas partições?
Qualquer conselho seria muito apreciado, obrigado.
André
EDIT:- Adicionando o esquema de partição e a função:-
DECLARE @CurrentDate DATETIME;
CREATE PARTITION FUNCTION PF_Example (DATETIME)
AS RANGE RIGHT
FOR VALUES (@CurrentDate+7,@CurrentDate+6,@CurrentDate+5,@CurrentDate+4,
@CurrentDate+3,@CurrentDate+2,@CurrentDate+1,@CurrentDate,
@CurrentDate-1,@CurrentDate-2,@CurrentDate-3,@CurrentDate-4,
@CurrentDate-5,@CurrentDate-6,@CurrentDate-7,@CurrentDate-8);
CREATE PARTITION SCHEME PS_Example
AS PARTITION PF_Example
ALL TO (Data);
Ok, então aqui está um exemplo rápido demonstrando por que - no caso em que a maioria de suas operações (consultas de relatórios, operações de arquivamento, trocas de partição, etc.) identificará intervalos de linhas por data - é melhor agrupar na coluna de particionamento. Vamos ter um esquema e função simples de partição baseada em data:
Em seguida, duas tabelas - uma com uma PK agrupada em ID, Data e um índice não agrupado em Data, e outra com uma PK não agrupada em ID, Data e um índice agrupado em Data.
Agora preencha-os com alguns dados:
Portanto, devemos ter 100 linhas na partição 1 e 50 linhas na partição 2, certo?
sys.partitions
confirma:Resultados:
Observe que em ambos os casos os dados no PK são todos armazenados em uma única partição. Como isso afeta as consultas? Bem, considere estes quatro, que provavelmente são típicos (além do
SELECT *
, usado apenas para concisão):Aqui estão alguns resultados do SQL Sentry Plan Explorer :*
Custos estimados e métricas reais de tempo de execução:
O
SELECT *
contra o PK não clusterizado executou uma busca de índice clusterizado eficiente, acessando apenas uma única partição:Quando o PK está em cluster, ele decide executar uma varredura de índice em cluster, o que significa que não pode eliminar partições, levando a mais leituras e, portanto, a um custo de E/S mais alto. Interessante notar, também, que a varredura não é ordenada.
Coisas semelhantes acontecem com o delete. A parte mais cara da operação de exclusão em ambos os casos é a exclusão do índice clusterizado; ter o benefício da eliminação de partições torna o PK não clusterizado muito mais desejável para suportar esta operação (mesmo que, no final das contas, as leituras necessárias e superiores sejam aproximadamente as mesmas).
Com o PK clusterizado, as linhas de origem são encontradas com uma busca (que você pode esperar ser mais eficiente), mas, novamente, a maior parte do trabalho é executada pela exclusão subsequente, portanto, pelo menos neste tamanho, não tem muito impacto em tudo:
Agora, em volumes muito mais altos, essa varredura inicial pode acabar inclinando a balança na outra direção, então você terá que testar.
É claro que, nesse limite inferior, isso tem um impacto negativo nas consultas de linha única em que você identifica por ID, pois normalmente identifica a linha por uma busca de índice e, em seguida, precisa fazer uma pesquisa, em vez de uma busca de índice clusterizado único. Vamos considerar essas duas consultas (novamente, em relação a
SELECT *
, faça o que eu digo, não o que eu faço):Resultados do Plan Explorer:
O primeiro é simples, precisa apenas de uma busca de índice clusterizado (e, portanto, sem pesquisas):
Mas, como mencionado, o segundo decide uma busca não particionada contra o PK, mas uma pesquisa de chave particionada . Nesse caso, isso acaba sendo mais caro, mas nem sempre, e nem sempre pode ser a escolha do otimizador.
O mesmo tipo de coisa pode acontecer com certas consultas de junção, dependendo de quantas linhas e como a junção é construída.
E, novamente, as escolhas do otimizador aqui geralmente dependem do volume. Enfim: depende . Minha escolha com as informações que você forneceu seria agrupar na chave de particionamento e usar um PK não agrupado. E eu evitaria fortemente usar um GUID para esse ID em ambos os casos - embora essa distribuição possa ser boa para inserções se você estiver tentando inserir 8 bilhões de linhas por segundo, isso não ajudará em nada mais que você esteja fazendo.
Outra opção é usar um único PK combinado em Date primeiro, depois ID:
Isso obviamente resulta em menos linhas sendo armazenadas em menos páginas (nenhum índice não clusterizado para manter, por exemplo):
Resultados:
Mas como isso afeta essas outras consultas? O
SELECT *
é idêntico ao daSELECT *
versão PK sem cluster; uma busca simples de índice clusterizado. ODELETE
, no entanto, é um plano muito mais simples:A busca de linha única, no entanto, acaba sendo muito mais cara:
Provavelmente, você pode combater isso com um índice de cobertura não clusterizado no ID, que converteria a varredura em uma busca (com uma pesquisa se o índice não for de cobertura), mas ainda não se beneficiaria da eliminação da partição.
*
Isenção de responsabilidade: eu trabalho para o SQL Sentry.