Dynamics CRM 2011 数据库清理
长时间运行之后,由于一些workflow在设定的时候没有选择删除运行记录,
长期运行下来导致AsyncOperationBase 和WorkflowLogBase保留很多历史运行记录,影响系统效率。
这时候使用下面的script在特定组织数据库下运行就可以删除掉那些运行记录了
IF EXISTS (SELECT name from sys.indexes WHERE name = N'CRM_AsyncOperation_CleanupCompleted') DROP Index AsyncOperationBase.CRM_AsyncOperation_CleanupCompleted GO CREATE NONCLUSTERED INDEX CRM_AsyncOperation_CleanupCompleted ON [dbo].[AsyncOperationBase] ([StatusCode],[StateCode],[OperationType]) GO while(1=1) begin declare @DeleteRowCount int = 10000 declare @rowsAffected int declare @DeletedAsyncRowsTable table (AsyncOperationId uniqueidentifier not null primary key) insert into @DeletedAsyncRowsTable(AsyncOperationId) Select top (@DeleteRowCount) AsyncOperationId from AsyncOperationBase where OperationType in (1, 9, 12, 25, 27, 10) AND StateCode = 3 AND StatusCode in (30, 32) select @rowsAffected = @@rowcount delete poa from PrincipalObjectAccess poa join WorkflowLogBase wlb on poa.ObjectId = wlb.WorkflowLogId join @DeletedAsyncRowsTable dart on wlb.AsyncOperationId = dart.AsyncOperationId delete WorkflowLogBase from WorkflowLogBase W, @DeletedAsyncRowsTable d where W.AsyncOperationId = d.AsyncOperationId delete BulkDeleteFailureBase From BulkDeleteFailureBase B, @DeletedAsyncRowsTable d where B.AsyncOperationId = d.AsyncOperationId delete WorkflowWaitSubscriptionBase from WorkflowWaitSubscriptionBase WS, @DeletedAsyncRowsTable d where WS.AsyncOperationId = d.AsyncOperationID delete AsyncOperationBase From AsyncOperationBase A, @DeletedAsyncRowsTable d where A.AsyncOperationId = d.AsyncOperationId /*If not calling from a SQL job, use the WAITFOR DELAY*/ if(@DeleteRowCount > @rowsAffected) return else WAITFOR DELAY '00:00:02.000' end
同时PrincipalObjectAccess 也会增大很多用户访问数据,这些也需要经常清理一下,利用下面的script
BEGIN TRY BEGIN TRAN t1 IF NOT EXISTS (SELECT * FROM sys.sysobjects WHERE id = object_id(N'[dbo].[ToDeletePOAEntries]') AND ObjectProperty(id, N'IsUserTable') = 1) create table ToDeletePoaEntries ( ObjectId uniqueidentifier, Otc int ) IF NOT EXISTS (SELECT * FROM sys.sysindexes si INNER JOIN sys.sysobjects so ON si.id = so.id WHERE so.id = OBJECT_ID(N'[dbo].[ToDeletePoaEntries]') AND OBJECTPROPERTY(so.id, N'IsUserTable') = 1 AND si.name LIKE '%mainindex%') CREATE UNIQUE NONCLUSTERED INDEX [mainindex] ON [dbo].[ToDeletePoaEntries] ( [ObjectId] ASC, [Otc] ASC )WITH (STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ----- Insert records to be deleted in ToDeletePoaEntries -- go through all user-owned entities which are not replicated and don't support duplicate detection declare entity_cursor cursor local FORWARD_ONLY READ_ONLY for select distinct e.ObjectTypeCode, e.BaseTableName, a.PhysicalName from EntityView e inner join AttributeView a on e.EntityId = a.EntityId and a.IsPKAttribute = 1 where e.IsReplicated = 0 and e.IsDuplicateCheckSupported = 0 and e.OwnershipTypeMask & 1 = 1 open entity_cursor declare @baseTableName sysname declare @otc nvarchar(20) declare @primaryKey sysname declare @totalCollected int = 0 declare @currentCollected int declare @tempRowCount int = 0 declare @collectstatement nvarchar(max) fetch next from entity_cursor into @otc, @baseTableName, @primaryKey while @@FETCH_STATUS = 0 begin print 'Cleaning up POA for ' + @baseTableName set @currentCollected = 0 set @collectstatement = 'insert into ToDeletePoaEntries(ObjectId, Otc) select distinct poa.ObjectId, poa.ObjectTypeCode from PrincipalObjectAccess poa left join ' + @baseTableName + ' e on poa.ObjectId = e.' + @primaryKey + ' where e.' + @primaryKey + ' is null and poa.ObjectTypeCode = ' + @otc; print @collectstatement exec(@collectstatement) set @tempRowCount = @@ROWCOUNT set @currentCollected = @currentCollected + @tempRowCount print CAST(@currentCollected as nvarchar(20)) + ' records collected for ' + @baseTableName set @totalCollected = @totalCollected + @currentCollected fetch next from entity_cursor into @otc, @baseTableName, @primaryKey end close entity_cursor deallocate entity_cursor print CAST(@totalCollected as nvarchar(20)) + ' total records collected' -- Delete query -- This scripts cleans up orphaned POA records for selected entities declare @deleteBatchSize int = 50000 declare @deleteBatchSizeNVarChar nvarchar(10) = CAST(@deleteBatchSize as nvarchar(10)) declare @totalDeleted int = 0 declare @currentDeleted int declare @deletestatement nvarchar(max) set @currentDeleted = 0 set @tempRowCount = 0 -- delete all records of the current entity type which don't have corresponding object in the base table set @deletestatement = 'delete top (' + @deleteBatchSizeNVarChar + ') from PrincipalObjectAccess from PrincipalObjectAccess poa join ToDeletePoaEntries e on poa.ObjectId = e.ObjectId and poa.ObjectTypeCode = e.Otc' print @deletestatement -- delete PrincipalObjectAccess records in batches exec(@deletestatement) set @tempRowCount = @@ROWCOUNT set @currentDeleted = @currentDeleted + @tempRowCount while @tempRowCount = @deleteBatchSize begin exec(@deletestatement) set @tempRowCount = @@ROWCOUNT set @currentDeleted = @currentDeleted + @tempRowCount print CAST(@currentDeleted as nvarchar(20)) + ' records deleted ' + cast(getUtcdate() as nvarchar(50)) --cleanup end COMMIT TRAN t1 -- Cleanup DROP Table [dbo].[ToDeletePoaEntries] PRINT 'EXECUTION SUCCEED' END TRY BEGIN CATCH ROLLBACK TRAN t1 -- Cleanup DROP Table [dbo].[ToDeletePoaEntries] PRINT 'EXECUTION FAILED :' + ERROR_MESSAGE() END CATCH
做完清理之后,重建一下index可以更好的利用缓存
ALTER INDEX ndx_for_cascaderelationship_lk_workflowlog_asyncoperations ON WorkflowLogBase REBUILD ALTER INDEX ndx_Security ON WorkflowLogBase REBUILD ALTER INDEX ndx_Cover ON PrincipalObjectAccess REBUILD ALTER INDEX ndx_PrimaryKey_PrincipalObjectAccess ON PrincipalObjectAccess REBUILD ALTER INDEX cndx_PrincipalObjectAccess ON PrincipalObjectAccess REBUILD ALTER INDEX fndx_Sync_VersionNumber ON PrincipalObjectAccess REBUILD ALTER INDEX UQ_PrincipalObjectAccess ON PrincipalObjectAccess REBUILD ALTER INDEX cndx_PrimaryKey_AsyncOperation ON AsyncOperationBase REBUILD ALTER INDEX ndx_SystemManaged_AsyncOperation ON AsyncOperationBase REBUILD
这些动作还是要经常进行的,最好做成sql agent job然后每个月运行
检查db_buffer_pages使用情况
-- Note: querying sys.dm_os_buffer_descriptors -- requires the VIEW_SERVER_STATE permission. DECLARE @total_buffer INT; SELECT @total_buffer = cntr_value FROM sys.dm_os_performance_counters WHERE RTRIM([object_name]) LIKE '%Buffer Manager' AND counter_name = 'Database Pages'; ;WITH src AS ( SELECT database_id, db_buffer_pages = COUNT_BIG(*) FROM sys.dm_os_buffer_descriptors --WHERE database_id BETWEEN 5 AND 32766 GROUP BY database_id ) SELECT [db_name] = CASE [database_id] WHEN 32767 THEN 'Resource DB' ELSE DB_NAME([database_id]) END, db_buffer_pages, db_buffer_MB = db_buffer_pages / 128, db_buffer_percent = CONVERT(DECIMAL(6,3), db_buffer_pages * 100.0 / @total_buffer) FROM src ORDER BY db_buffer_MB DESC;
检查index的空闲空间,需要rebuild
EXEC sp_MSforeachdb N'IF EXISTS (SELECT 1 FROM (SELECT DISTINCT DB_NAME ([database_id]) AS [name] FROM sys.dm_os_buffer_descriptors) AS names WHERE [name] = ''?'') BEGIN USE [?] SELECT ''?'' AS [Database], OBJECT_NAME (p.[object_id]) AS [Object], p.[index_id], i.[name] AS [Index], i.[type_desc] AS [Type], --au.[type_desc] AS [AUType], --DPCount AS [DirtyPageCount], --CPCount AS [CleanPageCount], --DPCount * 8 / 1024 AS [DirtyPageMB], --CPCount * 8 / 1024 AS [CleanPageMB], (DPCount + CPCount) * 8 / 1024 AS [TotalMB], --DPFreeSpace / 1024 / 1024 AS [DirtyPageFreeSpace], --CPFreeSpace / 1024 / 1024 AS [CleanPageFreeSpace], ([DPFreeSpace] + [CPFreeSpace]) / 1024 / 1024 AS [FreeSpaceMB], CAST (ROUND (100.0 * (([DPFreeSpace] + [CPFreeSpace]) / 1024) / (([DPCount] + [CPCount]) * 8), 1) AS DECIMAL (4, 1)) AS [FreeSpacePC] FROM (SELECT allocation_unit_id, SUM (CASE WHEN ([is_modified] = 1) THEN 1 ELSE 0 END) AS [DPCount], SUM (CASE WHEN ([is_modified] = 1) THEN 0 ELSE 1 END) AS [CPCount], SUM (CASE WHEN ([is_modified] = 1) THEN CAST ([free_space_in_bytes] AS BIGINT) ELSE 0 END) AS [DPFreeSpace], SUM (CASE WHEN ([is_modified] = 1) THEN 0 ELSE CAST ([free_space_in_bytes] AS BIGINT) END) AS [CPFreeSpace] FROM sys.dm_os_buffer_descriptors WHERE [database_id] = DB_ID (''?'') GROUP BY [allocation_unit_id]) AS buffers INNER JOIN sys.allocation_units AS au ON au.[allocation_unit_id] = buffers.[allocation_unit_id] INNER JOIN sys.partitions AS p ON au.[container_id] = p.[partition_id] INNER JOIN sys.indexes AS i ON i.[index_id] = p.[index_id] AND p.[object_id] = i.[object_id] WHERE p.[object_id] > 100 AND ([DPCount] + [CPCount]) > 12800 -- Taking up more than 100MB ORDER BY [FreeSpacePC] DESC; END';
检查index的缓存大小
;WITH src AS ( SELECT [Object] = o.name, [Type] = o.type_desc, [Index] = COALESCE(i.name, ''), [Index_Type] = i.type_desc, p.[object_id], p.index_id, au.allocation_unit_id FROM sys.partitions AS p INNER JOIN sys.allocation_units AS au ON p.hobt_id = au.container_id INNER JOIN sys.objects AS o ON p.[object_id] = o.[object_id] INNER JOIN sys.indexes AS i ON o.[object_id] = i.[object_id] AND p.index_id = i.index_id WHERE au.[type] IN (1,2,3) AND o.is_ms_shipped = 0 ) SELECT src.[Object], src.[Type], src.[Index], src.Index_Type, buffer_pages = COUNT_BIG(b.page_id), buffer_mb = COUNT_BIG(b.page_id) / 128 FROM src INNER JOIN sys.dm_os_buffer_descriptors AS b ON src.allocation_unit_id = b.allocation_unit_id WHERE b.database_id = DB_ID() GROUP BY src.[Object], src.[Type], src.[Index], src.Index_Type ORDER BY buffer_mb DESC;
当然最后还要重新整理一下缓存
DBCC FREESYSTEMCACHE ('ALL') DBCC FREESESSIONCACHE DBCC FREEPROCCACHE EXEC sys .sp_configure N'show advanced options', N'1' RECONFIGURE WITH OVERRIDE GO EXEC sys .sp_configure N'max server memory (MB)', N'4000' GO RECONFIGURE WITH OVERRIDE GO EXEC sys .sp_configure N'show advanced options', N'0' RECONFIGURE WITH OVERRIDE GO EXEC sys .sp_configure N'show advanced options', N'1' RECONFIGURE WITH OVERRIDE GO EXEC sys .sp_configure N'max server memory (MB)', N'11000' GO RECONFIGURE WITH OVERRIDE GO EXEC sys .sp_configure N'show advanced options', N'0' RECONFIGURE WITH OVERRIDE GO