如何在MS SQL Server中将表移动到另一个文件组?

71
我有一个带有两个庞大表的SQL Server 2008 EntOLTP数据库。我该如何在不中断服务的情况下将这些表移动到另一个filegroup?目前,每秒钟在这些表中插入100-130条记录并更新30-50条记录。每个表约有1亿条记录和六个field(包括一个geography字段)。
我正在Google中寻找解决方案,但所有的解决方案都包含:
 

创建第二个表,从第一个表中插入行,删除第一个表等等

我是否可以使用分区函数来解决这个问题?
10个回答

92

如果您只想将表移动到新的文件组中,您需要在要移动到的新文件组上重新创建表的聚集索引(毕竟:聚集索引就是表数据)。

您可以使用以下方法执行此操作:

CREATE CLUSTERED INDEX CIX_YourTable
   ON dbo.YourTable(YourClusteringKeyFields)
   WITH DROP_EXISTING
   ON [filegroup_name]

或者如果您的聚集索引是唯一的:

CREATE UNIQUE CLUSTERED INDEX CIX_YourTable
   ON dbo.YourTable(YourClusteringKeyFields)
   WITH DROP_EXISTING
   ON [filegroup_name]

这将创建一个新的聚集索引并删除现有的聚集索引,它会在你指定的文件组中创建新的聚集索引 - et voila,你的表数据已经移动到了新的文件组。

有关 CREATE INDEX 的所有可用选项的详细信息,请参见MSDN文档

当然,这还没有涉及到分区,但这是另一篇完全不同的故事...


10
天啊,我浪费了比我愿意承认的更多时间来弄清楚我创建的表为什么总是跑到FILEGROUP1...原来主键在FG1上的 create table(... constraint(...) on FG1 ) ON FG2 - FG1 优先级更高。感谢您的帮助。 - WernerCD
3
并非所有表都是聚集索引。我从Google搜索而来,正在尝试寻找有关堆的同样信息。 - Paul
2
对于那些谈论堆的人 - 你可以创建一个聚集索引来强制移动,然后再删除聚集索引?应该能够实现相同的结果。 - Scott Ivey
1
它也不会移动表上的索引。 - Ian Boyd
2
@WernerCD "天啊,这太有趣了" 这让我笑了。谢谢,我需要这个。 :-) - AndrewJacksonZA
显示剩余4条评论

33

要回答这个问题,首先我们必须了解:

  • 如果一个表没有索引,它的数据称为“堆”。
  • 如果一个表有一个聚集索引,那么该索引实际上就是你的表数据。因此,如果你移动聚集索引,你也会移动你的数据。

第一步是了解更多关于我们想要移动的表的信息。我们通过执行以下T-SQL语句来完成:

sp_help N'<<your table name>>'

输出结果将会展示名为“Data_located_on_filegroup”的列。这是一个方便的方式来知道你的表数据所在的文件组。但更重要的是输出结果将会显示有关该表索引的信息。(如果你只想查看有关表索引的信息,请运行sp_helpindex N'<<your table name>>')你的表可能有1)没有索引(因此它是堆),2)单个索引,或3)多个索引。如果索引描述以“聚集,唯一,…”开头,则是你想要移动的索引。如果该索引也是主键,那没关系,你仍然可以移动它。

要移动索引,请注意上述帮助查询结果中显示的index_name和index_keys,并将它们填写到以下查询的<<blanks>>中:

CREATE UNIQUE CLUSTERED INDEX [<<name of clustered index>>]
ON [<<table name>>]([<<column name the index is on - from index_keys above>>])
WITH (DROP_EXISTING = ON, ONLINE = ON)
ON <<name of file group you want to move the index to>>

上述的 DROP EXISTING, ONLINE 选项很重要。 DROP EXISTING 确保索引不会重复,而 ONLINE 则在移动表时保持表在线(现仅适用于企业版)。

如果您要移动的索引不是聚集索引,则将上面的 UNIQUE CLUSTERED 替换为 NONCLUSTERED

要移动堆表,请向其添加聚集索引,然后运行上述语句将其移动到其他文件组,然后删除索引。

现在,返回并在您的表上运行 sp_help,检查结果以查看您的表和索引数据现在所在的位置。

如果您的表有多个索引,则在运行上述语句将聚集索引移动后,sp_helpindex 将显示您的聚集索引位于新文件组中,但任何其余索引仍将位于原始文件组中。 表将继续正常运行,但您应该有一个充分的理由希望索引位于不同的文件组中。 如果您希望表及其所有索引位于同一个文件组中,请重复上述说明以移动每个索引,根据您正在移动的索引类型,必要时使用 CREATE [NONCLUSTERED, or other] ... DROP EXISTING... 进行替换。


2
在这里有关于移动非聚集数据的好信息。 - Rots
2
最新版本的语法是 WITH (DROP_EXISTING = ON, ONLINE = ON),否则会出现“无效使用选项ONLINE”的错误。 - Alex from Jitbit

8

分区是一种解决方案,但您可以使用以下方法将聚集索引“移动”到新文件组而不会中断服务(受某些条件的限制,请参见下面的链接):

CREATE CLUSTERED /*oops*/ INDEX ... WITH (DROP_EXISTING = ON, ONLINE = ON, ...) ON newfilegroup

聚集索引就是数据本身,这就像移动文件组一样。

请参见创建索引

这取决于你的主键是否为聚集索引,这会改变我们的操作方式。


你需要指定 CREATE *CLUSTERED* INDEX..... 才能实际移动数据,对吗? :-) - marc_s

3
请注意,重新创建聚集索引只会移动“原始”列,如int、bit、datetime等。
要移动varchar(max)、varbinary和其他“blob”列,您需要重新创建表。值得庆幸的是,在SSMS中有一种半自动化的方法可以实现这一点——通过在表“设计”窗口中更改“文本文件组”,然后保存更改。

(2021年的快速更新): 或者,您可以创建一个临时的“分区”规则(分区规则是决定数据存储在哪个文件组中的函数),将其指向表中所有值的新文件组。应用此分区方案实际上将移动数据。

如果需要更多详细信息,请参阅我在此处的博客:https://www.jitbit.com/alexblog/153-moving-sql-table-textimage-to-a-new-filegroup/

2
这段来自SQL Server Books Online的摘录已经说明了一切:"由于聚集索引的叶级别和数据页根据定义是相同的,因此创建聚集索引并使用ON partition_scheme_name或ON filegroup_name子句有效地将表从创建它的文件组移动到新的分区方案或文件组。"(来源-http://msdn.microsoft.com/en-us/library/ms188783.aspx)来自(http://www.mssqltips.com/sqlservertip/2442/move-data-between-sql-server-database-filegroups/
如其他朋友所说,如marc_s所接受的答案,以下是屏幕截图,为您提供使用SSMS GUI的另一种方法。
请注意,在存储选项卡中的索引属性中,您可以轻松地将其移动到另一个文件组。 enter image description here

2

在SSMS中,展开Tables,展开您想要移动的表,展开Indexes,在聚簇索引上右键单击,单击"Script Index as" -> "Drop and Create to"

这将打开一个查询窗口,其中包含删除聚簇索引并创建与原始索引相同规格的新索引的脚本。

在查询窗口中,在"ALTER TABLE <> ADD CONSTRAINT"语句处,在语句末尾的"ON"关键字后更改文件组的名称,例如,如果表位于PRIMARY文件组上,您想要将其移到名为"SECONDARY"的文件组上,则将"ON [PRIMARY]"更改为"ON [SECONDARY]"
如果您希望在操作期间保持表在线,请还将"ONLINE = OFF"更改为"ONLINE = ON"

执行该脚本,它将删除原始索引并在指定的文件组中创建一个新索引。


1

如何将表移动到另一个文件组?

注意:将表移动到另一个文件组仅适用于企业版。

步骤 1:

检查表所在的文件组:

-- Query to check the tables and their current filegroup:

SELECT    tbl.name AS [Table Name], 
          CASE WHEN dsidx.type='FG' THEN dsidx.name ELSE '(Partitioned)' END AS [File Group] 
FROM      sys.tables AS tbl 
JOIN      sys.indexes AS idx 
ON        idx.object_id = tbl.object_id 
AND       idx.index_id <= 1 
LEFT JOIN sys.data_spaces AS dsidx 
ON        dsidx.data_space_id = idx.data_space_id 
ORDER BY  [File Group], [Table Name] 

步骤2:

将现有表格移动到新的文件组

如果您想要将表格移动到的文件组不存在,请先创建辅助文件组,然后再移动表格。

将表格移动到不同的文件组涉及将表格的聚集索引移动到新的文件组。聚集索引的叶级别实际上包含了表格数据。因此,可以使用DROP_EXISTING子句在单个语句中移动聚集索引,如下所示:

CREATE UNIQUE CLUSTERED INDEX [Index_Name] ON [SchemaName].[TableName]
(
    [ClusteredIndexKeyFields]
)WITH (DROP_EXISTING = ON, ONLINE = ON) ON [FilegroupName]
GO

步骤3:

将剩余的非聚集索引移动到辅助文件组

您需要使用以下语法手动移动非聚集索引:

--1st check the index information using the following sp
sp_helpindex [YourTableName]


--Now by using the following query you can move the remaining indexes to secondary filegroup
CREATE NONCLUSTERED INDEX [Index_Name] ON [SchemaName].[TableName]
(
    [IndexKeyFields]
)WITH (DROP_EXISTING = ON, ONLINE = ON) ON [FilegroupName]
GO

将堆移动到另一个文件组:

据我所知,将堆移动到另一个文件组的唯一方法是在新文件组上暂时添加聚集索引,然后再删除它(如果需要)。


将表移动到另一个文件组的参考:https://msdn.microsoft.com/zh-cn/library/ms175905.aspx - user7488971
http://www.sqlpassion.at/archive/2016/09/26/how-to-move-data-between-file-groups-in-sql-server/ - user7488971

0

我曾经遇到过同样的问题,这是我想出来的脚本(已测试,效果与您预期的一样):

DECLARE @Target_Filegroup sysname = N'XXX';
-----------------------------------------------------------------------------------------
;WITH [IX] AS(
SELECT
[Schema]                 = SCHEMA_NAME(so.[schema_id]) COLLATE DATABASE_DEFAULT,
[Object_Name]            = so.[name] COLLATE DATABASE_DEFAULT,
[Object_Type]            = so.[type],
[Is_Published]           = so.[is_published],
[Is_Schema_Published]    = so.[is_schema_published],
[IX_Name]                = ix.[name] COLLATE DATABASE_DEFAULT,
[IX_Type]                = ix.[type],
[IX_Type_Desc]           = ix.[type_desc] COLLATE DATABASE_DEFAULT,
[Is_PK]                  = ix.[is_primary_key],
[Is_Unique]              = ix.[is_unique],
[IX_Data_Space]          = ds.[name] COLLATE DATABASE_DEFAULT,
[Is_UC]                  = ix.[is_unique_constraint],
[FF]                     = ix.[fill_factor],
[Is_Padded]              = ix.[is_padded],
[Is_Disabled]            = ix.[is_disabled],
[Is_Hypothetical]        = ix.[is_hypothetical],
[Allow_Row_Locks]        = ix.[allow_row_locks],
[Allow_Page_Locks]       = ix.[allow_page_locks],
[Has_Filter]             = ix.[has_filter],
[Filter]                 = ix.[filter_definition] COLLATE DATABASE_DEFAULT,
--[auto_created]         = ix.[auto_created],
--[optimize_seq_key]     = ix.[optimize_for_sequential_key],
[Indexed_Columns] = STUFF(( SELECT [text()] = CONCAT(', ', QUOTENAME(COL_NAME(ic.[object_id],ic.[column_id])))
                            FROM sys.index_columns ic
                            WHERE ic.[object_id] = so.[object_id]
                            AND ic.[index_id] = ix.[index_id]
                            AND ic.[is_included_column] = 0
                            ORDER BY ic.[key_ordinal]
                            FOR XML PATH('')
                            ), 1, 2, '') COLLATE DATABASE_DEFAULT,
[Indexed_Columns_Order] = STUFF(( SELECT [text()] = CONCAT(', ', QUOTENAME(COL_NAME(ic.[object_id],ic.[column_id])), CASE [is_descending_key] WHEN 1 THEN ' DESC' ELSE ' ASC' END)
                            FROM sys.index_columns ic
                            WHERE ic.[object_id] = so.[object_id]
                            AND ic.[index_id] = ix.[index_id]
                            AND ic.[is_included_column] = 0
                            ORDER BY ic.[key_ordinal]
                            FOR XML PATH('')
                            ), 1, 2, '') COLLATE DATABASE_DEFAULT,
[Included_Columns] = STUFF(( SELECT [text()] = CONCAT(', ', QUOTENAME(COL_NAME(ic.[object_id],ic.[column_id])))
                            FROM sys.index_columns ic
                            WHERE ic.[object_id] = so.[object_id]
                            AND ic.[index_id] = ix.[index_id]
                            AND ic.[is_included_column] = 1
                            ORDER BY ic.[key_ordinal]
                            FOR XML PATH('')
                            ), 1, 2, '') COLLATE DATABASE_DEFAULT
FROM sys.objects so
LEFT JOIN sys.indexes ix ON so.[object_id] = ix.[object_id]
LEFT JOIN sys.data_spaces ds ON ix.[data_space_id] = ds.[data_space_id]
WHERE so.[type] IN ('U', 'V')
AND so.[is_ms_shipped] = 0
AND ix.[type] IS NOT NULL --| so we get heaps, and indexed views 
)
SELECT
[Schema], [Object_Name], [Object_Type],
--[Is_Published], [Is_Schema_Published],
[IX_Name],
[IX_Data_Space],
[IX_Move_SQL] = CASE WHEN [IX_Data_Space] <> @Target_Filegroup AND [IX_Type] IN (1,2) THEN CONCAT(
                                                'CREATE ', CASE [Is_Unique] WHEN 1 THEN 'UNIQUE ' END, [IX_Type_Desc], ' INDEX ', QUOTENAME([IX_Name]),
                                                ' ON ', QUOTENAME([Schema]), '.', QUOTENAME([Object_Name]), ' (', [Indexed_Columns_Order], ')',
                                                CASE WHEN [Included_Columns] IS NOT NULL THEN CONCAT(' INCLUDE (', [Included_Columns], ')') END,
                                                CASE WHEN [Has_Filter] = 1 THEN CONCAT(' WHERE ', [Filter]) END,
                                                ' WITH (PAD_INDEX=', CASE [Is_Padded] WHEN 1 THEN 'ON' ELSE 'OFF' END,
                                                ', FILLFACTOR=', CASE WHEN [FF] = 0 THEN '100' ELSE CAST([FF] as varchar(3)) COLLATE DATABASE_DEFAULT END,
                                                ', ALLOW_ROW_LOCKS=', CASE [Allow_Row_Locks] WHEN 1 THEN 'ON' ELSE 'OFF' END,
                                                ', ALLOW_PAGE_LOCKS=', CASE [Allow_Page_Locks] WHEN 1 THEN 'ON' ELSE 'OFF' END,
                                                ', DROP_EXISTING=ON ',')',
                                                ' ON ', QUOTENAME(@Target_Filegroup), ';')

                                                 END COLLATE DATABASE_DEFAULT
FROM [IX]
ORDER BY [Object_Type] ASC, [Schema] ASC , [Object_Name] ASC;

0

我认为这些步骤非常简单明了,可以通过管理工具将任何表移动到不同的文件组中:

  • 通过更改每个索引的FileGroup属性,将所有非聚集索引移动到新的文件组中

  • 像上一步骤一样,将您的聚集索引更改为非聚集,并更改其文件组

  • 通过此命令(或通过IDE)在“新文件组”中添加一个新的临时聚集索引:

    CREATE CLUSTERED INDEX [PK_temp]
    ON YOURTABLE([Id])
      ON NEWFILEGROUP
    

    (以上命令会导致将所有数据移动到新的文件组中)

  • 删除上述临时PK(当它完美地完成其工作时!)

  • 再次通过IDE将主要聚集索引更改为聚集索引

以上步骤的好处是不需要删除现有的外键关系。此外,使用IDE可以防止在错误条件下丢失数据。
注意:确保未为您的文件组启用磁盘配额或正确设置它。否则,您将收到“文件组已满”的异常!

0
CREATE CLUSTERED INDEX IXC_Products_Product_id
ON dbo.Products(Product_id)
WITH (DROP_EXISTING = ON) ON MyNewFileGroup

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接