it-swarm.com.de

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

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 Cluster- oder PK-Index). Der Datentyp ändert sich nicht (es ist ein INT). Nur die Nullbarkeit. Die Aussage lautet wie folgt:

Alter Table dbo.Workflow Alter Column LineId Int NULL

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

Die betreffende Tabelle ist gruppiert (kein Heap).

25
Randy Minder

Wie von @Souplex in den Kommentaren angedeutet, könnte eine mögliche Erklärung sein, wenn diese Spalte die erste NULL -fähige Spalte in dem nicht gruppierten Index ist, an dem sie teilnimmt.

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 ix 248 Blattseiten und eine einzelne Stammseite hat.

Eine typische Zeile auf einer Indexblattseite sieht aus wie

enter image description here

Und auf der Stammseite

enter image description here

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 erneut überprüfen, sehen die Zeilen jetzt so aus

enter image description here

und die Zeilen auf den oberen Seiten wie unten.

enter image description here

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 der Zwischenebene zusammen mit der Stammseite.

Der Ausführungsplan für die

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

sieht wie folgt aus

enter image description here

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

27
Martin Smith

Es wird definitiv den nicht gruppierten Index neu erstellen und nicht nur Metadaten aktualisieren. Dies wird unter SQL 2014 getestet und sollte eigentlich 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 lustigen Teil:

DBCC IND (0, z, -1)

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

Suchen Sie das PagePID, wobei IndexID 2 und PageType 2 ist, und führen Sie dann die folgenden Schritte aus:

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:

(Page header extract

Jetzt machen wir:

alter table z alter Column b int null;

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

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


Viele ALTER TABLE ... ALTER COLUMN ... - Operationen können ONLINE ab SQL Server 2016 ausgeführt werden, aber:

ALTER TABLE (Transact-SQL)

  • Das Ändern einer Spalte von NOT NULL In NULL wird als Online-Vorgang nicht unterstützt, wenn auf die geänderte Spalte von nicht gruppierten Indizes verwiesen wird.
9
Spörri