2009-11-09 12 views
14

Quiero crear una tabla en MS SQL Server 2005 para registrar detalles de ciertas operaciones del sistema. Como se puede ver en el diseño de la tabla a continuación, cada columna, excepto Details, no admite nulos.¿Debo usar una columna varchar (max) en línea o almacenarla en una tabla separada?

CREATE TABLE [Log] 
(
[LogID] [int] IDENTITY(1,1) NOT NULL, 
[ActionID] [int] NOT NULL, 
[SystemID] [int] NOT NULL, 
[UserID] [int] NOT NULL, 
[LoggedOn] [datetime] NOT NULL, 
[Details] [varchar](max) NULL 
) 

Porque la columna Details no siempre tendrá datos en ella. ¿Es más eficiente almacenar esta columna en una tabla separada y proporcionarle un enlace?

CREATE TABLE [Log] 
(
[LogID] [int] IDENTITY(1,1) NOT NULL, 
[ActionID] [int] NOT NULL, 
[SystemID] [int] NOT NULL, 
[UserID] [int] NOT NULL, 
[LoggedOn] [datetime] NOT NULL, 
[DetailID] [int] NULL 
)  

CREATE TABLE [Detail] 
(
[DetailID] [int] IDENTITY(1,1) NOT NULL, 
[Details] [varchar](max) NOT NULL 
) 

Para un tipo de datos más pequeño Realmente no lo consideraría, pero para un varchar(max) qué hacer esto ayudará a mantener el tamaño de la tabla más pequeña? ¿O simplemente estoy tratando de sacar la base de datos de manera inteligente y no lograr nada?

Respuesta

25

Manténgalo en línea. Bajo las cubiertas SQL Server ya almacena las columnas MAX en una 'unidad de asignación' separada desde SQL 2005. Ver Table and Index Organization. Esto en efecto es exactamente lo mismo que mantener la columna MAX en su propia tabla, pero sin ninguna desventaja de hacerlo explícitamente.

Tener una tabla explícita en realidad sería tanto más lenta (debido a la restricción de clave externa) y consumen más espacio (debido a la duplicación DetaiID). Sin mencionar que requiere más código, y los errores se introducen al ... escribir código.

alt text http://i.msdn.microsoft.com/ms189051.3be61595-d405-4b30-9794-755842d7db7e(en-us,SQL.100).gif

actualización

Para comprobar la ubicación real de los datos, una prueba simple puede mostrar que:

use tempdb; 
go 

create table a (
    id int identity(1,1) not null primary key, 
    v_a varchar(8000), 
    nv_a nvarchar(4000), 
    m_a varchar(max), 
    nm_a nvarchar(max), 
    t text, 
    nt ntext); 
go 

insert into a (v_a, nv_a, m_a, nm_a, t, nt) 
values ('v_a', N'nv_a', 'm_a', N'nm_a', 't', N'nt'); 
go 

select %%physloc%%,* from a 
go 

La columna %%physloc%% seudo mostrará la ubicación física real de la fila, en mi caso era la página 200:

dbcc traceon(3604) 
dbcc page(2,1, 200, 3) 

Slot 0 Column 2 Offset 0x19 Length 3 Length (physical) 3 
v_a = v_a        
Slot 0 Column 3 Offset 0x1c Length 8 Length (physical) 8 
nv_a = nv_a       
m_a = [BLOB Inline Data] Slot 0 Column 4 Offset 0x24 Length 3 Length (physical) 3 
m_a = 0x6d5f61      
nm_a = [BLOB Inline Data] Slot 0 Column 5 Offset 0x27 Length 8 Length (physical) 8 
nm_a = 0x6e006d005f006100    
t = [Textpointer] Slot 0 Column 6 Offset 0x2f Length 16 Length (physical) 16 
TextTimeStamp = 131137536   RowId = (1:182:0)      
nt = [Textpointer] Slot 0 Column 7 Offset 0x3f Length 16 Length (physical) 16 
TextTimeStamp = 131203072   RowId = (1:182:1) 

Todos los valores de columnas, pero TEXT y NTEXT se almacenaron en línea, incluidos los tipos MAX.
Después de cambiar las opciones de la tabla e insertar una nueva fila (sp_tableoption no afecta a las filas existentes), los tipos MAX fueron desalojados en su propio almacenamiento:

sp_tableoption 'a' , 'large value types out of row', '1'; 
insert into a (v_a, nv_a, m_a, nm_a, t, nt) 
values ('2v_a', N'2nv_a', '2m_a', N'2nm_a', '2t', N'2nt');  
dbcc page(2,1, 200, 3); 

Nota cómo m_A y columnas nm_a son ahora una Textpointer en el unidad de asignación de LOB:

Slot 1 Column 2 Offset 0x19 Length 4 Length (physical) 4 
v_a = 2v_a       
Slot 1 Column 3 Offset 0x1d Length 10 Length (physical) 10 
nv_a = 2nv_a       
m_a = [Textpointer] Slot 1 Column 4 Offset 0x27 Length 16 Length (physical) 16 
TextTimeStamp = 131268608   RowId = (1:182:2)      
nm_a = [Textpointer] Slot 1 Column 5 Offset 0x37 Length 16 Length (physical) 16 
TextTimeStamp = 131334144   RowId = (1:182:3)      
t = [Textpointer] Slot 1 Column 6 Offset 0x47 Length 16 Length (physical) 16 
TextTimeStamp = 131399680   RowId = (1:182:4)      
nt = [Textpointer] Slot 1 Column 7 Offset 0x57 Length 16 Length (physical) 16 
TextTimeStamp = 131465216   RowId = (1:182:5)      

Para sakeness terminación también puede forzar el uno de los campos no máximo fuera de la fila:

update a set v_a = replicate('X', 8000); 
dbcc page(2,1, 200, 3); 

Nota cómo la columna de la V_A se almacena en el almacenamiento de desbordamiento de fila:

Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4 
v_a = [BLOB Inline Root] Slot 0 Column 2 Offset 0x19 Length 24 Length (physical) 24 
Level = 0       Unused = 99       UpdateSeq = 1 
TimeStamp = 1098383360    
Link 0 
Size = 8000       RowId = (1:176:0) 

Así que, como otros ya han comentado, los tipos MAX se almacenan en línea por defecto, si se ajustan. Para muchos proyectos de DW, esto sería inaceptable porque las cargas típicas de DW deben escanear o, al menos, escanear el rango, por lo que se debe usar sp_tableoption ..., 'large value types out of row', '1'. Tenga en cuenta que esto no afecta a las filas existentes, en mi prueba ni siquiera en la reconstrucción del índice, por lo que la opción debe activarse antes.

Para la mayoría de las cargas de tipo OLTP, aunque el hecho de que los tipos MAX se almacenen en línea si es posible es una ventaja, ya que el patrón de acceso OLTP es buscar y el ancho de fila tiene poco impacto en él.

Sin embargo, con respecto a la pregunta original: no es necesaria una tabla por separado. Al activar la opción large value types out of row se obtiene el mismo resultado a un costo gratuito para desarrollo/prueba.

+1

+! ¿Sería SQL Server almacenar un varchar (max) con 17 caracteres en una unidad de asignación separada? – Andomar

+3

El comportamiento predeterminado es que no almacenará varchar (max) en el almacenamiento de LoB/asignaciones a menos que exceda la asignación, Cade Roux publicó un enlace a la configuración para alterar esto. – Andrew

+0

Muy detallado - Gracias por su completitud Remus –

0

Manténgalo en línea. El punto entero de varchar es que ocupa 0 bytes si está vacío, 4 bytes para 'Hola', y así sucesivamente.

+1

Son 2 bytes si están vacíos y creo que quiere decir 5 bytes para "Hola", pero debería ser 5 + 2. http://msdn.microsoft.com/en-us/library/ms176089.aspx – RichardTheKiwi

+0

@cyberkiwi: Tiene razón acerca de "Hola", pero un varchar nulo realmente ocupa 0 bytes. Cada fila tiene un mapa de bits nulo con un bit por columna. Ver http://weblogs.sqlteam.com/mladenp/archive/2007/09/06/How_does_SQL_Server_really_store_NULL-s.aspx – Andomar

+1

Estaba pensando en la cadena vacía '' no nula, ya que dijiste "si está vacía" en lugar de " si es nulo ". – RichardTheKiwi

0

Lo normalizaría creando la tabla de Detalle. ¿Asumo que algunas de las entradas en Log tendrán el mismo detalle? Por lo tanto, si lo normaliza, solo almacenará un FK id INTEGER en lugar del texto para cada ocurrencia si almacenó el texto en la tabla de detalles. Si tiene razones para desnormalizar, hágalo, pero de su pregunta no veo que ese sea el caso.

+0

@ StarShip3000, ¿sugiere que la tabla de detalles sea una tabla de dimensiones donde almacena valores de detalles predefinidos para vincular? Si es así, no creo que sea práctico para una tabla de registro (dado que sus detalles deben ser varchar (máx), los detalles probablemente no puedan ser predefinidos). Creo que está preguntando sobre la división de la tabla en dos partes uno a uno, lo que realmente no sería un problema de normalización, solo un problema de almacenamiento a nivel de página. –

+0

Lo sugiero solo si los valores son repetidos y todos conocidos. Si es un tipo de registro genérico que no, no lo sugeriría. – Kuberchaun

7

Paradójicamente, si sus datos son normalmente menos de 8000 caracteres, los almacenaría en una tabla separada, mientras que si los datos son mayores de 8000 caracteres, los mantendría en la misma tabla.

Esto es porque SQL Server mantiene los datos en la página si permite que la fila se asiente en una sola página, pero cuando los datos se hacen más grandes, se mueve igual que el tipo de datos TEXT y deja solo un puntero en la fila. Entonces, para un grupo de filas de 3000 caracteres, está ajustando menos filas por página, lo que es realmente ineficiente, pero para un grupo de filas de 12000 caracteres, los datos están fuera de la fila, por lo que en realidad es más eficiente.

Habiendo dicho esto, normalmente tiene una amplia gama de longitudes y por lo tanto la movería a su propia mesa. Esto le da flexibilidad para mover esta tabla a un grupo de archivos diferente, etc.

Tenga en cuenta que can also specify it to force the data out of the row usando el sp_tableoption. varchar (max) es básicamente similar al tipo de datos TEXT con su valor predeterminado en datos en fila (para varchar (max)) en lugar de valores predeterminados en datos fuera de la fila (para TEXT).

+0

Pensé que TEXT, IMAGE y amigos estaban almacenados fuera de la fila por defecto? –

+0

TEXT se almacena fuera de la fila de forma predeterminada, mientras que varchar (max) se almacena en fila de manera predeterminada. Aparte de eso, se comportan de manera muy similar en términos de almacenamiento. –

2

Debe estructurar sus datos en lo que parezca la estructura más lógica y permitir que SQL Server realice sus optimizaciones en cuanto a cómo almacenar físicamente los datos.

Si encuentra, a través del análisis del rendimiento, que su estructura es un problema de rendimiento, entonces considere realizar cambios en su estructura o en la configuración de almacenamiento.

0

Tener una columna anulable cuesta 2 bytes por cada 16 de ellos. Si esta es la única (o 17ª, 33ª, etc.) columna con nulos de la tabla, le costará 2 bytes por fila, de lo contrario, nada.

+0

IOW, simplemente no importa. – erikkallen

Cuestiones relacionadas