/*================================================================================== Curso: SQL SERVER 2019 https://www.udemy.com/course/draft/3957796/?referralCode=FB10D369E786D9FE8A48 Instrutor: Sandro Servino https://www.linkedin.com/in/sandroservino/?originalSubdomain=pt https://filiado.wixsite.com/sandroservino ATIVIDADES ROTINEIRAS BÁSICAS DO DBA PERFORMANCE, compactação de índices e incríveis Free Scripts para analisar índices. A partir do SQL Server 2008 foi implantada uma particularidade chamada compressão de dados. A compressão está disponível somente no SQL Server 2008 Enterprise e Developer. Essa compressão suporta dois tipos: ROW e PAGE. Compressão não está disponível para objetos de sistema. A configuração de compressão não é aplicada automaticamente a índices nonclustered, por isso cada índice nonclustered deve ser configurado individual e manualmente. A compressão de dados pode ser configurada para os seguintes objetos de banco de dados: - Para uma tabela que é armazenada como um heap. - Para uma tabela que é armazenada como um índice clustered. - Para um índice nonclustered. - Para uma view indexada. - Para tabelas e índices particionados, a opção de compressão pode ser configurada para cada partição, e as várias partições de um objeto podem conter diferentes tipos de configurações de compressão. Row Compression Level: Esta característica de compressão leva em conta o tipo de estruturas de dados variáveis que definem uma coluna. Row Compression Level é um nível de compressão que não utiliza nenhum algoritmo de compressão. O principal objetivo da Row Compression Level é reduzir o armazenamento de dados do tipo fixos, ou seja, quando você está permitindo Row Level Compression você está apenas mudando o formato de armazenamento físico dos dados que estão associados a um tipo de dados. Usa menos recursos (processador, memória, etc.) -- obs> ANTES DE RODAR EM PRODUCAO, RODE EM AMBIENTE DE DEV/QA E REALIZE OS DEVIDOS TESTES. ==================================================================================*/ use StackOverflow2010 go -- Vamos verificar a quantidade de dados da tabela e indice sp_spaceused users go sp_spaceused votes go -- Vamos verificar se as tabelas ja estao compactadas e por qual tipo SELECT DISTINCT object_name(A.[object_id]) AS [object_name], data_compression_desc FROM sys.partitions A join sys.objects B on A.[object_id] = B.[object_id] WHERE B.is_ms_shipped = 0 -- como estimar o nivel de compressao por linha e coluna -- Repare nas colunas size_with_current_compression_setting(KB) e -- size_with_requested_compression_setting(KB), essas colunas mostras -- o valor atual e o valor após a compressão. EXEC sp_estimate_data_compression_savings 'dbo', 'users', NULL, NULL, 'NONE' ; GO EXEC sp_estimate_data_compression_savings 'dbo', 'users', NULL, NULL, 'ROW' ; GO EXEC sp_estimate_data_compression_savings 'dbo', 'users', NULL, NULL, 'PAGE' ; GO EXEC sp_estimate_data_compression_savings 'dbo', 'votes', NULL, NULL, 'NONE' ; GO EXEC sp_estimate_data_compression_savings 'dbo', 'votes', NULL, NULL, 'ROW' ; GO EXEC sp_estimate_data_compression_savings 'dbo', 'votes', NULL, NULL, 'PAGE' ; GO -- Notem as colunas size_with_current_compression_setting(KB) e -- size_with_requested_compression_setting(KB), -- Essas colunas mostram o valor atual e o valor após a compressão. -- Dessa forma podemos saber quanto de espaço iremos ganhar com a aplicação da compressão. -- Vamos rodar estes codigos e verificar a quantidade de paginas lidas e o tempo set statistics io on go SELECT TOP (100) PERCENT Location, CreationDate, AccountId, COUNT(Id) AS qt, AVG(Id) AS avg, SUM(Id) AS sum, MIN(Id) AS min FROM dbo.Users GROUP BY Location, AccountId, CreationDate ORDER BY Location, AccountId, CreationDate option (recompile, maxdop 0) go -- 299.398 registros lidos, logical reads 7758. -- Vamos fazer a compressao via Script SQL: sp_spaceused USERS go ALTER TABLE USERS REBUILD WITH (DATA_COMPRESSION=ROW) GO ALTER INDEX ALL ON USERS REBUILD WITH ( FILLFACTOR = 100, SORT_IN_TEMPDB = ON, ONLINE = ON, DATA_COMPRESSION = ROW ) GO sp_spaceused USERS GO sp_spaceused VOTES go ALTER TABLE VOTES REBUILD WITH (DATA_COMPRESSION=ROW) GO ALTER INDEX ALL ON VOTES REBUILD WITH ( FILLFACTOR = 100, SORT_IN_TEMPDB = ON, ONLINE = ON, DATA_COMPRESSION = ROW ) GO sp_spaceused VOTES GO -- Note a diferença entre as colunas DATA e INDEX_SIZE. -- Vamos verificar novamente a quantidade de paginas de dados lidas novamente. SELECT TOP (100) PERCENT Location, CreationDate, AccountId, COUNT(Id) AS qt, AVG(Id) AS avg, SUM(Id) AS sum, MIN(Id) AS min FROM dbo.Users GROUP BY Location, AccountId, CreationDate ORDER BY Location, AccountId, CreationDate option (recompile, maxdop 0) go -- 299.398 registros lidos, logical reads 6004. -- Podemos fazer o mesmo processo de compactação graficamente pelo SSMS com tabela PostLinks. -- Clique com o botão direito em cima da tabela escolha a opção Storage -> Manage Compression ----------------------------- -- Page Compression Level -- O valor redundante ou duplicado é armazenado apenas uma vez dentro da página e o -- será referenciado em todas as outras ocorrências, dessa forma temos o Page Compression Level. -- Usa mais recursos (processador, memória, etc.) -- Maior taxa de compressão em relação ao nível de linha. Faz a compactação por linha também. -- Vamos verificar como ficara a mesma tabela com compressao em nivel de pagina. -- Primeiro vamos remover -- a compressao realizada anteriormente retirando a compressao por linha: ALTER TABLE VOTES REBUILD WITH (DATA_COMPRESSION=NONE) GO ALTER TABLE USERS REBUILD WITH (DATA_COMPRESSION=NONE) GO ALTER INDEX ALL ON VOTES REBUILD WITH ( FILLFACTOR = 100, SORT_IN_TEMPDB = ON, ONLINE = ON, DATA_COMPRESSION = NONE ) GO ALTER INDEX ALL ON USERS REBUILD WITH ( FILLFACTOR = 100, SORT_IN_TEMPDB = ON, ONLINE = ON, DATA_COMPRESSION = NONE ) GO sp_spaceused USERS GO sp_spaceused VOTES GO -- Vamos verificar se a tabela foi descompactada SELECT A.[partition_id], A.[object_id], object_name(A.[object_id]) AS [object_name], data_compression_desc FROM sys.partitions A join sys.objects B on A.[object_id] = B.[object_id] WHERE B.is_ms_shipped = 0 GO -- Agora vamos compactar a tabela e os indices da tabela com tipo de compressao = PAGE sp_spaceused USERS GO ALTER TABLE USERS REBUILD WITH (DATA_COMPRESSION=PAGE) GO ALTER INDEX ALL ON USERS REBUILD WITH ( FILLFACTOR = 100, SORT_IN_TEMPDB = ON, ONLINE = ON, DATA_COMPRESSION = PAGE ) GO sp_spaceused USERS GO sp_spaceused VOTES go ALTER TABLE VOTES REBUILD WITH (DATA_COMPRESSION=PAGE) GO ALTER INDEX ALL ON VOTES REBUILD WITH ( FILLFACTOR = 100, SORT_IN_TEMPDB = ON, ONLINE = ON, DATA_COMPRESSION = PAGE ) GO sp_spaceused VOTES GO -- Note a diferença entre as colunas DATA e INDEX_SIZE. -- Vamos verificar novamente a quantidade de paginas de dados lidas novamente. SELECT TOP (100) PERCENT Location, CreationDate, AccountId, COUNT(Id) AS qt, AVG(Id) AS avg, SUM(Id) AS sum, MIN(Id) AS min FROM dbo.Users GROUP BY Location, AccountId, CreationDate ORDER BY Location, AccountId, CreationDate option (recompile, maxdop 0) go -- 299.398 registros lidos, logical reads 5812. -- Vamos novamente remover a compactacao das tabelas e de todos os indices ALTER TABLE VOTES REBUILD WITH (DATA_COMPRESSION=NONE) GO ALTER TABLE USERS REBUILD WITH (DATA_COMPRESSION=NONE) GO ALTER INDEX ALL ON VOTES REBUILD WITH ( FILLFACTOR = 100, SORT_IN_TEMPDB = ON, ONLINE = ON, DATA_COMPRESSION = NONE ) GO ALTER INDEX ALL ON USERS REBUILD WITH ( FILLFACTOR = 100, SORT_IN_TEMPDB = ON, ONLINE = ON, DATA_COMPRESSION = NONE ) GO sp_spaceused USERS GO sp_spaceused VOTES GO -- Vamos verificar se a tabela foi descompactada SELECT A.[partition_id], A.[object_id], object_name(A.[object_id]) AS [object_name], data_compression_desc FROM sys.partitions A join sys.objects B on A.[object_id] = B.[object_id] WHERE B.is_ms_shipped = 0 GO ------------------------------------------------------------------------------------------ FIM -- Segue a seguir alguns scripts interessantes que serão uteis dentro deste contexto: -- 1: -- https://www.insidesql.org/blogs/holgerschmeling/2011/02/27/calculating-sql-server-data-compression-savings -- Determine the estimated impact of compression -- NOTE: This script is only for SQL Server Enterprise and Developer edition. set nocount on -- We create a temp table for the result if (object_id('tempdb..#comp', 'U') is not null) drop table #comp go create table #comp ( object_name sysname ,schema_name sysname ,index_id int ,partition_number int ,[size_with_current_compression_setting (KB)] bigint ,[size_with_requested_compression_setting (KB)] bigint ,[sample_size_with_current_compression_setting (KB)] bigint ,[sample_size_with_requested_compression_setting (KB)] bigint ) go -- Calculate estimated impact of page level compression for all -- user-tables and indexes in all schemas. -- NOTE: -- 1) To get the estimated impact of row level compression change the last parameter -- of sp_estimate_data_compression_savings to 'row' instead. -- 2) We don't care about partitioning here. If this is important for you, -- you have to modify forth parameter of sp_estimate_data_compression_savings. -- Please refer to BOL. declare @cmd nvarchar(max) set @cmd = '' select @cmd = @cmd +';insert #comp exec sp_estimate_data_compression_savings ''' + schema_name(schema_id)+''',''' + name + ''',null, null, ''page''' from sys.tables where objectproperty(object_id, 'IsUserTable') = 1 exec (@cmd) ; -- Do some further calculations for a more meaningful result with compressionSavings as ( select quotename(schema_name) + '.' + quotename(object_name) as table_name ,index_id ,[size_with_current_compression_setting (KB)] ,[size_with_requested_compression_setting (KB)] ,cast(case when [size_with_current_compression_setting (KB)] = 0 then 0 else 100.0*(1.0-1.0 *[size_with_requested_compression_setting (KB)] /[size_with_current_compression_setting (KB)]) end as decimal(6,2)) as [Estimated Savings (%)] from #comp ) select cs.table_name ,isnull(i.name, i.type_desc) as index_name ,cs.[size_with_current_compression_setting (KB)] ,cs.[size_with_requested_compression_setting (KB)] ,cs.[Estimated Savings (%)] from compressionSavings as cs left outer join sys.indexes as i on i.index_id = cs.index_id and i.object_id = object_id(cs.table_name, 'U') order by cs.[Estimated Savings (%)] desc -- Get rid of the temp table drop table #comp go ------------------------------------------------- FIM DO SCRIPT -- 2: -- IMPORTANTE: DEPOIS DE RODAR OS CODIGOS ABAIXO E COMPACTAR AS TABELAS/INDICES -- VER SE EXISTEM TABELAS HEAP (SEM INDICE CLUSTER, ou seja, dados são armazenados no heap -- sem especificar uma ordem). -- Alterar a configuração de compactação de um heap exige que todos os índices não clusterizados -- na tabela sejam reconstruídos para que eles tenham ponteiros para os novos locais de linha no heap. -- ENTAO DEPOIS DO REBUILD DAS TABELAS HEAP, PRECISA REALIZAR UM REBUILD EM TODOS OS INDICES NONCLUSTER -- mas nao precisa alterar nos jobs para colocar para compactar novamente, porque um novo rebuild -- nao perde a compactacao, -- a nao ser que explicite a retirada do compress WITH (DATA_COMPRESSION=NONE). -- PARA COMPACTAR TABELAS E INDICES SEGUE CODIGO. -- Basta gerar o script para criar os comandos e rodar em um job fora do horario. -- obs> ANTES DE RODAR EM PRODUCAO, RODE EM AMBIENTE DE DEV/QA E REALIZE OS DEVIDOS TESTES. --Creates the ALTER TABLE Statements SET NOCOUNT ON SELECT distinct 'ALTER TABLE ' + '[' + s.[name] + ']'+'.' + '[' + o.[name] + ']' + ' REBUILD WITH (DATA_COMPRESSION=PAGE, ONLINE = ON);' FROM sys.objects AS o WITH (NOLOCK) INNER JOIN sys.indexes AS i WITH (NOLOCK) ON o.[object_id] = i.[object_id] INNER JOIN sys.schemas AS s WITH (NOLOCK) ON o.[schema_id] = s.[schema_id] INNER JOIN sys.dm_db_partition_stats AS ps WITH (NOLOCK) ON i.[object_id] = ps.[object_id] AND ps.[index_id] = i.[index_id] WHERE o.[type] = 'U' --Creates the ALTER INDEX Statements SET NOCOUNT ON SELECT 'ALTER INDEX '+ '[' + i.[name] + ']' + ' ON ' + '[' + s.[name] + ']' + '.' + '[' + o.[name] + ']' + ' REBUILD WITH (DATA_COMPRESSION=PAGE, ONLINE = ON);' FROM sys.objects AS o WITH (NOLOCK) INNER JOIN sys.indexes AS i WITH (NOLOCK) ON o.[object_id] = i.[object_id] INNER JOIN sys.schemas s WITH (NOLOCK) ON o.[schema_id] = s.[schema_id] INNER JOIN sys.dm_db_partition_stats AS ps WITH (NOLOCK) ON i.[object_id] = ps.[object_id] AND ps.[index_id] = i.[index_id] WHERE o.type = 'U' AND i.[index_id] >0 ORDER BY ps.[reserved_page_count] ------------------------------------------------- FIM DO SCRIPT -- 3: -- Aplicando a compactacao em um banco de dados inteiro. -- https://www.dirceuresende.com/blog/comprimindo-todas-as-tabelas-de-um-database-no-sql-server/ -- O AUTOR sugere não compactar o banco de dados (Shrink), após, -- alterando o parametro @Fl_Rodar_Shrink. Eu também -- sugiro nao realizar Shrink database pelos problemas que este procedimento causa. -- Favor rever as aulas de Shrink se tiver duvidas. -- obs> ANTES DE RODAR EM PRODUCAO, RODE EM AMBIENTE DE DEV/QA E REALIZE OS DEVIDOS TESTES. --------------------------------------------------------------------------------------- -- Exemplo de como chamar a procedure abaixo que vai compactar todos os objetos do banco: USE AuditoriaDBA GO EXEC dbo.stpCompacta_Database @Ds_Database = 'AuditoriaDBA', -- sysname @Fl_Rodar_Shrink = 0, -- bit - desta forma nao compacta o banco no final. Mais seguro porque nao vai bloquear o banco. @Fl_Parar_Se_Falhar = 0, -- bit @Fl_Exibe_Comparacao_Tamanho = 1, -- bit @Fl_Metodo_Compressao_Page = 1 -- bit ------------------------------------------------------------------------------------ USE AuditoriaDBA GO CREATE PROCEDURE [dbo].[stpCompacta_Database] ( @Ds_Database SYSNAME, @Fl_Rodar_Shrink BIT = 1, @Fl_Parar_Se_Falhar BIT = 1, @Fl_Exibe_Comparacao_Tamanho BIT = 1, @Fl_Metodo_Compressao_Page BIT = 1 ) AS BEGIN SET NOCOUNT ON DECLARE @Ds_Query VARCHAR(MAX), @Ds_Comando_Compactacao VARCHAR(MAX), @Ds_Metodo_Compressao VARCHAR(20) = (CASE WHEN @Fl_Metodo_Compressao_Page = 1 THEN 'PAGE' ELSE 'ROW' END), @Nr_Metodo_Compressao VARCHAR(20) = (CASE WHEN @Fl_Metodo_Compressao_Page = 1 THEN 2 ELSE 1 END) IF (OBJECT_ID('tempdb..#Comandos_Compactacao') IS NOT NULL) DROP TABLE #Comandos_Compactacao CREATE TABLE #Comandos_Compactacao ( Id BIGINT IDENTITY(1, 1), Tabela SYSNAME, Indice SYSNAME NULL, Comando VARCHAR(MAX) ) IF (@Fl_Exibe_Comparacao_Tamanho = 1) BEGIN SET @Ds_Query = ' SELECT (SUM(a.total_pages) / 128) AS Vl_Tamanho_Tabelas_Antes_Compactacao FROM [' + @Ds_Database + '].sys.tables t WITH(NOLOCK) INNER JOIN [' + @Ds_Database + '].sys.indexes i WITH(NOLOCK) ON t.OBJECT_ID = i.object_id INNER JOIN [' + @Ds_Database + '].sys.partitions p WITH(NOLOCK) ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id INNER JOIN [' + @Ds_Database + '].sys.allocation_units a WITH(NOLOCK) ON p.partition_id = a.container_id WHERE i.OBJECT_ID > 255 ' EXEC(@Ds_Query) END SET @Ds_Query = 'INSERT INTO #Comandos_Compactacao( Tabela, Indice, Comando ) SELECT DISTINCT A.name AS Tabela, NULL AS Indice, ''ALTER TABLE ['' + ''' + @Ds_Database + ''' + ''].['' + C.name + ''].['' + A.name + ''] REBUILD PARTITION = ALL WITH (ONLINE = ON, DATA_COMPRESSION = ' + @Ds_Metodo_Compressao + ')'' AS Comando FROM [' + @Ds_Database + '].sys.tables A INNER JOIN [' + @Ds_Database + '].sys.partitions B ON A.object_id = B.object_id INNER JOIN [' + @Ds_Database + '].sys.schemas C ON A.schema_id = C.schema_id WHERE B.data_compression <> ' + @Nr_Metodo_Compressao + ' -- NONE AND B.index_id = 0 AND A.type = ''U'' UNION SELECT DISTINCT B.name AS Tabela, A.name AS Indice, ''ALTER INDEX ['' + A.name + ''] ON ['' + ''' + @Ds_Database + ''' + ''].['' + C.name + ''].['' + B.name + ''] REBUILD PARTITION = ALL WITH ( STATISTICS_NORECOMPUTE = OFF, ONLINE = ON, SORT_IN_TEMPDB = OFF, DATA_COMPRESSION = ' + @Ds_Metodo_Compressao + ')'' FROM [' + @Ds_Database + '].sys.indexes A INNER JOIN [' + @Ds_Database + '].sys.tables B ON A.object_id = B.object_id INNER JOIN [' + @Ds_Database + '].sys.schemas C ON B.schema_id = C.schema_id INNER JOIN [' + @Ds_Database + '].sys.partitions D ON A.object_id = D.object_id AND A.index_id = D.index_id WHERE D.data_compression <> ' + @Nr_Metodo_Compressao + ' -- NONE AND D.index_id <> 0 AND B.type = ''U'' ORDER BY Tabela, Indice ' EXEC(@Ds_Query) DECLARE @Qt_Comandos INT = (SELECT COUNT(*) FROM #Comandos_Compactacao), @Contador INT = 1, @Ds_Mensagem VARCHAR(MAX), @Nr_Codigo_Erro INT = (CASE WHEN @Fl_Parar_Se_Falhar = 1 THEN 16 ELSE 10 END) WHILE(@Contador <= @Qt_Comandos) BEGIN SELECT @Ds_Comando_Compactacao = Comando FROM #Comandos_Compactacao WHERE Id = @Contador BEGIN TRY SET @Ds_Mensagem = 'Executando comando "' + @Ds_Comando_Compactacao + '"... Aguarde...' RAISERROR(@Ds_Mensagem, 10, 1) WITH NOWAIT EXEC(@Ds_Comando_Compactacao) END TRY BEGIN CATCH SELECT ERROR_NUMBER() AS ErrorNumber, ERROR_SEVERITY() AS ErrorSeverity, ERROR_STATE() AS ErrorState, ERROR_PROCEDURE() AS ErrorProcedure, ERROR_LINE() AS ErrorLine, ERROR_MESSAGE() AS ErrorMessage; SET @Ds_Mensagem = 'Falha ao executar o comando "' + @Ds_Comando_Compactacao + '"' RAISERROR(@Ds_Mensagem, @Nr_Codigo_Erro, 1) WITH NOWAIT RETURN END CATCH SET @Contador = @Contador + 1 END IF (@Fl_Exibe_Comparacao_Tamanho = 1) BEGIN SET @Ds_Query = ' SELECT (SUM(a.total_pages) / 128) AS Vl_Tamanho_Tabelas_Depois_Compactacao FROM [' + @Ds_Database + '].sys.tables t WITH(NOLOCK) INNER JOIN [' + @Ds_Database + '].sys.indexes i WITH(NOLOCK) ON t.OBJECT_ID = i.object_id INNER JOIN [' + @Ds_Database + '].sys.partitions p WITH(NOLOCK) ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id INNER JOIN [' + @Ds_Database + '].sys.allocation_units a WITH(NOLOCK) ON p.partition_id = a.container_id WHERE i.OBJECT_ID > 255 ' EXEC(@Ds_Query) END IF (@Fl_Rodar_Shrink = 1) BEGIN SET @Ds_Query = ' USE ' + @Ds_Database + ' DBCC SHRINKFILE (' + @Ds_Database + ', 1) WITH NO_INFOMSGS ' EXEC(@Ds_Query) END IF (@Qt_Comandos > 0) PRINT 'Database "' + @Ds_Database + '" compactado com sucesso!' ELSE PRINT 'Nenhum objeto para compactar no database "' + @Ds_Database + '"' END ------------------------------------------------- FIM DO SCRIPT -- Vamos verificar se todas as tabelas do banco auditoriadba foram compactadas. SELECT A.[partition_id], A.[object_id], object_name(A.[object_id]) AS [object_name], data_compression_desc FROM sys.partitions A join sys.objects B on A.[object_id] = B.[object_id] WHERE B.is_ms_shipped = 0 GO