Work around SQL Server maximum columns limit 1024 and 8kb record size

前端 未结 7 1177
北海茫月
北海茫月 2020-12-01 05:46

I am creating a table with 1000 columns. Most of the columns are nvarchar type. Table is created, but with a warning

Warning: The t

7条回答
  •  南笙
    南笙 (楼主)
    2020-12-01 06:07

    This simply isn't possible. See Inside the Storage Engine: Anatomy of a record

    Assuming your table is something like this.

    CREATE TABLE T1(
        col_1 varchar(8000) NULL,
        col_2 varchar(8000) NULL,
        /*....*/
        col_999 varchar(8000) NULL,
        col_1000 varchar(8000) NULL
    ) 
    

    Then even a row with all NULL values will use the following storage.

    • 1 byte status bits A
    • 1 byte status bits B
    • 2 bytes column count offset
    • 125 bytes NULL_BITMAP (1 bit per column for 1,000 columns)

    So that is a guaranteed 129 bytes used up already (leaving 7,931).

    If any of the columns have a value that is not either NULL or an empty string then you also need space for

    • 2 bytes variable length column count (leaving 7,929).
    • Anywhere between 2 - 2000 bytes for the column offset array.
    • The data itself.

    The column offset array consumes 2 bytes per variable length column except if that column and all later columns are also zero length. So updating col_1000 would force the entire 2000 bytes to be used whereas updating col_1 would just use 2 bytes.

    So you could populate each column with 5 bytes of data and when taking into account the 2 bytes each in the column offset array that would add up to 7,000 bytes which is within the 7,929 remaining.

    However the data you are storing is 102 bytes (51 nvarchar characters) so this can be stored off row with a 24 byte pointer to the actual data remaining in row.

    FLOOR(7929/(24 + 2)) = 304
    

    So the best case would be that you could store 304 columns of this length data and that is if you are updating from col_1, col_2, .... If col_1000 contains data then the calculation is

    FLOOR(5929/24) = 247
    

    For NTEXT the calculation is similar except it can use a 16 byte pointer which would allow you to squeeze data into a few extra columns

    FLOOR(7929/(16 + 2)) = 440
    

    The need to follow all these off row pointers for any SELECT against the table would likely be highly detrimental to performance.

    Script to test this

    DROP TABLE T1
    
    /* Create table with 1000 columns*/
    DECLARE @CreateTableScript nvarchar(max) = 'CREATE TABLE T1('
    
    SELECT @CreateTableScript += 'col_' + LTRIM(number) + ' VARCHAR(8000),'
    FROM master..spt_values
    WHERE type='P' AND number BETWEEN 1 AND 1000
    ORDER BY number
    
    SELECT @CreateTableScript += ')'
    
    EXEC(@CreateTableScript)
    
    /* Insert single row with all NULL*/
    INSERT INTO T1 DEFAULT VALUES
    
    
    /*Updating first 304 cols succeed. Change to 305 and it fails*/
    DECLARE @UpdateTableScript nvarchar(max) = 'UPDATE T1 SET  '
    
    SELECT @UpdateTableScript += 'col_' + LTRIM(number) + ' = REPLICATE(1,1000),'
    FROM master..spt_values
    WHERE type='P' AND number BETWEEN 1 AND 304
    ORDER BY number
    
    SET @UpdateTableScript = LEFT(@UpdateTableScript,LEN(@UpdateTableScript)-1)
    EXEC(@UpdateTableScript)
    

提交回复
热议问题