THE SQL Server Blog Spot on the Web

Welcome to SQLblog.com - The SQL Server blog spot on the web Sign in | Join | Help
in Search

Kalen Delaney

Geek City: Where are LOBs stored?

When researching a question from one of the students in my class last week, I was reading the documentation for CREATE TABLE about storing LOB columns at http://msdn.microsoft.com/en-us/library/ms174979.aspx. For this discussion LOB columns includes text, image, ntext, xml and the MAX columns when they are over 8000 bytes and stored outside the regular data row. I knew that SQL Server gives us the capability of storing LOB columns in a separate filegroup with the TEXTIMAGE_ON clause, but I was surprised at one thing the docs mentioned.

The documentation says:

TEXTIMAGE_ON { filegroup | "default" }

Are keywords that indicate that the text, ntext, image, xml, varchar(max), nvarchar(max), varbinary(max), and CLR user-defined type columns are stored on the specified filegroup.

TEXTIMAGE_ON is not allowed if there are no large value columns in the table. TEXTIMAGE_ON cannot be specified if <partition_scheme> is specified. If "default" is specified, or if TEXTIMAGE_ON is not specified at all, the large value columns are stored in the default filegroup. The storage of any large value column data specified in CREATE TABLE cannot be subsequently altered.

The way I read this highlighted sentence is that if you don’t put the LOB data on a particular filegroup, it will always go on the default filegroup. The default filegroup has a specific meaning for SQL Server. It is the filegroup where all objects are placed if you don’t use the ON clause to put them on a specific filegroup. By default, the default filegroup is the PRIMARY filegroup, but with the ALTER DATABASE / MODIFY FILEGROUP command, you can change the default filegroup.

I did not think that what this sentence was saying was true, so I decided to test it.

I first created a new database with three filegroups. The PRIMARY filegroup is the default filegroup, because I am not changing the default to be anything else. The two additional filegroups are FG1 and FG2.

USE master;

GO

IF EXISTS (SELECT * FROM sys.databases WHERE name = 'TestFGs')

DROP DATABASE TestFGs;

GO

CREATE DATABASE TestFGs

ON PRIMARY

(NAME = Data ,

FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TestFGPrimary.mdf'

, SIZE = 10 MB

, MAXSIZE = 1 GB

, FILEGROWTH = 25%),

FILEGROUP FG1

(NAME = FG1_data ,

FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\FG1_data.mdf'

, SIZE = 10 MB

, MAXSIZE = 1 GB

, FILEGROWTH = 25%),

FILEGROUP FG2

(NAME = FG2_data ,

FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\FG2_data.mdf'

, SIZE = 10 MB

, MAXSIZE = 1 GB

, FILEGROWTH = 25%)

LOG ON

(NAME = TestFG_log,

FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TestFG_log.ldf'

, SIZE = 20 MB

, MAXSIZE = 2 GB

, FILEGROWTH = 25%);

GO

I then use the new database, and create three tables, each with the same column definitions. One of the column is a TEXT column which will be stored separately from the regular data rows unless I specify otherwise.

The first table does not specify any filegroups so everything should go on the default filegroup. The second table specifies a filegroup for the table (FG1) but not for the LOB data. The third table specifies a filegroup for the table (FG1) and separate filegroup for the LOB data (FG2).

To determine where the data has been stored, I examine the sys.allocation_units view. I need to join it to sys.partitions in order to get the corresponding object_id. The function object_name will give me the name of the table, and the function filegroup_name will translate the column data_space_id into a filegroup name.

USE TestFGs

GO

IF EXISTS (SELECT 1 FROM sys.tables WHERE name = 'bigrows')

DROP TABLE bigrows;

GO

CREATE TABLE bigrows

(a char(1000),

b varchar(8000),

c text );

GO

INSERT INTO bigrows

SELECT REPLICATE('a', 1000), REPLICATE('b', 1000),

REPLICATE('c', 50);

go

SELECT OBJECT_NAME(object_id) as object_name, FILEGROUP_NAME(data_space_id) as FG_name, type_desc

FROM sys.partitions p

JOIN sys.allocation_units a

on p.partition_id = a.container_id

WHERE OBJECT_NAME(object_id) = 'bigrows';

GO

IF EXISTS (SELECT 1 FROM sys.tables WHERE name = 'bigrows2')

DROP TABLE bigrows2;

GO

CREATE TABLE bigrows2

(a char(1000),

b varchar(8000),

c text )

ON FG1;

GO

INSERT INTO bigrows2

SELECT REPLICATE('a', 1000), REPLICATE('b', 1000),

REPLICATE('c', 50);

go

SELECT OBJECT_NAME(object_id) as object_name, FILEGROUP_NAME(data_space_id) as FG_name, type_desc

FROM sys.partitions p

JOIN sys.allocation_units a

on p.partition_id = a.container_id

WHERE OBJECT_NAME(object_id) LIKE 'bigrows%';

GO

IF EXISTS (SELECT 1 FROM sys.tables WHERE name = 'bigrows3')

DROP TABLE bigrows3;

GO

CREATE TABLE bigrows3

(a char(1000),

b varchar(8000),

c text )

ON FG1 TEXTIMAGE_ON FG2;

GO

INSERT INTO bigrows3

SELECT REPLICATE('a', 1000), REPLICATE('b', 1000),

REPLICATE('c', 50);

GO

SELECT OBJECT_NAME(object_id) as object_name, FILEGROUP_NAME(data_space_id) as FG_name, type_desc

FROM sys.partitions p

JOIN sys.allocation_units a

on p.partition_id = a.container_id

WHERE OBJECT_NAME(object_id) like 'bigrows%';

And here are my results. The first table, bigrows1 has both its regular rows and its LOB data on the default filegroup, PRIMARY. You might also notice that an allocation unit for row-overflow data was created, even though we didn’t have any data that needed that space.

The second table, bigrows2, has all its data on FG1 even though I just specified to put the table there. The LOB data is stored with the rest of the table, if I don’t specifically place the LOB data on a different filegroup.

Only for the third table, bigrows3, is the LOB data stored separately, and only because I specifically used the TEXTIMAGE_ON clause when creating the table.

clip_image001

So now you know.

~Kalen

Published Sunday, June 26, 2011 3:27 PM by Kalen Delaney
Filed under: , ,

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

Wes Clark said:

What you have proved is the actual behavior is how I instinctually understood what the documentation was saying, but as you have pointed out, default has a specific meaning, and the documentation is wrong.  Have you filed a bug on that?

Did you try specifying "default" to see where the LOB data went then?

June 27, 2011 2:14 PM
 

Kalen Delaney said:

Hi Wes...

Are you saying you understood "default" to mean "the FG where the rest of the table was going?

I showed you the code I ran. I would guess that explicitly saying "default" would refer to the default fg, in this case PRIMARY.

You're free to take my good and modify one of the CREATE TABLE commands to specify "default" for the TEXTIMAGE_ON filegroup. Let us know what you get!

Thanks

Kalen

June 27, 2011 2:32 PM
 

Stuart Cowen said:

Great article and example code. I actually ran this step-by-step in my 180-day eval SS2K8R2 instance and realized the same results. This post is way over my current skill-level, but I was able to track with you and it seemed to make sense. I appreciate it :)

June 28, 2011 3:25 PM
 

Kalen Delaney said:

Thanks for your feedback, Stuart!  This is a great way to start getting a feel for what is waiting for you as you start getting and deeper and deeper in!

~Kalen

June 28, 2011 5:52 PM
 

Greg Linwood said:

nice article Kalen, thx. I was having a discussion about this with a customer just a few days ago so I can now refer them here for further reading :)

June 29, 2011 8:05 PM
 

Kalen Delaney said:

Hi Greg!

I'm glad it was useful.

:-)

June 29, 2011 8:15 PM
 

Something for the Weekend – SQL Server Links 01/07/11 said:

July 1, 2011 7:46 AM
 

Dale said:

I added the following code to the above:

IF EXISTS (SELECT 1 FROM sys.tables WHERE name = 'bigrows4')

DROP TABLE bigrows4;

GO

CREATE TABLE bigrows4

(a char(1000),

b varchar(8000),

c text )

ON FG1 TEXTIMAGE_ON 'default';

GO

INSERT INTO bigrows4

SELECT REPLICATE('a', 1000), REPLICATE('b', 1000),

REPLICATE('c', 50);

GO

SELECT OBJECT_NAME(object_id) as object_name, FILEGROUP_NAME(data_space_id) as FG_name, type_desc

FROM sys.partitions p

JOIN sys.allocation_units a

on p.partition_id = a.container_id

WHERE OBJECT_NAME(object_id) like 'bigrows%';

GO

--------------------------

The results I got back were:

object_name FG_name type_desc

bigrows PRIMARY IN_ROW_DATA

bigrows PRIMARY LOB_DATA

bigrows PRIMARY ROW_OVERFLOW_DATA

bigrows2 FG1 IN_ROW_DATA

bigrows2 FG1 LOB_DATA

bigrows2 FG1 ROW_OVERFLOW_DATA

bigrows3 FG1 IN_ROW_DATA

bigrows3 FG2 LOB_DATA

bigrows3 FG1 ROW_OVERFLOW_DATA

bigrows4 FG1 IN_ROW_DATA

bigrows4 PRIMARY LOB_DATA

bigrows4 FG1 ROW_OVERFLOW_DATA

--------------------------------

So it looks like if you specify TEXTIMAGE_ON 'default', it goes to the PRIMARY filegroup.

July 5, 2011 10:12 AM
 

Kalen Delaney said:

Thanks, Dale. In this script it went to the PRIMARY filegroup because the PRIMARY FG is the default FG, but remember that you can change which FG is default. So 'default' actually means a specific FG, whichever one was declared default, and it does not mean 'the FG where the rest of the data is'.

~Kalen

July 5, 2011 3:44 PM
 

Matic Zeljko said:

If you create table without LOB and after some time add LOB column you should drop and recreate table to be able to assigne textimage_on option.

It's more complicated if you use table in merge replication and do smoething of following:

- on creation of table you not specify textimage_on and add after some time add it to replication

- in time of making table as part of replication you not have LOB (If you don't have LOB textimage_on can't be specified (or is ignored and changed in data filegroup)) but after some time you add LOB to table through replication.

In both case you cant't change or add textimage_on option.

July 12, 2011 8:19 PM
 

Kalen Delaney said:

Hi Matic

Thanks for the additional data points!

~Kalen

July 12, 2011 9:20 PM
 

Chirag Shah said:

I had the same question when creating a table (found your blog post)

It appears to me that SQL 2000 BOL documentation is much cleaner.

from http://msdn.microsoft.com/en-us/library/aa258255(v=sql.80).aspx

If TEXTIMAGE_ON is not specified, the text, ntext, and image columns are stored in the same filegroup as the table

January 9, 2012 4:03 PM
 

Kalen Delaney said:

Hi Chirag

They had to rewrite the docs for SQL 2005, to add info about varchar(MAX), and partitions, so they seemed to have made it more complicated that it needed to be.

Thanks!

Kalen

January 9, 2012 8:06 PM

Leave a Comment

(required) 
(optional)
(required) 
Submit

This Blog

Syndication

Favorite Non-technical Sites or Blogs

Powered by Community Server (Commercial Edition), by Telligent Systems
  Privacy Statement