SQL Server 在AlwaysOn上使用內存表"踩坑"


介紹

因為線上alwayson環境的一個數據庫上使用內存表。經過大概一個星期監控程序發現了一個非常嚴重問題這個數據庫的日志文件不會截斷,已用空間一直在增加(存在定時的每個小時的日志備份),同時內存表數據庫文件也無法刪除,下面就介紹一下后面我的處理過程。

數據庫:SQL Server2014 Enterprise Edition (64-bit)

 

 

刪除文件


使用一個單獨非alwayson環境的數據庫測試。

一、創建內存表

---創建內存表文件組
ALTER DATABASE [test] ADD FILEGROUP [test_ag] CONTAINS MEMORY_OPTIMIZED_DATA   
GO 
----創建內存表數據庫文件
ALTER DATABASE [test] 
ADD FILE  
(  
   NAME = 'test_memory',  
   FILENAME ='D:\database\memory'  
)  
TO FILEGROUP [test_ag];  
GO  

二、刪除內存表數據庫文件

USE [test]
GO
ALTER DATABASE [test]  REMOVE FILE [test_memory]
GO

備注:此時還未創建表,創建完后數據庫文件執行刪除就無法刪除,接下來試試在線文檔的刪除方法方法

 三、官方相關的刪除方法

即使已使用“DBCC SHRINKFILE”操作清空 FILESTREAM 容器,但出於各種系統維護原因,數據庫可能仍然需要保留對已刪除文件的引用。 sp_filestream_force_garbage_collection (TRANSACT-SQL)將運行 FILESTREAM 垃圾回收器刪除這些文件時,則可以安全進行這些操作。 除非 FILESTREAM 垃圾回收器已從 FILESTREAM 容器中刪除所有文件,否則 ALTER DATABASEREMOVE FILE 操作將無法刪除 FILESTREAM 容器並返回錯誤。 建議使用以下過程刪除 FILESTREAM 容器。

1.運行DBCC SHRINKFILE (TRANSACT-SQL)帶有 EMPTYFILE 選項以將此容器的活動內容移動到其他容器。

USE test;  
GO  
-- Create a data file and assume it contains data.  
ALTER DATABASE test   
ADD FILE (  
    NAME = Test1data,  
    FILENAME = 'D:\database\t1data.ndf',  
    SIZE = 5MB  
    );  
GO  
-- Empty the data file.  
DBCC SHRINKFILE (test_memory, EMPTYFILE);  
GO  

2.確保已在 FULL 或 BULK_LOGGED 恢復模型中執行日志備份。

3.確保復制日志讀取器作業已運行(如果相關)。

通過log_reuse_wait_desc的狀態可以看到當前數據庫已經無需日志備份,當然我已經執行過日志備份。

4.運行sp_filestream_force_garbage_collection (TRANSACT-SQL)強制垃圾回收器刪除不再需要此容器中的任何文件。

USE [test]
GO  
EXEC sp_filestream_force_garbage_collection @dbname = N'test' @filename = N' test_memory ';  

5.執行帶有 REMOVE FILE 選項的 ALTER DATABASE,以刪除此容器。

USE [test]
GO
ALTER DATABASE [test]  REMOVE FILE [test_memory]
GO

還是無法刪除!!!

四、問題分析

一開始是在alwayson的環境中刪除,提示由於副本的原因無法刪除。后面單獨在一個非alwayson的環境下的數據庫測試同樣是無法刪除,起初以為是創建了內存表的原因后面測試僅僅創建文件組和文件然后來刪除文件同樣是無法刪除,個人猜測有可能是buffer的緣故;在buffer中一直存在內存表相關的文件存在,通過執行DBCC DROPCLEANBUFFERS命令也無法清空buffer中的內存表對象。使盡渾身解數還是無法將它刪除掉,最后只能投降了!!!線上環境等不下去;只能使用最不願使用的生成表結構導出數據的辦法來重建新的數據庫。

 

生成腳本重建數據庫


創建一個新的數據庫同時保證當前數據庫可用(重命名當前的數據庫,新創建的數據庫使用之前的名稱這樣可以保證應用程序那邊不需要改變),這樣如果出現什么問題也可以及時的切換回來。

步驟如下(在允許停機維護的情況下進行):

1.禁用所有相關作業

2禁用應用程序登入用戶

同時保證相關進程事務都已完成。

ALTER LOGIN [test] DISABLE
GO

USE [master]
GO
ALTER DATABASE [test] SET  SINGLE_USER WITH ROLLBACK IMMEDIATE;--將數據庫設置成單用戶並回滾當前連接

USE [test];---保持連接操作,防止其它用戶此時進行連接
GO

3.執行checkpoint刷新所有臟頁

CHECKPOINT

---返回當前buffer中每個數據庫所占的buffer大小和buffer中臟頁的大小
WITH    CTE1
          AS ( SELECT   COUNT(*) * 8 / 1024 AS dirty_cached_size_MB ,
                        COUNT(*) AS dirty_pages,
                        CASE database_id
                          WHEN 32767 THEN 'ResourceDb'
                          ELSE DB_NAME(database_id)
                        END AS database_name
               FROM     sys.dm_os_buffer_descriptors
               WHERE    is_modified = 1
               GROUP BY DB_NAME(database_id),database_id
             ),
        CET2
          AS ( SELECT   COUNT(*) * 8 / 1024 AS cached_size_MB ,
                        COUNT(*) AS pages,
                        CASE database_id
                          WHEN 32767 THEN 'ResourceDb'
                          ELSE DB_NAME(database_id)
                        END AS database_name
               FROM     sys.dm_os_buffer_descriptors
               GROUP BY DB_NAME(database_id),database_id
             )
    SELECT 
    CET2.database_name,
    CET2.cached_size_MB,
    --CET2.pages,
    CTE1.dirty_cached_size_MB
    --CTE1.dirty_pages 
    FROM CTE1 INNER JOIN CET2 ON CTE1.database_name = CET2.database_name

---將數據庫選項改成多用戶訪問
ALTER DATABASE [test] 
SET MULTI_USER;

4.生成數據庫腳本

5.重命名舊的數據庫

注意:如果數據庫是在alwayson中,需要先從可用性數據庫中刪除,否則無法重命名數據庫。

/*
1.斷開數據庫所有連接同時禁止新的連接進來
2.比如禁止登入用戶、將實例設為單用戶模式等。
*/
----1.設置數據庫脫機
USE [master] 
ALTER DATABASE [test] SET  OFFLINE WITH ROLLBACK IMMEDIATE;

----2.手動修改數據庫物理文件名,例如將test.mdf改成test_old.mdf

----3.語句修改
USE [master] 
ALTER DATABASE [test] 
MODIFY FILE (NAME = test, FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\test_old.mdf');
GO
ALTER DATABASE [test] 
MODIFY FILE (NAME = test_log, FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\test_old_log.ldf');
GO

---4.設置數據庫在線
USE [master] 
ALTER DATABASE [test] SET  ONLINE


----5.修改數據庫邏輯文件名

USE [test]
GO
ALTER DATABASE [test] MODIFY FILE (NAME=N'test', NEWNAME=N'test_old')
GO
USE [test]
GO
ALTER DATABASE [test] MODIFY FILE (NAME=N'test_log', NEWNAME=N'test_old_log')
GO

----6.重命名數據庫
USE [master] 
EXEC sp_renamedb N'test', N'test_old';  

----7.查詢
SELECT *
FROM sys.master_files
WHERE database_id = DB_ID('test_old');

6.創建新的數據庫同時導入腳本到新的數據庫

如果同時導出表結構和數據在ssms工具中執行可能會因為腳本過大無法執行,可以使用sqlcmd工具執行腳本導入,具體方法可以百度一下。當然還有其他方法就是只導出表結構然后通過“導出數據\導入數據”的方法同步數據。

注意:如果使用“導出數據\導入數據”的方法同步數據,注意勾選“啟用標示插入”

7.其它

1.如果存在alwayson記得將新的數據庫加入到可用性數據庫組中。

2.將新的數據庫加入到備份作業中。

3.對比新舊兩個數據庫的表數量是否相同。

4.配置登入用戶新的數據庫權限。

總結

內存表是2014新引入的功能所以對於新功能的第一個版本使用要比較慎重,特別是在線上環境。雖然在上線之前做過測試,但是顯然備份這塊的測試往往比較容易被忽略因為沒有線上的這種環境。好在是這次影響的是一個新上的項目數據量和並發都很小且允許節假日停機維護;如果是非常大的系統對於需要導入導出數據肯定是非常頭疼的事情關鍵還得看允許停機的時長。因為自己在生產環境踩了坑,寫這篇文章希望后面的人可以避免踩坑。

備注:內存表在2014版本的alwayson中無法同步到輔助副本,這就導致了它的作用大打折扣,2016版本可以同步到輔助副本,建議有條件的直接上2016。

 

 

 

 

備注:

    作者:pursuer.chen

    博客:http://www.cnblogs.com/chenmh

本站點所有隨筆都是原創,歡迎大家轉載;但轉載時必須注明文章來源,且在文章開頭明顯處給明鏈接。

《歡迎交流討論》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM