Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Saiba como otimizar o desempenho do seu código de transações num pool SQL dedicado, minimizando o risco de longos processos de reversão.
Transações e registo de eventos
Transactions are an important component of a relational SQL pool engine. Transactions are used during data modification. Estas transações podem ser explícitas ou implícitas. Instruções únicas INSERT, UPDATE e DELETE são exemplos de transações implícitas. As transações explícitas usam BEGIN TRAN, COMMIT TRAN ou ROLLBACK TRAN. Explicit transactions are typically used when multiple modification statements need to be tied together in a single atomic unit.
Changes to the SQL pool are tracked using transaction logs. Cada distribuição tem seu próprio log de transações. As gravações do log de transações são automáticas. Não é necessária nenhuma configuração. However, whilst this process guarantees the write it does introduce an overhead in the system. Você pode minimizar esse impacto escrevendo código transacionalmente eficiente. O código transacionalmente eficiente se enquadra amplamente em duas categorias.
- Utilize construções mínimas de registo sempre que possível
- Process data using scoped batches to avoid singular long running transactions
- Adotar um padrão de comutação de partição para grandes modificações em uma determinada partição
Minimal vs. full logging
Ao contrário das operações totalmente registradas, que usam o log de transações para acompanhar todas as alterações de linha, as operações minimamente registradas controlam apenas as alocações de extensão e as alterações de metadados. Portanto, o log mínimo envolve registrar apenas as informações necessárias para reverter a transação após uma falha ou para uma solicitação explícita (ROLLBACK TRAN). Como muito menos informações são rastreadas no log de transações, uma operação minimamente registrada tem um desempenho melhor do que uma operação totalmente registrada de tamanho semelhante. Furthermore, because fewer writes go the transaction log, a much smaller amount of log data is generated and so is more I/O efficient.
Os limites de segurança da transação só se aplicam a operações totalmente registradas.
Observação
Operações minimamente registradas podem participar de transações explícitas. Como todas as alterações nas estruturas de alocação são rastreadas, é possível reverter operações minimamente registradas.
Operações minimamente registadas
As seguintes operações podem ser minimamente registradas:
- CREATE TABLE AS SELECT (CTAS)
- INSERIR.. SELECIONAR
- CRIAR ÍNDICE
- ALTER INDEX REBUILD
- DROP INDEX
- TRUNCATE TABLE
- DROP TABLE
- ALTER TABLE SWITCH PARTITION
Observação
As operações internas de movimentação de dados (como BROADCAST e SHUFFLE) não são afetadas pelo limite de segurança da transação.
Minimal logging with bulk load
CTAS e INSERT... SELECT são ambas operações de carga em massa. No entanto, ambos são influenciados pela definição da tabela de destino e dependem do cenário de carga. A tabela a seguir explica quando as operações em massa são total ou minimamente registradas:
Índice primário | Cenário de carga | Modo de registo |
---|---|---|
Heap | Any | Mínimo |
Índice agrupado | Tabela de destino vazia | Mínimo |
Índice agrupado | Loaded rows do not overlap with existing pages in target | Mínimo |
Índice agrupado | Loaded rows overlap with existing pages in target | Full |
Clustered Columnstore Index | Batch size >= 102,400 per partition aligned distribution | Mínimo |
Clustered Columnstore Index | Batch size < 102,400 per partition aligned distribution | Full |
Vale a pena notar que todas as gravações para atualizar índices secundários ou não clusterizados sempre serão operações totalmente registradas.
Important
A dedicated SQL pool has 60 distributions. Therefore, assuming all rows are evenly distributed and landing in a single partition, your batch will need to contain 6,144,000 rows or larger to be minimally logged when writing to a Clustered Columnstore Index. Se a tabela for particionada e as linhas que estão sendo inseridas ampliarem os limites da partição, você precisará de 6.144.000 linhas por limite de partição, assumindo uma distribuição de dados uniforme. Each partition in each distribution must independently exceed the 102,400 row threshold for the insert to be minimally logged into the distribution.
O carregamento de dados em uma tabela não vazia com um índice clusterizado geralmente pode conter uma mistura de linhas totalmente registradas e minimamente registradas. A clustered index is a balanced tree (b-tree) of pages. If the page being written to already contains rows from another transaction, then these writes will be fully logged. However, if the page is empty then the write to that page will be minimally logged.
Optimizing deletes
DELETE é uma operação totalmente registrada. If you need to delete a large amount of data in a table or a partition, it often makes more sense to SELECT
the data you wish to keep, which can be run as a minimally logged operation. Para selecionar os dados, crie uma nova tabela com CTAS. Uma vez criada, use RENAME para trocar sua tabela antiga pela tabela recém-criada.
-- Delete all sales transactions for Promotions except PromotionKey 2.
--Step 01. Create a new table select only the records we want to kep (PromotionKey 2)
CREATE TABLE [dbo].[FactInternetSales_d]
WITH
( CLUSTERED COLUMNSTORE INDEX
, DISTRIBUTION = HASH([ProductKey])
, PARTITION ( [OrderDateKey] RANGE RIGHT
FOR VALUES ( 20000101, 20010101, 20020101, 20030101, 20040101, 20050101
, 20060101, 20070101, 20080101, 20090101, 20100101, 20110101
, 20120101, 20130101, 20140101, 20150101, 20160101, 20170101
, 20180101, 20190101, 20200101, 20210101, 20220101, 20230101
, 20240101, 20250101, 20260101, 20270101, 20280101, 20290101
)
)
AS
SELECT *
FROM [dbo].[FactInternetSales]
WHERE [PromotionKey] = 2
OPTION (LABEL = 'CTAS : Delete')
;
--Step 02. Rename the Tables to replace the
RENAME OBJECT [dbo].[FactInternetSales] TO [FactInternetSales_old];
RENAME OBJECT [dbo].[FactInternetSales_d] TO [FactInternetSales];
Optimizing updates
UPDATE é uma operação totalmente registrada. Se você precisar atualizar um grande número de linhas em uma tabela ou partição, muitas vezes pode ser muito mais eficiente usar uma operação minimamente registrada, como CTAS , para fazer isso.
In the example below a full table update has been converted to a CTAS so that minimal logging is possible.
Neste caso, estamos adicionando retrospetivamente um valor de desconto às vendas na tabela:
--Step 01. Create a new table containing the "Update".
CREATE TABLE [dbo].[FactInternetSales_u]
WITH
( CLUSTERED INDEX
, DISTRIBUTION = HASH([ProductKey])
, PARTITION ( [OrderDateKey] RANGE RIGHT
FOR VALUES ( 20000101, 20010101, 20020101, 20030101, 20040101, 20050101
, 20060101, 20070101, 20080101, 20090101, 20100101, 20110101
, 20120101, 20130101, 20140101, 20150101, 20160101, 20170101
, 20180101, 20190101, 20200101, 20210101, 20220101, 20230101
, 20240101, 20250101, 20260101, 20270101, 20280101, 20290101
)
)
)
AS
SELECT
[ProductKey]
, [OrderDateKey]
, [DueDateKey]
, [ShipDateKey]
, [CustomerKey]
, [PromotionKey]
, [CurrencyKey]
, [SalesTerritoryKey]
, [SalesOrderNumber]
, [SalesOrderLineNumber]
, [RevisionNumber]
, [OrderQuantity]
, [UnitPrice]
, [ExtendedAmount]
, [UnitPriceDiscountPct]
, ISNULL(CAST(5 as float),0) AS [DiscountAmount]
, [ProductStandardCost]
, [TotalProductCost]
, ISNULL(CAST(CASE WHEN [SalesAmount] <=5 THEN 0
ELSE [SalesAmount] - 5
END AS MONEY),0) AS [SalesAmount]
, [TaxAmt]
, [Freight]
, [CarrierTrackingNumber]
, [CustomerPONumber]
FROM [dbo].[FactInternetSales]
OPTION (LABEL = 'CTAS : Update')
;
--Step 02. Rename the tables
RENAME OBJECT [dbo].[FactInternetSales] TO [FactInternetSales_old];
RENAME OBJECT [dbo].[FactInternetSales_u] TO [FactInternetSales];
--Step 03. Drop the old table
DROP TABLE [dbo].[FactInternetSales_old]
Observação
A recriação de tabelas grandes pode se beneficiar do uso de recursos dedicados de gerenciamento de carga de trabalho do pool SQL. Para obter mais informações, consulte Classes de recursos para gerenciamento de carga de trabalho.
Optimizing with partition switching
If faced with large-scale modifications inside a table partition, then a partition switching pattern makes sense. Se a modificação de dados for significativa e abranger várias partições, a iteração sobre as partições alcançará o mesmo resultado.
As etapas para executar uma opção de partição são as seguintes:
- Criar uma partição vazia
- Perform the 'update' as a CTAS
- Switch out the existing data to the out table
- Switch in the new data
- Limpar os dados
No entanto, para ajudar a identificar as partições a serem alternadas, crie o seguinte procedimento auxiliar.
CREATE PROCEDURE dbo.partition_data_get
@schema_name NVARCHAR(128)
, @table_name NVARCHAR(128)
, @boundary_value INT
AS
IF OBJECT_ID('tempdb..#ptn_data') IS NOT NULL
BEGIN
DROP TABLE #ptn_data
END
CREATE TABLE #ptn_data
WITH ( DISTRIBUTION = ROUND_ROBIN
, HEAP
)
AS
WITH CTE
AS
(
SELECT s.name AS [schema_name]
, t.name AS [table_name]
, p.partition_number AS [ptn_nmbr]
, p.[rows] AS [ptn_rows]
, CAST(r.[value] AS INT) AS [boundary_value]
FROM sys.schemas AS s
JOIN sys.tables AS t ON s.[schema_id] = t.[schema_id]
JOIN sys.indexes AS i ON t.[object_id] = i.[object_id]
JOIN sys.partitions AS p ON i.[object_id] = p.[object_id]
AND i.[index_id] = p.[index_id]
JOIN sys.partition_schemes AS h ON i.[data_space_id] = h.[data_space_id]
JOIN sys.partition_functions AS f ON h.[function_id] = f.[function_id]
LEFT JOIN sys.partition_range_values AS r ON f.[function_id] = r.[function_id]
AND r.[boundary_id] = p.[partition_number]
WHERE i.[index_id] <= 1
)
SELECT *
FROM CTE
WHERE [schema_name] = @schema_name
AND [table_name] = @table_name
AND [boundary_value] = @boundary_value
OPTION (LABEL = 'dbo.partition_data_get : CTAS : #ptn_data')
;
GO
Este procedimento maximiza a reutilização de código e mantém o exemplo de comutação de partição mais compacto.
O código a seguir demonstra as etapas mencionadas anteriormente para alcançar uma rotina completa de comutação de partição.
--Create a partitioned aligned empty table to switch out the data
IF OBJECT_ID('[dbo].[FactInternetSales_out]') IS NOT NULL
BEGIN
DROP TABLE [dbo].[FactInternetSales_out]
END
CREATE TABLE [dbo].[FactInternetSales_out]
WITH
( DISTRIBUTION = HASH([ProductKey])
, CLUSTERED COLUMNSTORE INDEX
, PARTITION ( [OrderDateKey] RANGE RIGHT
FOR VALUES ( 20020101, 20030101
)
)
)
AS
SELECT *
FROM [dbo].[FactInternetSales]
WHERE 1=2
OPTION (LABEL = 'CTAS : Partition Switch IN : UPDATE')
;
--Create a partitioned aligned table and update the data in the select portion of the CTAS
IF OBJECT_ID('[dbo].[FactInternetSales_in]') IS NOT NULL
BEGIN
DROP TABLE [dbo].[FactInternetSales_in]
END
CREATE TABLE [dbo].[FactInternetSales_in]
WITH
( DISTRIBUTION = HASH([ProductKey])
, CLUSTERED COLUMNSTORE INDEX
, PARTITION ( [OrderDateKey] RANGE RIGHT
FOR VALUES ( 20020101, 20030101
)
)
)
AS
SELECT
[ProductKey]
, [OrderDateKey]
, [DueDateKey]
, [ShipDateKey]
, [CustomerKey]
, [PromotionKey]
, [CurrencyKey]
, [SalesTerritoryKey]
, [SalesOrderNumber]
, [SalesOrderLineNumber]
, [RevisionNumber]
, [OrderQuantity]
, [UnitPrice]
, [ExtendedAmount]
, [UnitPriceDiscountPct]
, ISNULL(CAST(5 as float),0) AS [DiscountAmount]
, [ProductStandardCost]
, [TotalProductCost]
, ISNULL(CAST(CASE WHEN [SalesAmount] <=5 THEN 0
ELSE [SalesAmount] - 5
END AS MONEY),0) AS [SalesAmount]
, [TaxAmt]
, [Freight]
, [CarrierTrackingNumber]
, [CustomerPONumber]
FROM [dbo].[FactInternetSales]
WHERE OrderDateKey BETWEEN 20020101 AND 20021231
OPTION (LABEL = 'CTAS : Partition Switch IN : UPDATE')
;
--Use the helper procedure to identify the partitions
--The source table
EXEC dbo.partition_data_get 'dbo','FactInternetSales',20030101
DECLARE @ptn_nmbr_src INT = (SELECT ptn_nmbr FROM #ptn_data)
SELECT @ptn_nmbr_src
--The "in" table
EXEC dbo.partition_data_get 'dbo','FactInternetSales_in',20030101
DECLARE @ptn_nmbr_in INT = (SELECT ptn_nmbr FROM #ptn_data)
SELECT @ptn_nmbr_in
--The "out" table
EXEC dbo.partition_data_get 'dbo','FactInternetSales_out',20030101
DECLARE @ptn_nmbr_out INT = (SELECT ptn_nmbr FROM #ptn_data)
SELECT @ptn_nmbr_out
--Switch the partitions over
DECLARE @SQL NVARCHAR(4000) = '
ALTER TABLE [dbo].[FactInternetSales] SWITCH PARTITION '+CAST(@ptn_nmbr_src AS VARCHAR(20)) +' TO [dbo].[FactInternetSales_out] PARTITION ' +CAST(@ptn_nmbr_out AS VARCHAR(20))+';
ALTER TABLE [dbo].[FactInternetSales_in] SWITCH PARTITION '+CAST(@ptn_nmbr_in AS VARCHAR(20)) +' TO [dbo].[FactInternetSales] PARTITION ' +CAST(@ptn_nmbr_src AS VARCHAR(20))+';'
EXEC sp_executesql @SQL
--Perform the clean-up
TRUNCATE TABLE dbo.FactInternetSales_out;
TRUNCATE TABLE dbo.FactInternetSales_in;
DROP TABLE dbo.FactInternetSales_out
DROP TABLE dbo.FactInternetSales_in
DROP TABLE #ptn_data
Minimize logging with small batches
Para operações de modificação de dados grandes, pode fazer sentido dividir a operação em partes ou lotes para definir o escopo da unidade de trabalho.
Um código a seguir é um exemplo de trabalho. O tamanho do lote foi definido para um número trivial para destacar a técnica. Na realidade, o tamanho do lote seria significativamente maior.
SET NO_COUNT ON;
IF OBJECT_ID('tempdb..#t') IS NOT NULL
BEGIN
DROP TABLE #t;
PRINT '#t dropped';
END
CREATE TABLE #t
WITH ( DISTRIBUTION = ROUND_ROBIN
, HEAP
)
AS
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS seq_nmbr
, SalesOrderNumber
, SalesOrderLineNumber
FROM dbo.FactInternetSales
WHERE [OrderDateKey] BETWEEN 20010101 and 20011231
;
DECLARE @seq_start INT = 1
, @batch_iterator INT = 1
, @batch_size INT = 50
, @max_seq_nmbr INT = (SELECT MAX(seq_nmbr) FROM dbo.#t)
;
DECLARE @batch_count INT = (SELECT CEILING((@max_seq_nmbr*1.0)/@batch_size))
, @seq_end INT = @batch_size
;
SELECT COUNT(*)
FROM dbo.FactInternetSales f
PRINT 'MAX_seq_nmbr '+CAST(@max_seq_nmbr AS VARCHAR(20))
PRINT 'MAX_Batch_count '+CAST(@batch_count AS VARCHAR(20))
WHILE @batch_iterator <= @batch_count
BEGIN
DELETE
FROM dbo.FactInternetSales
WHERE EXISTS
(
SELECT 1
FROM #t t
WHERE seq_nmbr BETWEEN @seq_start AND @seq_end
AND FactInternetSales.SalesOrderNumber = t.SalesOrderNumber
AND FactInternetSales.SalesOrderLineNumber = t.SalesOrderLineNumber
)
;
SET @seq_start = @seq_end
SET @seq_end = (@seq_start+@batch_size);
SET @batch_iterator +=1;
END
Diretrizes de pausa e escalonamento
Dedicated SQL pool lets you pause, resume, and scale your dedicated SQL pool on demand. Quando você pausa ou dimensiona seu pool SQL dedicado, é importante entender que todas as transações a bordo são encerradas imediatamente; fazendo com que quaisquer transações abertas sejam revertidas. If your workload had issued a long running and incomplete data modification prior to the pause or scale operation, then this work will need to be undone. This undoing might impact the time it takes to pause or scale your dedicated SQL pool.
Important
Both UPDATE
and DELETE
are fully logged operations and so these undo/redo operations can take significantly longer than equivalent minimally logged operations.
The best scenario is to let in flight data modification transactions complete prior to pausing or scaling a dedicated SQL pool. No entanto, este cenário pode nem sempre ser prático. Para reduzir o risco de uma reversão longa, considere uma das seguintes opções:
- Reescrever operações de longa execução usando CTAS
- Break the operation into chunks; operating on a subset of the rows
Próximos passos
Consulte Transações no pool SQL dedicado para saber mais sobre níveis de isolamento e limites transacionais. For an overview of other Best Practices, see Dedicated SQL pool best practices.