Ändern einer Spalte von NOT NULL in NULL - Was ist unter der Haube los?


25

Wir haben eine Tabelle mit 2.3B Zeilen. Wir möchten eine Spalte von NOT NULL in NULL ändern. Die Spalte ist in einem Index enthalten (nicht im Clustered- oder PK-Index). Der Datentyp ändert sich nicht (es ist ein INT). Nur die Nichtigkeit. Die Aussage ist wie folgt:

Alter Table dbo.Workflow Alter Column LineId Int NULL

Der Vorgang dauert mehr als 10 Minuten, bevor er beendet wird (wir haben ihn noch nicht einmal vollständig ausgeführt, da es sich um einen blockierenden Vorgang handelt, der zu lange gedauert hat). Wir werden die Tabelle wahrscheinlich auf einen Entwickler-Server kopieren und testen, wie lange es tatsächlich dauert. Aber ich bin neugierig, ob jemand weiß, was SQL Server unter der Haube tut, wenn es von NOT NULL nach NULL konvertiert? Müssen betroffene Indizes auch neu erstellt werden? Der generierte Abfrageplan zeigt nicht an, was gerade passiert.

Die betreffende Tabelle ist gruppiert (kein Heap).


2
Ich denke, es müsste die Null-Bitmap auf allen Blattebenen-Datenseiten aktualisieren. Und mit 2.3B Zeilen hätte es bestimmt eine Menge Seiten, die man durcharbeiten müsste. Da bin ich mir allerdings nicht so sicher.
souplex

3
Könnte damit beschäftigt sein, auch eine Null-Bitmap in den Index einzufügen. NULL-Bitmap ist in einem NICHT-CLUSTERED-INDEX NICHT vorhanden, wenn alle Spalten der Indexdefinition als NOT NULL definiert sind.
souplex

Antworten:


27

Wie von @Souplex in den Kommentaren angedeutet, könnte eine mögliche Erklärung darin bestehen, ob diese Spalte die erste NULLverfügbare Spalte im nicht gruppierten Index ist, an dem sie beteiligt ist.

Für das folgende Setup

CREATE TABLE Foo
  (
     A UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID() PRIMARY KEY,
     B CHAR(1) NOT NULL DEFAULT 'B'
  )

CREATE NONCLUSTERED INDEX ix
  ON Foo(B);

INSERT INTO Foo
            (B)
SELECT TOP 100000 'B'
FROM   master..spt_values v1,
       master..spt_values v2 

sys.dm_db_index_physical_stats zeigt, dass der nicht gruppierte Index ix248 Blattseiten und eine einzelne Stammseite hat.

Eine typische Zeile in einer Indexblattseite sieht wie folgt aus

Bildbeschreibung hier eingeben

Und auf der Root-Seite

Bildbeschreibung hier eingeben

Dann rennen ...

CHECKPOINT;

GO

ALTER TABLE Foo ALTER COLUMN B  CHAR(1) NULL;


SELECT Operation, 
       Context,
       ROUND(SUM([Log Record Length]) / 1024.0,1) AS [Log KB],
       COUNT(*) as [OperationCount]
FROM sys.fn_dblog(NULL,NULL)
WHERE AllocUnitName = 'dbo.Foo.ix'
GROUP BY Operation, Context

Ist zurückgekommen

+-----------------+--------------------+-------------+----------------+
|    Operation    |      Context       |   Log KB    | OperationCount |
+-----------------+--------------------+-------------+----------------+
| LOP_SET_BITS    | LCX_GAM            | 4.200000    |             69 |
| LOP_FORMAT_PAGE | LCX_IAM            | 0.100000    |              1 |
| LOP_SET_BITS    | LCX_IAM            | 4.200000    |             69 |
| LOP_FORMAT_PAGE | LCX_INDEX_INTERIOR | 8.700000    |              3 |
| LOP_FORMAT_PAGE | LCX_INDEX_LEAF     | 2296.200000 |            285 |
| LOP_MODIFY_ROW  | LCX_PFS            | 16.300000   |            189 |
+-----------------+--------------------+-------------+----------------+

Wenn Sie das Indexblatt noch einmal überprüfen, sehen die Zeilen nun so aus

Bildbeschreibung hier eingeben

und die Zeilen in den oberen Seiten wie unten.

Bildbeschreibung hier eingeben

Jede Zeile wurde aktualisiert und enthält jetzt zwei Bytes für die Spaltenanzahl sowie ein weiteres Byte für die NULL_BITMAP.

Aufgrund der zusätzlichen Zeilenbreite hat der nicht gruppierte Index jetzt 285 Blattseiten und jetzt zwei Seiten auf Zwischenebene zusammen mit der Stammseite.

Der Ausführungsplan für die

 ALTER TABLE Foo ALTER COLUMN B  CHAR(1) NULL;

sieht wie folgt aus

Bildbeschreibung hier eingeben

Dadurch wird eine brandneue Kopie des Index erstellt, anstatt den vorhandenen zu aktualisieren und die Seiten zu teilen.


9

Es wird definitiv den nicht gruppierten Index neu erstellen und nicht nur Metadaten aktualisieren. Dies wurde in SQL 2014 getestet und sollte nicht auf einem Produktionssystem getestet werden:

CREATE TABLE [z](
    [a] [int] IDENTITY(1,1) NOT NULL,
    [b] [int] NOT NULL,
 CONSTRAINT [c_a] PRIMARY KEY CLUSTERED  ([a] ASC))
go
CREATE NONCLUSTERED INDEX [nc_b] on z (b asc)
GO
insert into z (b)
values (1);

Und jetzt zum spaßigen Teil:

DBCC IND (0, z, -1)

Dadurch erhalten wir die Datenbankseiten, auf denen die Tabelle und der nicht gruppierte Index gespeichert sind.

Suchen Sie nach dem PagePIDWert IndexID2 und PageType2 und gehen Sie dann wie folgt vor:

DBCC TRACEON(3604) --are you sure that you are allowed to do this?

und dann:

dbcc page (0, 1, PagePID, 3) with tableresults

Beachten Sie, dass der Header eine Null-Bitmap enthält:

Seitenkopf-Extrakt

Nun lass uns machen:

alter table z alter Column b int null;

Wenn Sie wirklich ungeduldig sind, können Sie versuchen, den dbcc pageBefehl erneut auszuführen , aber er schlägt fehl. Überprüfen Sie die Zuordnung also erneut mit DBCC IND (0, z, -1). Die Seite wird sich wie von Zauberhand bewegt haben.

Das Ändern der Nullfähigkeit einer Spalte wirkt sich also auf die Speicherung von nicht gruppierten Indizes aus, die diese Spalte abdecken, da die Metadaten aktualisiert werden müssen und Sie die Indizes anschließend nicht neu erstellen müssen.


Viele ALTER TABLE ... ALTER COLUMN ...Vorgänge können ONLINEab SQL Server 2016 ausgeführt werden, aber:

ALTER TABLE (Transact-SQL)

  • Das Ändern einer Spalte von NOT NULLbis NULLwird nicht als Online-Vorgang unterstützt, wenn auf die geänderte Spalte von nicht gruppierten Indizes verwiesen wird.
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.