Executive Summary:
The script included with this article lets database administrators move tables and indexed views between filegroups in SQL Server 2000 and SQL Server 2005 more efficiently than moving tables and indexed views manually.
|
Filegroups are useful tools for controlling a database’s
location and size because they let DBAs
easily move files between different disks or network
computers. But using filegroups can present problems
when you need to move tables because if you define
multiple filegroups for a single database, then some tables
can reside in one filegroup while other tables might be
stored in a different filegroup. If you then need to move
data files, the fact that the data is spread out among filegroups
means you’ll likely have to move tables from one
filegroup to another. Here’s a sample scenario: A database
called [MyDB] has two filegroups, which the sample code
in Listing 1 shows. The first filegroup, called [PRIMARY],
contains the data file C:\MyDB_Primary.mdf; the second
filegroup, [MyFileGroup], has the data file D:\MyDB_
MyFilegroup.ndf. If I want to move all the table data to
drive D while maintaining 100 percent uptime (in which
case detaching databases or taking files offline is not an
acceptable option), then I must move tables from one
filegroup to the other. The need to move tables arises
in other situations, such as in filegroup consolidation
or before deleting database files. To make moving tables
between filegroups easier and more efficient, I’ve written
a SQL script that moves one table between
filegroups. The script doesn’t assume any prior
knowledge about the table and can be used to
move tables that have a clustered index and also
to move heaps. In addition, the script lets users
specify whether a table’s nonclustered indexes and unique
constraints should be moved as well.
My script is useful for moving tables or indexed views
for SQL Server 2005 and SQL Server 2000. If a table
you want to move has full-text indexes, the script won’t
register those indexes with the full text catalog, nor will
the script move user-defined statistics. Note that although
I composed the script to move single tables, you can easily
modify it to move a collection of tables and indexed views.
Before I explain how my script works, let’s take a look at
the basics of tables and moving them between filegroups.
(The description following applies as well to indexed
views because indexes on views have the same underlying
architecture that table indexes do.)
Understanding Table Types
A table is stored on the filegroup that holds its data pages.
In order to move a table between filegroups and keep the
table schema intact, it’s important to distinguish between
two types of tables. The first table type refers to tables
that have a clustered index. When a table has a clustered
index, the data pages are the leaf pages of the clustered
index B-tree. To move such tables, you must recreate the
clustered index on the target filegroup and also move the
nonclustered indexes and constraints. The second table
type refers to heaps. With tables that don’t have a clustered
index, you can move the data pages by dropping the
table, then recreating it on the desired file group. A better
solution that omits the need to drop the table creates a
clustered index on the target filegroup, then drops the
clustered index later.
Although it might seem from the foregoing explanation
that tables can be moved easily, keep in mind that
changing clustered indexes can negatively affect a server’s
performance and thus the availability of data, especially
in large tables. When it comes to moving tables, the devil
truly is in the details.
Existing Table-Moving Techniques
Assume that the database [MyDB] contains the table
[dbo].[MyTable1]. This table has a primary key that is
also a clustered index, a nonclustered index, and a unique
constraint (the Data Definition Language—DDL—for
the table is provided in the sample code in Listing 2). The
indexes and constraints for this table all reside on the
[PRIMARY] filegroup—we can validate this by running
the SQL code in Listing 3 against the [MyDB] database.
This SQL statement returns the filegroup information
for all indexes and constraints that belong to the table
[dbo].[MyTable1].
One technique for moving a table to a different filegroup
is to use Enterprise Manager in SQL Server 2005 or SQL
Server 2000. In Enterprise Manager you can right-click the
table, select Design Table, click the Properties icon, then
select the desired filegroup from the Table Filegroup dropdown
menu, as Figure 1, page 38, shows. To move the table’s
indexes and constraints, you can go to the Indexes/Keys tab
on the Properties page, as Figure 2, page 38, shows, change
the filegroup for each object in the Index Filegroup dropdown
menu, click Close, then click Yes when prompted to
apply the changes. Unfortunately, the ability to move a table
to a different filegroup from the UI is no longer available in
SQL Server Management Studio.
This technique’s main benefit is that it’s straightforward.
However, a quick look under the hood reveals that it
isn’t particularly efficient: Moving several tables requires a
lot of tedious and manual work. Moreover, if you were to
run SQL Profiler during the table move, you’d see that the
GUI creates a new table called [dbo].[Tmp_utbMyTable1],
moves the data from [dbo].[utbMyTable1] to the new
table, then makes the new table assume the identity of the
original one. These actions are inefficient because the table
data is duplicated and stored twice in the database (once
in [dbo].[utbMyTable1] and once in [dbo].[Tmp_utbMy-
Table1]), which is extremely wasteful, especially in the
case of large tables. Another problem with this method
is that if the table [dbo].[utbMyTable1] has user-defined
statistics, those statistics are dropped without warning and
are not recreated. These actions are embedded in Enterprise
Manager and in the SQL Distributed Management
Objects (DMO)—both for SQL Server 2000 and the SQL
Server 2005–compatible DMO—and therefore can’t be controlled by the user.
Another simple and
popular way to move tables
with a clustered index is to
recreate the clustered index
using the CREATE INDEX
command together with
the DROP_EXISTING
clause, as the sample code
in Listing 4, page 38, illustrates.
This method is quick
and easy; however, it results
in redundant and wasteful
operations. The SQL statement
in Listing 4 moves
only the clustered index
and therefore has to be followed by similar statements
for all nonclustered indexes. The recreation of the clustered
index forces a rebuild of all nonclustered indexes
and B-trees because the leaf pages of the nonclustered
indexes must be redirected to point to the leaf pages of
the newly reconstructed clustered index. In other words,
using this technique rebuilds the nonclustered indexes
twice—once when the clustered index is moved and again
when the nonclustered indexes are recreated on the target
filegroup.
A More Efficient Solution
As I’ve mentioned, the script I’ve written can move any
type of table, including heaps. Here’s how it operates. The
script accepts five user-assigned parameters: The variables
@SourceFileGroupID and @TargetFileGroupID are set
by the user and hold the original filegroup ID for the table
to be moved, as well as the ID of the target filegroup. You
can obtain the IDs of the database file groups by running
EXEC sp_helpfile or by querying the system object [sysfiles].
Before running the script, you should also specify the
name of the table to move as the variable @TableToMove.
Moreover, you can indicate whether the primary key and
constraints should also be moved by setting the value of
the bit variable @MovePKAndAllUniqueConstraints to
1. In the same way, the bit
variable @MoveAllNon-
ClusteredIndexes indicates
whether to move the nonclustered
indexes.
In order to move the
table, the script performs the
following steps and actions.
First, the script determines
whether the source and target filegroups and the requested table indeed exist, and
aborts if they do not. Then, the script checks whether the
requested table has a clustered index or is a heap. If the
table is a heap, the script determines whether the table
contains a unique column (i.e., an identity column or a
column with a primary key or unique constraint). If a
unique column indeed exists, the script creates a nonunique
clustered index on that column on the appropriate
target filegroup.
Applying the clustered
index on a
unique column is
useful because the
column data has
high cardinality
even if duplicate
data entries exist
(e.g., as in the
case of manually
populating an
identity column
with SET IDENTITY
INSERT),
which guarantees
fast and efficient
creation of the
clustered index. In
the event that no
unique columns
are available, the
script adds a new
identity column and builds a clustered index on it. All
items that are added to the table as part of the move
process are later dropped to ensure that the table schema
remains unchanged.
In a case where the requested table has a unique clustered
index, the script will first drop all foreign keys that
reference the unique index. (If the clustered index is not unique, then there’s no need to drop foreign keys.) Next,
the clustered index is dropped. If you instruct the script to
move the nonclustered indexes and the table constraints,
they will be dropped to avoid the duplicate index rebuild
I described previously. Finally, the script creates the
clustered index on the target filegroup and also recreates
all the indexes, constraints, and keys that were dropped
earlier while maintaining all properties and attributes for
all objects (such as PADINDEX and FILLFACTOR).
Moving IMAGE and TEXT
Columns to Other Filegroups in
SQL Server 2000
In SQL Server 2000 we have the option to explicitly specify
the filegroup for columns that use long data types (i.e.,
IMAGE, TEXT, NTEXT) by using the TEXTIMAGE_
ON clause. For example, we can change the table creation
statement in Listing 1 to write:
CREATE TABLE [dbo].[utbMyTable1]
([Col1] INT IDENTITY(1, 1),
[Col2] NVARCHAR(10),
[Col3] BIGINT,
[Col4] IMAGE)
ON [PRIMARY]
TEXTIMAGE_ON [MyFilegroup]
in which case the IMAGE data stored in column Col4
would reside on the filegroup [MyFilegroup]. If a table
has more than one column that utilizes a large data type,
then the data for all such large columns will be stored
on the filegroup specified in the TEXTIMAGE_ON
section. In SQL Server 2005 the TEXTIMAGE_ON
clause is no longer available—the data contained in
IMAGE, XML, TEXT, NTEXT, VARBINARY(MAX),
VARCHAR(MAX), and NVARCHAR(MAX) columns
always resides on the same filegroup as the table’s data
pages. It’s important to note that my script doesn’t move
data for the column types specified above because the
only way to move these data entries is by dropping and
recreating the entire table.
Limitations and Possibilities
My script doesn’t handle the case in which two objects in
the database have the same name and belong to different
schemas. On top, the script handles one table or indexed
view at a time. I encourage interested readers to tune the
script to move multiple objects and handle richer scenarios
to fit their database environments.
End of Article