SQL Server数据库文件与文件组总结

时间:2019-05-11 14:37:24 来源:互联网 作者: 神秘的大神 字体:

 

文件和文件组概念

 

关于文件与文件组,简单概括如下,详情请参考官方文档数据库文件和文件组Database Files and Filegroups或更多相关资料:

 

数据文件概念:

 

每个SQL Server数据库至少包含两个作系统文件:一个数据文件(data file)和一个日志文件(log file)。数据文件包含数据和对象,例如表、索引、存储过程和视图....。日志文件包含恢复数据库所需的所有事务的相关数据。其实在SQL Server中,数据文件分为三类,分别为:

 

主数据文件

 

        主数据文件包含数据库的启动信息,并指向数据库中的其他文件。 用户数据和对象可存储在此文件中,也可以存储在辅助数据文件中。每个数据库有一个主要数据文件。 

        主要数据文件的建议文件扩展名是 .mdf。

 

辅助数据文件

 

        辅助数据文件是可选的,由用户定义并存储用户数据。 通过将每个文件放在不同的磁盘驱动器上,辅助数据文件可用于将数据分散到多个磁盘上。 

        另外,如果数据库超过了单个 Windows 文件的最大大小,可以使用次要数据文件,这样数据库就能继续增长。

        辅助数据文件的建议文件扩展名是 .ndf。

 

    事务日志文件

        事务日志文件保存用于恢复数据库的日志信息。 每个数据库必须至少有一个日志文件。 事务日志的建议文件扩展名是 .ldf。

 

 

 

    注意:虽然文件的扩展名是可以修改的,但强烈建议不要去改扩展名。

 

 

文件组概念:

 

文件组(File Group),简单来说,就是数据文件的组合。为了便于分配和管理,可以将数据文件集合起来,放到文件组中。每个数据库有一个主要文件组(Primary File Group),这个是默认的。 此文件组包含主要数据文件和未放入其他文件组的所有次要文件。 可以创建用户定义的文件组,用于将数据文件集合起来,以便于管理、数据分配和放置。总体来说,文件组是一个逻辑概念,类似于Oracle数据库的表空间,文件组与文件的关系就类似于ORACLE中表空间与文件的概念。

 

文件组分类:

 

        主文件组(Primary)

        内存优化数据文件组(Memory Optimized Data)

        Filestream 文件组(Filestream)

        用户自定义文件组(User-defined)

 

注意事项:PRIMARY 文件组是默认文件组,除非使用 ALTER DATABASE 语句进行了修改。另外,即使指定其它文件组为默认文件组。但系统对象和表仍然分配给 PRIMARY 文件组,而不是新的默认文件组。

   

 

 

文件组的优缺点

 

 文件组是逻辑概念,一个文件组对应一个或多个文件。创建表的时候指定文件组即可(默认,指定PRIMARY文件组),这样可以隔离用户对文件的依赖,耦合性就要低很多。另外,使用多个文件组和文件不仅仅是为了分散IO和提高性能,还有高可用性方面的原因。有关一个数据库应该包含几个文件或文件组[下面部分内容来自SQL Server中数据库文件的存放方式,文件和文件组]

 

  数据库中使用多个文件或文件组在高可用性方面的好处包括:

·         某文件的IO损坏,数据库还可以保证部分在线。

·         将索引和表分开存放,假如索引文件不在线,数据依然可以被访问。

·         历史数据和热数据分开,历史归档数据损坏,不影响热数据。

·         分开数据文件使得在灾难恢复时仅仅恢复部分数据从而缩短了宕机时间

·         数据库分为多个文件使得可以通过增加文件或移动文件的方式解决空间不足的问题

 

 

 从用户的角度来说,创建对象时需要指定存储文件组的只有三种数据对象:表,索引和大对象(LOB)

  

  使用文件组可以隔离用户对文件的依赖,使得用户仅仅针对文件组来建立表和索引,而不用关心实际磁盘中的文件的情况。当文件移动或修改时,由于用户建立的表和索引是建立在文件组上的,并不依赖具体文件,因此SQL Server可以放心的管理文件。

 

  另外,使用文件组的方式来管理文件,可以使得同一文件组内的文件分布在不同的硬盘中,能够大大提供IO性能。

 

  SQL Server根据每个文件设置的初始大小和增量值自动分配新加入的空间,假设在同一文件A设置的大小为文件B的两倍,新增一个数据占用3页,则按比例将2页分配到文件A中,1页分配到文件B

 

 

 

新增文件组

 

如下所示,创建两个文件组,DB_Data_Groups用来存放数据(聚集索引),DB_Index_Groups用来存放索引(非聚集索引)

 

 

--创建文件组DB_Index_Groups
USE [master]
GO
ALTER DATABASE [YourSQLDb] ADD FILEGROUP [DB_Index_Groups]
GO
 
--创建文件组DB_Data_Groups
USE [master]
GO
ALTER DATABASE [YourSQLDb] ADD FILEGROUP [DB_Data_Groups]
GO
 
 
--向文件组新增文件
USE [master];
GO
ALTER DATABASE [YourSQLDb] ADD FILE ( NAME = N'YourSQLDb_IND_01', FILENAME = N'D:\SQL_DATA\YourSQLDb_IND_01.ndf' , SIZE = 16GB , FILEGROWTH = 256MB ) TO FILEGROUP [DB_Index_Groups];
GO
ALTER DATABASE [YourSQLDb] ADD FILE ( NAME = N'YourSQLDb_IND_02', FILENAME = N'E:\SQL_DATA\YourSQLDb_IND_02.ndf' , SIZE = 16GB , FILEGROWTH = 256MB ) TO FILEGROUP [DB_Index_Groups];
GO
ALTER DATABASE [YourSQLDb] ADD FILE ( NAME = N'YourSQLDb_IND_03', FILENAME = N'F:\SQL_DATA\YourSQLDb_IND_03.ndf' , SIZE = 16GB , FILEGROWTH = 256MB ) TO FILEGROUP [DB_Index_Groups];
GO
ALTER DATABASE [YourSQLDb] ADD FILE ( NAME = N'YourSQLDb_IND_04', FILENAME = N'G:\SQL_DATA\YourSQLDb_IND_04.ndf' , SIZE = 16GB , FILEGROWTH = 256MB ) TO FILEGROUP [DB_Index_Groups];
GO
 
USE [master];
GO
ALTER DATABASE [YourSQLDb] ADD FILE ( NAME = N'YourSQLDb_DATA_01', FILENAME = N'D:\SQL_DATA\YourSQLDb_DATA_01.ndf' , SIZE = 26GB , FILEGROWTH = 256MB ) TO FILEGROUP [DB_Data_Groups];
GO
ALTER DATABASE [YourSQLDb] ADD FILE ( NAME = N'YourSQLDb_DATA_02', FILENAME = N'E:\SQL_DATA\YourSQLDb_DATA_02.ndf' , SIZE = 26GB , FILEGROWTH = 256MB ) TO FILEGROUP [DB_Data_Groups];
GO
ALTER DATABASE [YourSQLDb] ADD FILE ( NAME = N'YourSQLDb_DATA_03', FILENAME = N'F:\SQL_DATA\YourSQLDb_DATA_03.ndf' , SIZE = 26GB , FILEGROWTH = 256MB ) TO FILEGROUP [DB_Data_Groups];
GO
ALTER DATABASE [YourSQLDb] ADD FILE ( NAME = N'YourSQLDb_DATA_04', FILENAME = N'G:\SQL_DATA\YourSQLDb_DATA_04.ndf' , SIZE = 26GB , FILEGROWTH = 256MB ) TO FILEGROUP [DB_Data_Groups];
GO
 
 

 

 

 

 

 

文件组间移动数据

 

如何在SQL Server的文件组直接移动数据呢?关于这个问题呢,有时候也算简单,但是如果你没有踩过坑,不是一个老司机的话,建议你还是看一看下面内容。对于在文件组移动数据文件,分下面几种情况:

 

非分区表

 

1: 聚集索引表

   

    1.1 表没有LOB对象

       

        方法:重建聚集索引和非聚集索引可以移动文件组。

 

    1.2 表拥有LOB对象

           

        方法:重建表到新文件组,将数据导入。因为重建聚集索引,不会将LOB对象移动到对应表空间。

   

2: 堆表

   

    2.1 表没有LOB对象

 

        方法1:新建一个聚集索引,转移文件组后,删除聚集索引。

        方法2:重建表到新文件组,导入数据。

       

    2.1 表有LOB对象

 

        方法:重建表到新文件组,导入数据。

 

案例演示:我们要将数据库AdventureWorks2014中的表BusinessEntityAddress 从文件组Primary移动到文件组[DB_Data_Groups],在移动之前,我们先检查一下表BusinessEntityAddress的相关情况,如下所示,这个表属于上述情况的1.1。

 

 

DECLARE @file_group_name  NVARCHAR(32);
 
SET @file_group_name ='PRIMARY'
 
SELECT ds.name                         AS DataSpaceName 
      ,au.type_desc                    AS AllocationDesc 
      ,au.total_pages*1.0 / 128        AS [TotalSize(MB)] 
      ,au.used_pages*1.0  / 128        AS [UsedSize(MB)] 
      ,au.data_pages*1.0  / 128        AS [DataSize(MB)] 
      ,sch.name                        AS SchemaName     
      ,obj.name                        AS ObjectName 
      ,obj.type_desc                   AS ObjectType   
      ,idx.type_desc                   AS IndexType 
      ,idx.name                        AS IndexName 
      INTO #tmp_tables
FROM sys.data_spaces AS ds 
     INNER JOIN sys.allocation_units AS au 
         ON ds.data_space_id = au.data_space_id 
     INNER JOIN sys.partitions AS pa 
         ON (AU.type IN (1, 3)  
             AND au.container_id = pa.hobt_id) 
            OR 
            (au.type = 2 
             AND au.container_id = pa.partition_id) 
     INNER JOIN sys.objects AS obj
         ON pa.object_id = obj.object_id 
     INNER JOIN sys.schemas AS sch 
         ON obj.schema_id = sch.schema_id 
     LEFT JOIN sys.indexes AS idx 
         ON pa.object_id = idx.object_id 
            AND pa.index_id = idx.index_id 
WHERE DS.name=@file_group_name
ORDER BY AU.total_pages DESC
 
 
 
SELECT  *
FROM    #tmp_tables
WHERE   ObjectType = 'USER_TABLE' AND ObjectName='Person'
ORDER BY  [TotalSize(MB)]  DESC;
 
 
SELECT  COUNT(DISTINCT ObjectName)
FROM    #tmp_tables
WHERE   ObjectType != 'SYSTEM_TABLE';
DROP TABLE #tmp_tables;

 

 

<img title="clip_image001_thumb" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="clip_image001_thumb" src="https://img2018.cnblogs.com/blog/73542/201904/73542-20190425133734807-1068952205.png" width="753" height="137">

 

对于这种情况,我们用下面脚本生成重建索引的脚本(为什么要用脚本生成呢,实际情况中,可能有很多或大量的表需要移动表空间,此时脚本的效率就凸现出来了。)

 

--重新创建聚集索引,移动数据到文件组DB_Data_Groups
SELECT ' CREATE '
    CASE WHEN I.is_unique = 1 THEN ' UNIQUE ' ELSE '' END  +   
    I.type_desc COLLATE DATABASE_DEFAULT +' INDEX ' +    
    QUOTENAME(I.name)  + ' ON '  +   
    Schema_name(T.Schema_id)+'.'+ QUOTENAME(T.name) + ' ( '
    KeyColumns + ' )  '
    ISNULL(' INCLUDE ('+IncludedColumns+' ) ','') +  
    ISNULL(' WHERE  '+I.Filter_definition,'') + ' WITH ( '
    CASE WHEN I.is_padded = 1 THEN ' PAD_INDEX = ON ' ELSE ' PAD_INDEX = OFF ' END + ','  +  
    'FILLFACTOR = '+CONVERT(CHAR(5),CASE WHEN I.Fill_factor = 0 THEN 100 ELSE I.Fill_factor END) + ','  +  
    -- default value  
    'SORT_IN_TEMPDB = OFF '  + ','  +  
    CASE WHEN I.ignore_dup_key = 1 THEN ' IGNORE_DUP_KEY = ON ' ELSE ' IGNORE_DUP_KEY = OFF ' END + ','  +  
    CASE WHEN ST.no_recompute = 0 THEN ' STATISTICS_NORECOMPUTE = OFF ' ELSE ' STATISTICS_NORECOMPUTE = ON ' END + ','  +  
    -- default value   
    ' DROP_EXISTING = ON '  + ','  +  
    -- default value   
    ' ONLINE = OFF '  + ','  +  
   CASE WHEN I.allow_row_locks = 1 THEN ' ALLOW_ROW_LOCKS = ON ' ELSE ' ALLOW_ROW_LOCKS = OFF ' END + ','  +  
   CASE WHEN I.allow_page_locks = 1 THEN ' ALLOW_PAGE_LOCKS = ON ' ELSE ' ALLOW_PAGE_LOCKS = OFF ' END  + ' ) ON [DB_Data_Groups]; '  [CreateIndexScript]  
   --,I.is_unique
FROM sys.indexes I    
 JOIN sys.tables T ON T.Object_id = I.Object_id         
 JOIN (SELECT * FROM (   
    SELECT IC2.object_id , IC2.index_id ,   
        STUFF((SELECT ' , ' + C.name + CASE WHEN MAX(CONVERT(INT,IC1.is_descending_key)) = 1 THEN ' DESC ' ELSE ' ASC ' END 
    FROM sys.index_columns IC1   
    JOIN Sys.columns C    
       ON C.object_id = IC1.object_id    
       AND C.column_id = IC1.column_id    
       AND IC1.is_included_column = 0   
    WHERE IC1.object_id = IC2.object_id    
       AND IC1.index_id = IC2.index_id    
    GROUP BY IC1.object_id,C.name,index_id   
    ORDER BY MAX(IC1.key_ordinal)   
       FOR XML PATH('')), 1, 2, '') KeyColumns    
    FROM sys.index_columns IC2    
    GROUP BY IC2.object_id ,IC2.index_id) tmp3 )tmp4    
  ON I.object_id = tmp4.object_id AND I.Index_id = tmp4.index_id   
 JOIN sys.stats ST ON ST.object_id = I.object_id AND ST.stats_id = I.index_id    
 JOIN sys.data_spaces DS ON I.data_space_id=DS.data_space_id    
 JOIN sys.filegroups FG ON I.data_space_id=FG.data_space_id    
 LEFT JOIN (SELECT * FROM (    
    SELECT IC2.object_id , IC2.index_id ,    
        STUFF((SELECT ' , ' + C.name  
    FROM sys.index_columns IC1    
    JOIN Sys.columns C     
       ON C.object_id = IC1.object_id     
       AND C.column_id = IC1.column_id     
       AND IC1.is_included_column = 1    
    WHERE IC1.object_id = IC2.object_id     
       AND IC1.index_id = IC2.index_id     
    GROUP BY IC1.object_id,C.name,index_id    
       FOR XML PATH('')), 1, 2, '') IncludedColumns     
   FROM sys.index_columns IC2     
   GROUP BY IC2.object_id ,IC2.index_id) tmp1    
   WHERE IncludedColumns IS NOT NULL ) tmp2     
ON tmp2.object_id = I.object_id AND tmp2.index_id = I.index_id    
WHERE  I.type=1 AND i.data_space_id=1 
AND t.name ='BusinessEntityAddress'
AND FG.name !='DB_Data_Groups'

 

 

此时上面脚本会生成下面脚本

 

 

CREATE  UNIQUE CLUSTERED INDEX [PK_BusinessEntityAddress_BusinessEntityID_AddressID_AddressTypeID] 
ON Person.[BusinessEntityAddress] (  BusinessEntityID ASC  , AddressID ASC  , AddressTypeID ASC  )   
WITH (  PAD_INDEX = OFF ,FILLFACTOR = 100  ,SORT_IN_TEMPDB = OFF , IGNORE_DUP_KEY = OFF , STATISTICS_NORECOMPUTE = OFF ,
    DROP_EXISTING = ON , ONLINE = OFF , ALLOW_ROW_LOCKS = ON , ALLOW_PAGE_LOCKS = ON  ) ON [DB_Data_Groups]; 

 

 

下面脚本生成对应表的非聚集索引,将其转移到表空间[DB_Index_Groups]

 

WITH NoClustTable AS
(
SELECT DISTINCT
        SCHEMA_NAME(so.schema_id) AS 'SchemaName' ,
        OBJECT_NAME(so.object_id) AS 'TableName' ,
        so.object_id AS 'object_id' ,
        CASE OBJECTPROPERTY(MAX(so.object_id), 'TableHasClustIndex')
          WHEN 0 THEN COUNT(si.index_id) - 1
          ELSE COUNT(si.index_id)
        END AS 'IndexCount' 
FROM    sys.objects so ( NOLOCK )
        JOIN sys.indexes si ( NOLOCK ) ON so.object_id = si.object_id
                                          AND so.type IN ( N'U', N'V' )
        JOIN sysindexes dmv ( NOLOCK ) ON so.object_id = dmv.id
                                          AND si.index_id = dmv.indid
        FULL OUTER JOIN ( SELECT    object_id ,
                                    COUNT(1) AS ColumnCount
                          FROM      sys.columns (NOLOCK)
                          GROUP BY  object_id
                        ) d ON d.object_id = so.object_id
WHERE   so.is_ms_shipped = 0
        AND so.object_id NOT IN (
        SELECT  major_id
        FROM    sys.extended_properties (NOLOCK)
        WHERE   name = N'microsoft_database_tools_support' )
        AND INDEXPROPERTY(so.object_id, si.name, 'IsStatistics') = 0
GROUP BY so.schema_id ,
        so.object_id
HAVING  ( OBJECTPROPERTY(MAX(so.object_id), 'TableHasClustIndex') = 0
          AND COUNT(si.index_id) - 1 > 0
        )
)
 
SELECT ' CREATE '
    CASE WHEN I.is_unique = 1 THEN ' UNIQUE ' ELSE '' END  +   
    I.type_desc COLLATE DATABASE_DEFAULT +' INDEX ' +    
    QUOTENAME(I.name)  + ' ON '  +   
    Schema_name(T.Schema_id)+'.'+ QUOTENAME(T.name) + ' ( '
    KeyColumns + ' )  '
    ISNULL(' INCLUDE ('+IncludedColumns+' ) ','') +  
    ISNULL(' WHERE  '+I.Filter_definition,'') + ' WITH ( '
    CASE WHEN I.is_padded = 1 THEN ' PAD_INDEX = ON ' ELSE ' PAD_INDEX = OFF ' END + ','  +  
    'FILLFACTOR = '+CONVERT(CHAR(5),CASE WHEN I.Fill_factor = 0 THEN 100 ELSE I.Fill_factor END) + ','  +  
    -- default value  
    'SORT_IN_TEMPDB = OFF '  + ','  +  
    CASE WHEN I.ignore_dup_key = 1 THEN ' IGNORE_DUP_KEY = ON ' ELSE ' IGNORE_DUP_KEY = OFF ' END + ','  +  
    CASE WHEN ST.no_recompute = 0 THEN ' STATISTICS_NORECOMPUTE = OFF ' ELSE ' STATISTICS_NORECOMPUTE = ON ' END + ','  +  
    -- default value   
    ' DROP_EXISTING = ON '  + ','  +  
    -- default value   
    ' ONLINE = OFF '  + ','  +  
   CASE WHEN I.allow_row_locks = 1 THEN ' ALLOW_ROW_LOCKS = ON ' ELSE ' ALLOW_ROW_LOCKS = OFF ' END + ','  +  
   CASE WHEN I.allow_page_locks = 1 THEN ' ALLOW_PAGE_LOCKS = ON ' ELSE ' ALLOW_PAGE_LOCKS = OFF ' END  + ' ) ON [DB_Index_Groups]; '  [CreateIndexScript]  
   --,I.is_unique
FROM sys.indexes I    
 JOIN sys.tables T ON T.Object_id = I.Object_id     
 --JOIN sys.sysindexes SI ON I.Object_id = SI.id AND I.index_id = SI.indid    
 JOIN (SELECT * FROM (   
    SELECT IC2.object_id , IC2.index_id ,   
        STUFF((SELECT ' , ' + C.name + CASE WHEN MAX(CONVERT(INT,IC1.is_descending_key)) = 1 THEN ' DESC ' ELSE ' ASC ' END 
    FROM sys.index_columns IC1   
    JOIN Sys.columns C    
       ON C.object_id = IC1.object_id    
       AND C.column_id = IC1.column_id    
       AND IC1.is_included_column = 0   
    WHERE IC1.object_id = IC2.object_id    
       AND IC1.index_id = IC2.index_id    
    GROUP BY IC1.object_id,C.name,index_id   
    ORDER BY MAX(IC1.key_ordinal)   
       FOR XML PATH('')), 1, 2, '') KeyColumns    
    FROM sys.index_columns IC2    
    GROUP BY IC2.object_id ,IC2.index_id) tmp3 )tmp4    
  ON I.object_id = tmp4.object_id AND I.Index_id = tmp4.index_id   
 JOIN sys.stats ST ON ST.object_id = I.object_id AND ST.stats_id = I.index_id    
 JOIN sys.data_spaces DS ON I.data_space_id=DS.data_space_id    
 JOIN sys.filegroups FG ON I.data_space_id=FG.data_space_id    
 LEFT JOIN (SELECT * FROM (    
    SELECT IC2.object_id , IC2.index_id ,    
        STUFF((SELECT ' , ' + C.name  
    FROM sys.index_columns IC1    
    JOIN Sys.columns C     
       ON C.object_id = IC1.object_id     
       AND C.column_id = IC1.column_id     
       AND IC1.is_included_column = 1    
    WHERE IC1.object_id = IC2.object_id     
       AND IC1.index_id = IC2.index_id     
    GROUP BY IC1.object_id,C.name,index_id    
       FOR XML PATH('')), 1, 2, '') IncludedColumns     
   FROM sys.index_columns IC2     
   GROUP BY IC2.object_id ,IC2.index_id) tmp1    
   WHERE IncludedColumns IS NOT NULL ) tmp2     
ON tmp2.object_id = I.object_id AND tmp2.index_id = I.index_id    
WHERE   T.object_id NOT IN (
        SELECT  major_id
        FROM    sys.extended_properties (NOLOCK)
        WHERE   name = N'microsoft_database_tools_support' )
        AND I.type = 2 AND T.object_id NOT IN (SELECT object_id  FROM NoClustTable)
        AND i.data_space_id =1
        AND T.name='BusinessEntityAddress'

 

 

 

CREATE UNIQUE NONCLUSTERED INDEX [AK_BusinessEntityAddress_rowguid] ON Person.[BusinessEntityAddress] (  rowguid ASC  )   WITH (  PAD_INDEX = OFF ,FILLFACTOR = 100  ,SORT_IN_TEMPDB = OFF , IGNORE_DUP_KEY = OFF , STATISTICS_NORECOMPUTE = OFF , DROP_EXISTING = ON , ONLINE = OFF , ALLOW_ROW_LOCKS = ON , ALLOW_PAGE_LOCKS = ON  ) ON [DB_Index_Groups]; 
CREATE NONCLUSTERED INDEX [IX_BusinessEntityAddress_AddressID] ON Person.[BusinessEntityAddress] (  AddressID ASC  )   WITH (  PAD_INDEX = OFF ,FILLFACTOR = 100  ,SORT_IN_TEMPDB = OFF , IGNORE_DUP_KEY = OFF , STATISTICS_NORECOMPUTE = OFF , DROP_EXISTING = ON , ONLINE = OFF , ALLOW_ROW_LOCKS = ON , ALLOW_PAGE_LOCKS = ON  ) ON [DB_Index_Groups]; 
CREATE NONCLUSTERED INDEX [IX_BusinessEntityAddress_AddressTypeID] ON Person.[BusinessEntityAddress] (  AddressTypeID ASC  )   WITH (  PAD_INDEX = OFF ,FILLFACTOR = 100  ,SORT_IN_TEMPDB = OFF , IGNORE_DUP_KEY = OFF , STATISTICS_NORECOMPUTE = OFF , DROP_EXISTING = ON , ONLINE = OFF , ALLOW_ROW_LOCKS = ON , ALLOW_PAGE_LOCKS = ON  ) ON [DB_Index_Groups]; 
 

 

 

对于聚集索引表,如果拥有LOB对象,那么上述方法不完全可行,我们以Person.Person为例,这个表有LOB对象。执行上面操作后,你会发现LOB_DATA 还是位于PRIMARY文件组。

 

 

<img title="clip_image002_thumb" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="clip_image002_thumb" src="https://img2018.cnblogs.com/blog/73542/201904/73542-20190425133735485-537392689.png" width="652" height="147">

 

 

 

这个是因为通过重建索引的方式是无法移动LOB对象,如下所示,生成该表的创建脚本,你会发现TEXTIMAGE_ON位于[PRIMARY]文件组,  这种情形,只能通过重建表,导入数据的方式。

 

<img title="clip_image003_thumb" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="clip_image003_thumb" src="https://img2018.cnblogs.com/blog/73542/201904/73542-20190425133736244-512565973.png" width="757" height="376">

 

 

个人在实际操作中,总结了一下简单步骤:

 

 

1:使用Generate And Publish Scripts 生成含有LOB对象的表的脚本(具体细节有很多需要注意的地方),修改对应的文件组(例如将PRIMARY 改为[DB_Data_Groups] 或[DB_Index_Groups]

 

 

2确认表单独授权给了那些用户或角色(如果是默认角色,可以忽略),新建这些表必须考虑权限问题,否则重建这些表就会出现权限问题。

 

 

--查看某个对象被授予了那些用户或角色

SELECT o.name as ObjectName,u.name as objname,

CASE o.type

     WHEN 'AF'  THEN 'Aggregate Function(CLR)'

     WHEN 'C'   THEN 'Check Constraint'

     WHEN 'D'   THEN 'Default Constraint'

     WHEN 'F'   THEN 'Foreign Key Constraint'

     WHEN 'FN'  THEN 'Scalar Function'

     WHEN 'FS'  THEN 'Assembly (CLR) Scalar-Function'

     WHEN 'FT'  THEN 'Assembly (CLR) Table-Valued Function'

     WHEN 'IT'  THEN 'Internal Table'

     WHEN 'K'   THEN 'Primary Key or Unique Constraint'

     WHEN 'U'   THEN 'User Table'

     WHEN 'P'   THEN 'Stored Procedure'

     WHEN 'PC'  THEN 'Assembly (CLR) Stored Procedure'

     WHEN 'S'   THEN 'System Table'

     WHEN 'SN'  THEN 'Synonym'

     WHEN 'V'   THEN 'View'

     WHEN 'X'   THEN 'Extended Store Procedure'

     WHEN 'TF'  THEN 'Table Function'

     ELSE 'OTHER'

END AS TYPE,

CASE WHEN  p.ACTION = 26  AND p.PROTECTTYPE = 205 THEN '' ELSE '' END AS 'REFERENCES',

CASE WHEN  p.ACTION = 193 AND p.PROTECTTYPE = 205 THEN '' ELSE '' END AS 'SELECT',

CASE WHEN  p.ACTION = 195 AND p.PROTECTTYPE = 205 THEN '' ELSE '' END AS 'DELETE',

CASE WHEN  p.ACTION = 196 AND p.PROTECTTYPE = 205 THEN '' ELSE '' END AS 'INSERT',

CASE WHEN  p.ACTION = 197 AND p.PROTECTTYPE = 205 THEN '' ELSE '' END AS 'UPDATE',

CASE WHEN  p.ACTION = 224 AND p.PROTECTTYPE = 205 THEN '' ELSE '' END AS 'EXECUTE',

CASE p.PROTECTTYPE

    WHEN 204 THEN 'GRANT_W_GRANT '

    WHEN 205 THEN 'GRANT'

    WHEN 206 THEN 'DENY'

    ELSE 'OTHER'

END AS PROTECTTYPE

FROM sysprotects p

 INNER JOIN sysobjects o on p.id = o.id

 INNER JOIN sysusers u on p.uid = u.uid

WHERE o.name='Person.Person'  --具体表名

 

 

3:我们需要将原表重命名(包括约束, 如下所示,使用下面脚本生成对应的脚本,执行后修改表名或约束名称),然后执行步骤1里面生成的脚本,创建新表()

 

 

--USE [AdventureWorks2014]
--GO
 
DECLARE @ori_tab_name NVARCHAR(64);
DECLARE @old_tab_name NVARCHAR(64);
 
SET @ori_tab_name = 'Person.Person';
SET @old_tab_name = RTRIM(@ori_tab_name) + '_old';
 
 
 
 
 
SELECT  'sp_rename ''' + SCHEMA_NAME(schema_id) +'.' + name + ''' ,'''
        + name + '_old'';' + CHAR(10) + 'GO'
FROM    sys.key_constraints
WHERE   parent_object_id = OBJECT_ID(@ori_tab_name);
 
 
SELECT  'sp_rename ''' + SCHEMA_NAME(schema_id) +'.' + name + ''', ''' 
        + name + '_old'';' + CHAR(10) + 'GO'
FROM    sys.default_constraints
WHERE   parent_object_id = OBJECT_ID(@ori_tab_name);
 
 
SELECT 'sp_rename ''' +  SCHEMA_NAME(schema_id) +'.' + name + ''', ''' 
        + name + '_old'';' + CHAR(10) + 'GO'
FROM sys.foreign_keys
WHERE parent_object_id =OBJECT_ID(@ori_tab_name);
 
 
SELECT 'sp_rename '''  + SCHEMA_NAME(schema_id) + '.' + name +''', ''' 
        + name + '_old'';'  + CHAR(10) + 'GO'
FROM sys.check_constraints
WHERE parent_object_id =OBJECT_ID(@ori_tab_name);
 
 
SELECT  'sp_rename ''' +SCHEMA_NAME(schema_id) + '.' +  name + ''', ''' + name + '_old'';'
FROM    sys.tables
WHERE   object_id = OBJECT_ID(@ori_tab_name);

 

 

 

注意:有外键约束指向这个表的,还需要额外调整。

 

 

 

INSERT  INTO Person.Person
        ( BusinessEntityID ,
          PersonType ,
          NameStyle ,
          Title ,
          FirstName ,
          MiddleName ,
          LastName ,
          Suffix ,
          EmailPromotion ,
          AdditionalContactInfo ,
          Demographics ,
          rowguid ,
          ModifiedDate
        )
        SELECT  BusinessEntityID ,
                PersonType ,
                NameStyle ,
                Title ,
                FirstName ,
                MiddleName ,
                LastName ,
                Suffix ,
                EmailPromotion ,
                AdditionalContactInfo ,
                Demographics ,
                rowguid ,
                ModifiedDate
        FROM    Person.Person_old;
 
sp_spaceused 'Person.Person';
GO
sp_spaceused 'Person.Person_old';
GO
 
TRUNCATE TABLE Person.Person_old;
GO
DROP TABLE Person.Person_old;
GO

 

 

如果遇到有自增字段,还必须考虑插入自增字段相关值。 总的来说,这样转移文件组是一个相当麻烦的事情。对于堆表,这里也没啥好介绍的。无法是细节问题需要注意。转移文件组倒没有什么技术含量。还有一个因素就是这样的操作会影响业务。

 

 

 

SET IDENTITY_INSERT [dbo].[YourTableName] ON;

GO

INSERT INTO [dbo].[YourTableName](............)

SELECT .............

GO

SET IDENTITY_INSERT [dbo].[YourTableName] OFF;

GO

 

 

 

 

 

分区表   

 

对于分区表而言,也可以通过重新创建聚集索引和非聚集索引来移动数据。另外,就是重新创建该表,转移数据。这个大同小异。当然,分区表还有切换分区(Switch Partition)这个机制来转移数据。

 

 

 

 

删除文件和文件组

 

删除文件前,必须确保文件为空,否则就会报The file 'xxxx' cannot be removed because it is not empty.",所以,首先必须转移文件当中的对象,然后收缩文件,最后删除文件。

 

DBCC SHRINKFILE (FileName, EMPTYFILE);

 

USE [AdventureWorks2014]

GO

ALTER DATABASE [AdventureWorks2014]  REMOVE FILE [AdventureWorks2014_DATA_01]

GO

 

 

正常情况下,删除文件组对应的文件后,就可以删除文件组。

 

ALTER DATABASE [AdventureWorks2014] REMOVE FILEGROUP [DB_Data_Groups]

GO

 

但是如果存在分区表,你可能无法删除文件组。因为还有几个东西依赖文件组,一是分区方案,二是使用该分区方案的分区表。

     

所以要删除分区方案才能删除文件组。但要删除分区方案之前要先更改依赖它的分区表,使其不依赖它。 这个主要是更改分区表的分区列,使其不使用分区方案,如果实在不会更改,在表里数据已经备份的前提下,可以直接删除表来解决。  然后再删除分区表方案,最后就可以直接删除文件组了。

 

 

1、修改分区表,使其不依赖分区方案。

 

2、删除分区方案(依赖要删除的文件组)。

 

DROP PARTITION SCHEME [PART_FUNC_SCHEME_NAME]

 

3、直接删除文件组。

 

ALTER DATABASE [DataBaseName] REMOVE FILEGROUP [xxxx]

 

 

参考资料:

 

https://www.sqlskills.com/blogs/paul/files-and-filegroups-survey-results/

https://docs.microsoft.com/zh-cn/sql/relational-databases/databases/database-files-and-filegroups?view=sql-server-2017

http://www.cnblogs.com/CareySon/archive/2011/12/26/2301597.html