Temos uma tabela, que é particionada por meio de um campo de data em anos separados.
Há uma visão sobre todas essas mesas (Call)
Esquema é o seguinte:
CREATE TABLE [dbo].[Call_2015](
[calID] [uniqueidentifier] NOT NULL,
[calPackageID] [int] NULL,
[calClientID] [int] NULL,
[calStartDate] [datetime] NOT NULL,
[calEndDate] [datetime] NOT NULL,
[calTimeIn] [char](5) NULL,
[calTimeOut] [char](5) NULL,
[calMinutes] [smallint] NULL,
[calPreferredTimeIn] [char](5) NULL,
[calPreferredTimeOut] [char](5) NULL,
[calActualTimeIn] [char](5) NULL,
[calActualTimeOut] [char](5) NULL,
[calActualMinutes] [smallint] NULL,
[calConfirmed] [smallint] NULL,
[calCarerID] [int] NULL,
[calRepCarerID] [int] NULL,
[calOriginalCarerID] [int] NULL,
[calContractID] [int] NULL,
[calNeedID] [int] NULL,
[calMedicationID] [int] NULL,
[calFrequency] [smallint] NULL,
[calFromDate] [datetime] NULL,
[calWeekNo] [smallint] NULL,
[calAlert] [smallint] NULL,
[calNoLeave] [smallint] NULL,
[calTimeCritical] [smallint] NULL,
[calStatus] [smallint] NULL,
[calClientAwayReasonID] [int] NULL,
[calCarerAwayReasonID] [int] NULL,
[calOutsideShift] [smallint] NULL,
[calHistoryID] [int] NULL,
[calInvoiceID] [int] NULL,
[calWagesheetID] [int] NULL,
[calReasonID] [int] NULL,
[calCallConfirmID] [varchar](50) NULL,
[calCreated] [datetime] NULL,
[calUpdated] [datetime] NULL,
[calVariation] [int] NULL,
[calVariationUserID] [int] NULL,
[calException] [smallint] NULL,
[calRetained] [smallint] NULL,
[calDoubleUpID] [uniqueidentifier] NULL,
[calDoubleUpOrder] [smallint] NULL,
[calNeedCount] [smallint] NULL,
[calNoStay] [smallint] NULL,
[calCoverCarerID] [int] NULL,
[calPayAdjustment] [real] NULL,
[calChargeAdjustment] [real] NULL,
[calTeamID] [int] NULL,
[calExpenses] [money] NULL,
[calMileage] [real] NULL,
[calOverrideStatus] [smallint] NULL,
[calLocked] [smallint] NULL,
[calDriver] [smallint] NULL,
[calPostcode] [char](10) NULL,
[calDayCentreID] [int] NULL,
[calMustHaveCarer] [smallint] NULL,
[calRoleID] [int] NULL,
[calUnavailableCarerID] [int] NULL,
[calClientInformed] [smallint] NULL,
[calFamilyInformed] [smallint] NULL,
[calMonthlyDay] [smallint] NULL,
[calOriginalTimeIn] [char](5) NULL,
[calLeadCarer] [smallint] NULL,
[calCallTypeID] [int] NULL,
[calActualStartDate] [datetime] NULL,
[calActualEndDate] [datetime] NULL,
[Table_Year] [int] NOT NULL,
CONSTRAINT [PK_Call_2015] PRIMARY KEY CLUSTERED
(
[Table_Year] ASC,
[calID] ASC,
[calStartDate] ASC,
[calEndDate] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[Call_2015] WITH CHECK ADD CONSTRAINT [CK_Call_Year_2015] CHECK (([Table_Year]=(2015)))
GO
ALTER TABLE [dbo].[Call_2015] CHECK CONSTRAINT [CK_Call_Year_2015]
GO
ALTER TABLE [dbo].[Call_2015] WITH CHECK ADD CONSTRAINT [CK_calStartDate_2015] CHECK (([calStartDate]>=CONVERT([datetime],'01 Jan 2015 00:00:00',(0)) AND [calStartDate]<=CONVERT([datetime],'31 DEC 2015 23:59:59',(0))))
GO
ALTER TABLE [dbo].[Call_2015] CHECK CONSTRAINT [CK_calStartDate_2015]
GO
ALTER TABLE [dbo].[Call_2015] ADD CONSTRAINT [DF_Call_2015_Table_Year] DEFAULT ((2015)) FOR [Table_Year]
GO
A atualização da tabela é a seguinte:
UPDATE Call SET
calStartDate = CASE
WHEN calFrequency = 14 THEN dbo.funDate(@MonthlyDay, MONTH(calStartDate), YEAR(calStartDate))
WHEN calFrequency IN (15,16) THEN dbo.funMonthlyCallDate(calFrequency, @MonthlyDay, calStartDate)
ELSE DateAdd(d, @StartDay-1, (calStartDate - datepart(dw,calStartDate)+1))
END,
calEndDate = CASE
WHEN calFrequency = 14 THEN dbo.funDate(@MonthlyDay + @EndDay - @StartDay, MONTH(calStartDate), YEAR(calStartDate))
WHEN calFrequency IN (15,16) THEN DATEADD(D, @EndDay - @StartDay, dbo.funMonthlyCallDate(calFrequency, @MonthlyDay, calStartDate))
ELSE DateAdd(d, @StartDay-1+@DayCount, (calStartDate - datepart(dw,calStartDate)+1))
END,
calTimeIn = @TimeIn,
calTimeOut = @TimeOut,
calMinutes = @Minutes,
calMonthlyDay = @MonthlyDay,
calClientInformed = Null,
calFamilyInformed = Null
WHERE calPackageID = @PackageID
AND calClientID = @ClientID
AND calWeekNo = @WeekNo
AND (DatePart(dw, calStartDate) = @OriginalDay OR calFrequency IN (14,15,16))
AND calStartDate BETWEEN @StartDate AND @EndDate
AND (calInvoiceID = 0 OR calInvoiceID Is Null OR @InvoicesFinalised = 1)
AND (calWagesheetID = 0 OR calWagesheetID Is Null OR @WagesFinalised = 1)
AND (calLocked = 0 OR calLocked Is Null)
AND (Table_Year = YEAR(@StartDate)
OR Table_Year =YEAR(@EndDate))
O SP atualiza um lote de linhas dependentes da entrada em @StartDate e @EndDate (atualiza todas as linhas com uma calStartDate entre as duas)
O problema então vem com o plano de execução. Há enormes custos de E/S para a operação, e eu acertei como o SQL está lidando com a atualização.
Atualmente temos 20 dessas mesas; particionada por ano. Cada atualização está causando uma atualização dos índices de cada tabela, independentemente de a tabela ser realmente tocada pela operação de atualização ou não.
Abaixo desta seção, ele atualiza, exatamente da mesma maneira, todas as tabelas na exibição.
Não consigo entender por que isso ocorre, pois especifiquei o Table_Year (no qual a tabela está particionada) no texto da consulta. O SQL não deveria atualizar apenas a tabela necessária?
[Observação: também respondido em answers.SQLPerformance.com .]
Na verdade, essas não são tabelas particionadas e, mesmo que fossem, a eliminação de partições não funcionaria realmente para atualizar índices, a menos que todos os índices também fossem alinhados por partição.
Como você está usando o Express Edition e não pode realmente usar o particionamento, tenho uma abordagem diferente para recomendar: UPDATEs dinâmicos que afetam apenas a(s) tabela(s) representada(s) em @StartDate / @EndDate. Você terá que preencher a lista de parâmetros duas vezes; uma vez com seus tipos de dados - isso deve ser fácil, pois presumo que eles já estejam declarados em algum lugar.
Como você pode abranger no máximo dois anos (veja os comentários abaixo), você pode simplificar um pouco alterando o CTE para:
Mas observe que isso ainda terminará com o mesmo número de consultas a serem executadas e sem corrigir algumas das outras coisas (como as cláusulas não sargáveis em algumas das colunas), isso ainda produzirá exatamente o mesmo desempenho que meu original versão, você simplesmente não precisava digitar os anos reais. Tenha muito cuidado com essa "otimização" se for possível que StartDate e EndDate tenham mais de 365 dias de diferença.
A exibição atende a todos os requisitos de particionamento para arquivos
Table_Year
ecalStartDate
. A última coluna é modificada pelaUPDATE
instrução para que o otimizador de consulta tenha que produzir um plano capaz de mover linhas entre as partições.Na verdade, as linhas não poderiam se mover entre as partições neste caso porque há uma relação de 1:1 entre
Table_Year
os valores anuais decalStartDate
, mas as etapas envolvidas nesse raciocínio são muito opacas para o otimizador.O novo valor para
calStartDate
é baseado em uma expressão complexa que faz referência a variáveis. O plano de consulta será armazenado em cache e poderá ser reutilizado quando as variáveis tiverem valores diferentes, que é apenas outro fator que significa que o plano deve ser muito geral.Todas essas considerações levam a um plano que não apresenta eliminação de partições estáticas. No entanto, apresenta eliminação de partição dinâmica :
No lado da leitura, a cadeia de operadores Filter imediatamente abaixo da Concatenation são todos filtros de inicialização . Eles avaliam seu predicado antes que a subárvore seja executada. Se o predicado for avaliado como falso, a subárvore sob o Filtro não será executada.
O efeito geral é que apenas as tabelas sob a exibição que podem conter linhas qualificadas (dependendo dos valores das variáveis de tempo de execução) são acessadas. Observe que o plano de execução mostra apenas as linhas sendo lidas de uma das tabelas base e a propriedade Execuções reais para todas as outras tabelas base é zero; esses operadores não foram executados em tempo de execução.
No fragmento do plano abaixo, os filtros de inicialização garantem que apenas os operadores verdes sejam executados; os vermelhos nunca começam:
No lado da escrita, o filtro normal (não 'inicialização') logo à direita de cada operador Clustered Index Update garante que apenas as alterações da tabela atual sejam transmitidas. No plano de exemplo, apenas uma atualização de índice clusterizado (e seus operadores de manutenção de índice não clusterizados associados) recebe qualquer linha: