Vous êtes sûr d’avoir les BACKUP avec CHECKSUM parce que vous utilisez l’option COMPRESSION ? Vérifiez une seconde fois !

Après avoir vu comment compresser toutes les sauvegardes de base de données dès SQL Server 2008, il est intéressant de trouver comment activer l’option CHECKSUM de l’instruction BACKUP.

A ce sujet, la documentation indique :

Indique que l’opération de sauvegarde vérifie dans chaque page les informations de somme de contrôle et de page endommagée, si elles sont activées et disponibles, et génère une somme de contrôle pour l’ensemble de la sauvegarde. Ceci est le comportement par défaut pour une sauvegarde compressée.

Vérifions donc si cela est vrai : nous allons créer une base de données de test, et nous allons corrompre une de ses pages afin de vérifier le comportement de la sauvegarde. En effet, l’option CHECKSUM permet de détecter la corruption de données au moment de la sauvegarde. Cela permet en plus de ne pas se retrouver dans l’inconfortable situation de devoir restaurer une base de données à partir d’un fichier de sauvegarde corrompu.

La vérification de l’intégrité des pages au niveau de la base de données

Par défaut, lorsqu’on crée une nouvelle base de données sous SQL Server 2005 et suivants, la vérification de l’intégrité des pages lues à partir du disque se fait à l’aide d’une somme de contrôle. Celle-ci est calculée à partir du contenu de la page, puis écrite dans l’en-tête de celle-ci au moment de l’écriture sur disque. Lorsque la page est ensuite lue à partir du disque, la somme de contrôle est recalculée, puis comparée à celle qui est dans l’en-tête de la page : si les valeurs sont identiques, alors la page est intègre; à l’inverse, on obtient l’erreur 824, qui est écrite à la fois dans les journaux de SQL Server et dans le journal d’événements de Windows.
Enfin lorsqu’on restaure une base de données qui a été créée sous une version antérieure à 2005, il est important de changer cette option de TORN_PAGE_DETECTION à PAGE_VERIFY, car la première est conservée par défaut. L’option TORN_PAGE_DETECTION stocke deux bits pour chaque secteur de 512 octets de toute page, dont la taille est 8192 octets. De la même façon, ces 16 * 2 bits sont stockés dans l’en-tête de la page, et comparés à la relecture de la page. Comme cette option est moins stricte que CHECKSUM, il est donc recommandé d’utiliser cette dernière option.

Le test

Nous créons donc la base de données sous SQL Server 2012, puis nous vérifions l’option du contrôle de l’intégrité des pages :

1
2
3
4
5
6
7
8
9
10
CREATE DATABASE TEST_CHECKSUM;
GO

USE TEST_CHECKSUM
GO

SELECT  name
        , page_verify_option_desc
FROM    sys.databases
WHERE   name = 'TEST_CHECKSUM'

Nous créons ensuite une table dans cette base de données, à laquelle nous ajoutons une ligne :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
USE TEST_CHECKSUM
GO

CREATE TABLE test_backup_checksum
(
        id tinyint IDENTITY
        , nom varchar(16)
)
GO

INSERT INTO dbo.test_backup_checksum (nom)
VALUES ('test_checksum')
GO

SELECT  *
FROM    dbo.test_backup_checksum

Nous forçons ensuite l’écriture des pages sur disque, à l’aide de l’instruction CHECKPOINT, puis nous vidons le cache de données, de sorte que la prochaine fois que nous exécuterons une requête sur cette table, ses pages seront lues à partir du disque, à l’aide de l’instruction DBCC DROPCLEANBUFFERS.

N.B. : N’exécutez JAMAIS une instruction non-documentée sur une base de données qui sert une charge de production. Si tel devait être le cas, je ne peux en aucun cas être tenu pour responsable des conséquences. Vous devez limiter strictement l’usage de cette instruction aux environnements de test.

1
2
3
4
5
CHECKPOINT
GO

DBCC DROPCLEANBUFFERS
GO

L’instruction DBCC IND, ou la DMF sys.dm_db_database_page_allocations

Encore deux options d’exploration de la mécanique interne de SQL Server, qui permettent de lister les pages d’une table ou d’un index, et qui ne sont pas documentées. Voici le prototype d’utilisation de ces deux alternatives :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
DBCC IND ('TEST_CHECKSUM', 'dbo.test_backup_checksum', 1) -- le dernier argument est l'index id
GO

SELECT  allocated_page_file_id
        , allocated_page_page_id
        , allocated_page_iam_file_id
        , allocated_page_iam_page_id
        , object_id
        , index_id
        , partition_id
        , allocation_unit_type_desc AS iam_chain_type
        , page_type_desc
        , page_level
        , rowset_id
        , next_page_file_id
        , next_page_page_id
        , previous_page_file_id
        , previous_page_page_id
FROM    sys.dm_db_database_page_allocations
        (
                DB_ID('TEST_CHECKSUM')
                , OBJECT_ID('dbo.test_backup_checksum')
                , 0 -- index id : 0 pour un tas, 1 pour un index cluster, plus grand pour un index non-cluster
                , NULL -- partition id
                , 'DETAILED' -- niveau de détail
        )
WHERE   is_allocated = 1;

On remarque tout de suite que la DMF produit bien plus d’informations que l’instruction DBCC (nous ne donnons pas ici toutes les colonnes), et que ses arguments sont similaires à la DMF sys.dm_db_index_physical_stats.

Voyons donc ce que contient la page 272, puisque c’est elle qui contient les données (page_type_desc = DATA_PAGE) :

1
2
3
4
5
6
7
8
-- Activation de la sortie de la commande DBCC
DBCC TRACEON (3604)
GO

-- DBCC PAGE ([database_name_or_id], [file_id], [page_id], [option])
-- option = 0 montre seulement l'en-tête de la page
DBCC PAGE ('TEST_CHECKSUM', 1, 272, 0);
GO

Nous obtenons :

PAGE: (1:272)

BUFFER:

BUF @0x000000027407BB80

bpage = 0x00000002649F4000 bhash = 0x0000000000000000 bpageno = (1:272)
bdbid = 8 breferences = 0 bcputicks = 0
bsampleCount = 0 bUse1 = 33295 bstat = 0x9
blog = 0x2121215a bnext = 0x0000000000000000

PAGE HEADER:

Page @0x00000002649F4000

m_pageId = (1:272) m_headerVersion = 1 m_type = 1
m_typeFlagBits = 0x0 m_level = 0 m_flagBits = 0x8200
m_objId (AllocUnitId.idObj) = 84 m_indexId (AllocUnitId.idInd) = 256
Metadata: AllocUnitId = 72057594043432960
Metadata: PartitionId = 72057594039042048 Metadata: IndexId = 0
Metadata: ObjectId = 245575913 m_prevPage = (0:0) m_nextPage = (0:0)
pminlen = 5 m_slotCnt = 1 m_freeCnt = 8069
m_freeData = 121 m_reservedCnt = 0 m_lsn = (30:345:30)
m_xactReserved = 0 m_xdesId = (0:0) m_ghostRecCnt = 0
m_tornBits = 1209479828 DB Frag ID = 1

Si nous nous en référons à la documentation donnée par Paul Randal, nous avons bien :

m_flagBits = 0x8200, qui indique que l’intégrité de la page est vérifiée par somme de contrôle
m_tornBits est la somme de contrôle de cette page

Nous allons maintenant mettre la base de données hors-ligne, puis éditer le fichier de données avec un éditeur hexadécimal, de façon à vérifier si SQL Server vérifie bien la somme à la lecture de la page :

1
2
ALTER DATABASE SET OFFLINE
GO

En ce qui concerne la lecture en hexadécimal, j’utilise le logiciel Hex Editor XVI32. Une fois l’outil ouvert en tant qu’administrateur, il suffit de chercher la chaîne que nous avons ajouté comme ligne dans la table, en suivant le menu Search / Find :

Un clic sur le bouton OK nous amène directement à l’emplacement, où l’on est alors libre de changer la chaîne, où j’ai changé le « k » en « d », puis sauvé le fichier :

Nous remettons la base de données en ligne :

1
2
3
ALTER DATABASE TEST_CHECKSUM
SET ONLINE
GO

Aucun problème. Exécutons un SELECT * FROM dbo.test_backup_checksum. Nous obtenons le message suivant :

Msg 824, Level 24, State 2, Line 1
SQL Server detected a logical consistency-based I/O error: incorrect checksum (expected: 0xf9ec3284; actual: 0xfe6c3284). It occurred during a read of page (1:228) in database ID 7 at offset 0x000000001c8000 in file ‘C:\Program Files\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQL\DATA\TEST_CHECKSUM.mdf’. Additional messages in the SQL Server error log or system event log may provide more detail. This is a severe error condition that threatens database integrity and must be corrected immediately. Complete a full database consistency check (DBCC CHECKDB). This error can be caused by many factors; for more information, see SQL Server Books Online.

Prenons maintenant une sauvegarde de cette base de données, avec COMPRESSION, mais sans spécifier l’option CHECKSUM :

1
2
3
4
BACKUP DATABASE TEST_CHECKSUM
TO DISK = 'F:\SQLServer2012\TEST_CHECKSUM.bak'
WITH COMPRESSION
GO

Aucun problème : nous obtenons :

Processed 280 pages for database ‘TEST_CHECKSUM’, file ‘TEST_CHECKSUM’ on file 1.
Processed 3 pages for database ‘TEST_CHECKSUM’, file ‘TEST_CHECKSUM_log’ on file 1.
BACKUP DATABASE successfully processed 283 pages in 0.468 seconds (4.709 MB/sec).

Voyons ce que contient la table d’historique des sauvegardes :

1
2
3
4
5
6
7
SELECT  user_name
        , backup_start_date
        , recovery_model
        , has_backup_checksums
        , is_damaged
FROM    msdb.dbo.backupset
WHERE   database_name = 'TEST_CHECKSUM'

On obtient un résultat similaire avec l’instruction RESTORE HEADERONLY (colonnes HasBackupChecksums et IsDamaged toutes deux à zéro) :

1
2
RESTORE HEADERONLY
FROM DISK = 'F:\SQLServer2012\TEST_CHECKSUM.bak'

Le fichier de sauvegarde ne dispose pas de la somme de contrôle, et n’est pas marqué comme endommagé. Pourtant nous avons bien corrompu le fichier de données, et nous ne pouvons pas lire la table que nous avons peuplé auparavant !

Voyons ce que produit la même instruction, avec l’option CHECKSUM :

1
2
3
4
BACKUP DATABASE TEST_CHECKSUM
TO DISK = 'F:\SQLServer2012\TEST_CHECKSUM_CC.bak'
WITH COMPRESSION, CHECKSUM
GO

Msg 3043, Level 16, State 1, Line 1
BACKUP ‘TEST_CHECKSUM’ detected an error on page (1:228) in file ‘C:\Program Files\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQL\DATA\TEST_CHECKSUM.mdf’.
Msg 3013, Level 16, State 1, Line 1
BACKUP DATABASE is terminating abnormally.

Voyons maintenant la même instruction, seulement avec l’option CHECKSUM :

1
2
3
4
BACKUP DATABASE TEST_CHECKSUM
TO DISK = 'F:\SQLServer2012\TEST_CHECKSUM_CHK.bak'
WITH CHECKSUM
GO

Nous obtenons la même erreur. Si nous interrogeons de nouveau la table d’historique des sauvegardes pour cette base de données, nous ne trouvons que le premier backup, dont on sait qu’il est corrompu. Pourtant nous n’avons (presque) aucun moyen de le savoir, car ni cette table, ni l’instruction RESTORE HEADERONLY ne permettent de vérifier que le fichier est bien intègre. On peut cependant vérifier les journaux de SQL Server à l’aide de la procédure stockée étendue xp_readerrorlog, où l’on trouve :

Détecter la corruption le plus tôt possible

Une des premières choses à faire après avoir réalisé une installation fraîche de SQL Server est d’ajouter une liste d’alertes de l’Agent SQL Server, qui enverront un mail dès la détection d’un problème de corruption. Ceci nécessite le paramétrage de Database Mail, et la mise en place d’un opérateur SQL Agent. Voici le code qui permet d’ajouter l’alerte :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
-------------------------------
-- Nicolas Souquet - 26/05/2013
-------------------------------
DECLARE @i smallint = 823
        , @alert_name sysname

DECLARE @operator_name sysname = 'monOperateur'

WHILE @i <= 825
BEGIN
        SELECT  @alert_name = 'Message ' + CAST(@i AS char(3))

        EXEC msdb.dbo.sp_add_alert
                @name = @alert_name
                , @message_id = @i
                , @severity = 0
                , @enabled = 1
                , @delay_between_responses = 0
                , @include_event_description_in = 1
                , @job_id = N'00000000-0000-0000-0000-000000000000'

        EXEC msdb.dbo.sp_add_notification
                @alert_name = @alert_name
                , @operator_name = @operator_name
                , @notification_method = 1

        SET @i = @i + 1
END

L’erreur qui nous intéresse est la 824, mais les erreurs 823 et 825 sont importantes aussi. Pour trouver leur signification, il suffit d’interroger la vue système sys.messages :

1
2
3
4
SELECT  *
FROM    sys.messages
WHERE   message_id BETWEEN 823 AND 825
AND     language_id = 1036 -- Français

On peut faire de même avec l’erreur n° 3043, qui est celle que l’on obtient lors de la sauvegarde.

Activer CHECKSUM pour toutes les sauvegardes

Cela devrait aussi faire partie de la checklist d’installation de SQL Server, d’autant que c’est d’une simplicité enfantine : il suffit d’activer le drapeau de trace n° 3023 :

1
DBCC TRACEON (3023, -1)

Après avoir exécuté cette instruction, si nous ré-exécutons maintenant la commande suivante :

1
2
3
4
BACKUP DATABASE TEST_CHECKSUM
TO DISK = 'F:\SQLServer2012\TEST_CHECKSUM.bak'
WITH COMPRESSION
GO

Nous obtenons l’erreur que nous avions obtenue lorsque nous avions ajouté l’option CHECKSUM :

Msg 3043, Level 16, State 1, Line 1
BACKUP ‘TEST_CHECKSUM’ detected an error on page (1:228) in file ‘C:\Program Files\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQL\DATA\TEST_CHECKSUM.mdf’.
Msg 3013, Level 16, State 1, Line 1
BACKUP DATABASE is terminating abnormally.

Ceci est très pratique :

– Si l’on prend les sauvegardes de base de données à l’aide de plans de maintenance, il est impossible de spécifier que l’on souhaite avoir la somme de contrôle;
– Si l’on code une procédure stockée qui s’en charge, cela évite d’avoir à l’ajouter dans le code.

D’autre part si la compression des sauvegardes est activée au niveau de l’instance, on n’a plus qu’a se soucier de la sauvegarde elle-même !

Bonnes sauvegardes et intégrité de pages à tous !

ElSüket.

Laisser un commentaire