Dedicated administrator connections are not supported via SSMS as it establishes multiple connections by design : que faire ?

Lorsqu’on tente d’ouvrir une connexion d’administrateur dédiée (DAC dans le jargon) à partir de l’explorateur d’objets de SQL Server Management Studio (SSMS), on obtient l’erreur suivante :
Lire la suite

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.
Lire la suite

Mise au point à propos du compteur de performance Page Life Expectancy

Parmi les compteurs de performance qu’expose SQL Server, il en existe quelques-uns qui ne laissent aucun doute sur les performances courantes d’une instance SQL Server. Page Life Expectancy est l’un d’entre eux, puisqu’il nous renseigne sur la durée de vie moyenne d’une page de données en RAM, en secondes.

L’ensemble des opérations de manipulation de données se faisant exclusivement en mémoire par tout SGBDR digne de ce nom, on comprend aisément que plus la valeur exposée par ce compteur est élevée, plus les performances de l’instance sont correctes. En effet, comme un accès en RAM est en pratique au moins 1000 fois plus rapide qu’un accès à des disques mécaniques, plus on peut traiter de données en RAM, moins on est exposé à des problèmes de performances.

Cela étant, les serveurs physiques actuels disposant de plusieurs sockets pour accueillir leurs CPUs, ils sont tous une implémentation de NUMA (Non-Uniform Memory Access), dans lequel chaque processeur dispose d’un bus mémoire dédié. On appelle nÅ“ud NUMA l’ensemble composé par l’espace mémoire et le processeur situés sur le même bus. On désigne par nÅ“ud NUMA distant un nÅ“ud NUMA distinct de celui sur lequel la requête s’exécute.

Si par le passé on pouvait donc se référer directement à la valeur de ce compteur, puisque les serveur ne disposaient que d’un seul socket, qu’en est-il avec les architectures multi-socket ? Comment suivre cette valeur sous SQL Server ? Quelle valeur de ce compteur peut servir de seuil d’alerte ?

Je vous propose la réponse à ces questions dans cet article !
Lire la suite

Une procédure stockée pour connaître l’état des fichiers du journal des transactions

Ha ! le fichier du journal des transactions, l’option de récupération, son grossissement, … et bien sûr la fameuse transaction restée ouverte qui a provoqué une explosion de la taille de celui-ci, allant jusqu’à remplir le volume disque qui l’héberge : tout autant de paramétrages et d’investigations fastidieuses.

Alors comme je n’aime pas beaucoup répéter plusieurs fois les mêmes opérations sans avoir un script, ou mieux, une procédure stockée sous la main, j’ai créé la suivante, dont voici le script de création :

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
USE master
GO

-----------------------------------------------------------------
-- Nicolas Souquet - 09/02/2013
-- LA PROCEDURE STOCKEE SUIVANTE EST DONNEE EN TANT QUE TELLE
-- SON UTILISATION ET SA MODIFICATION EST A VOS RISQUES ET PÉRILS
-----------------------------------------------------------------
CREATE PROCEDURE [dbo].[sp__log_space_get]
        @_database_name sysname = NULL
AS
BEGIN
        SET NOCOUNT ON

        DECLARE @sql varchar(256)

        -- Récupération de l'espace disque disponible
        -- sur tous les volumes visibles par l'instance SQL Server
        DECLARE @free_disk_space TABLE
        (
                drive_letter char(1) NOT NULL
                , free_space_MB int NOT NULL
        )

        INSERT  INTO @free_disk_space
        EXEC    master.dbo.xp_fixeddrives

        -- Récupération de l'occupation des fichiers des journaux de transaction
        -- de toutes les bases de données hébergées par l'instance SQL Server
        DECLARE @dbcc_sqlperf_logspace TABLE
        (
                database_name sysname
                , log_size float
                , log_space_used_pct decimal (5,2)
                , status tinyint
        )
        INSERT  INTO @dbcc_sqlperf_logspace
        EXEC    ('DBCC SQLPERF(logspace)')

        -- Récupération de la liste de tous les fichiers virtuels
        -- du fichier du journal des transactions de toutes les bases de données
        -- disponibles, hébergées par l'instance SQL Server
        DECLARE @dbcc_loginfo TABLE
        (
                recovery_unit_id tinyint -- new column in SQL Server 2012
                , fileid tinyint NOT NULL
                , file_size bigint NOT NULL
                , start_offset bigint NOT NULL
                , f_seq_no bigint NOT NULL
                , status tinyint NOT NULL
                , parity tinyint NOT NULL
                , create_LSN varbinary(max) NOT NULL
        )

        -- Cas où l'on souhaite réaliser l'audit du fichier du journal des transactions
        -- de toutes les bases de données hébergées par l'instance SQL Server
        -- Dans ce cas le paramètre d'entrée @_database_name est à NULL
        IF @_database_name IS NULL
        BEGIN
                DECLARE @dbcc_loginfo_all_db TABLE
                (
                        database_name varchar(128)
                        , log_file_id tinyint
                        , vlf_count smallint
                )

                -- Récupération de liste de toutes les bases de données disponibles
                -- (pas une capture iOFFLINE, SINGLE_USER, ou ... SUSPECT ;))
                INSERT  INTO @dbcc_loginfo_all_db
                (
                        database_name
                        , log_file_id
                        , vlf_count
                )
                SELECT          D.name
                                , MF.file_id
                                , 0
                FROM            sys.databases AS D
                INNER JOIN      sys.master_files AS MF
                                        ON D.database_id = MF.database_id
                WHERE           D.source_database_id IS NULL -- ce n'est pas une capture instantanée de base de données
                AND             D.state_desc = 'ONLINE' -- la base de données est disponible
                AND             D.user_access_desc = 'MULTI_USER' -- la base de données est à l'écoute de connexions utilisateur
                AND             MF.type_desc = 'LOG' -- le type de fichier est "journal des transactions"

                -- Pour chaque base de données, récupération de la liste des fichiers virtuels
                -- du fichier du journal des transactions pour toutes les bases de données disponibles
                WHILE EXISTS
                (
                        SELECT  *
                        FROM    @dbcc_loginfo_all_db
                        WHERE   vlf_count = 0
                )
                BEGIN
                        SELECT  TOP 1 @_database_name = database_name
                        FROM    @dbcc_loginfo_all_db
                        WHERE   vlf_count = 0

                        SET @sql = 'DBCC LOGINFO (''' + @_database_name + ''')'

                        INSERT  INTO @dbcc_loginfo
                        EXEC    (@sql)

                        ;WITH
                                CTE AS
                                (
                                        SELECT  fileid
                                                , COUNT(*) AS vlf_count
                                        FROM    @dbcc_loginfo
                                        GROUP   BY fileid
                                )
                        UPDATE          @dbcc_loginfo_all_db
                        SET             vlf_count = C.vlf_count
                        FROM            @dbcc_loginfo_all_db AS DLAD
                        INNER JOIN      CTE AS C
                                                ON DLAD.log_file_id = C.fileid
                        WHERE           DLAD.database_name = @_database_name

                        DELETE  FROM @dbcc_loginfo
                END

                -- Résultat final, avec addition de quelques détails
                SELECT          LS.database_name
                                , D.recovery_model_desc
                                , MF.name AS logical_name
                                , FDS.drive_letter AS transaction_log_file_stored_on
                                , FDS.free_space_MB AS volume_free_space_MB
                                , CAST(LS.log_size AS decimal(38,2)) AS transaction_log_file_size_MB
                                , LS.log_space_used_pct
                                , CAST((LS.log_space_used_pct / 100.0) * LS.log_size AS decimal(38,2)) AS log_used_size_MB
                                , CASE MF.is_percent_growth
                                        WHEN 1 THEN CAST(MF.growth AS varchar(3)) + ' %'
                                        ELSE CAST(MF.growth / 128 AS varchar(20)) + ' MB'
                                END AS growth                          
                                , D.log_reuse_wait_desc
                                , DLAD.vlf_count
                                , MF.physical_name
                FROM            sys.master_files AS MF
                INNER JOIN      @dbcc_sqlperf_logspace AS LS
                                        ON LS.database_name = DB_NAME(MF.database_id)
                INNER JOIN      @free_disk_space AS FDS
                                        ON FDS.drive_letter = LEFT(MF.physical_name, 1) COLLATE database_default
                INNER JOIN      sys.databases AS D
                                        ON D.name = LS.database_name COLLATE database_default
                INNER JOIN      @dbcc_loginfo_all_db AS DLAD
                                        ON DLAD.database_name = D.name COLLATE database_default
                                        AND DLAD.log_file_id = MF.file_id
                                        AND DLAD.database_name = DB_NAME(MF.database_id)
                WHERE           MF.type_desc = 'LOG'
                AND             D.source_database_id IS NULL
                AND             D.state_desc = 'ONLINE'
                AND             D.user_access_desc = 'MULTI_USER'
        END
        ELSE
        BEGIN
                -- Si la base de données que l'on souhaite auditer n'existe pas ==> exception
                IF DB_ID(@_database_name) IS NULL
                BEGIN
                        RAISERROR('The database named ''%s'' does not exist', 16, 1, @_database_name)
                        RETURN
                END
               
                -- récupération de la liste des fichiers virtuels
                -- du fichier du journal des transactions de la base de données choisie
                DECLARE @vlf_count int
                SET @sql = 'DBCC LOGINFO (''' + @_database_name + ''')'

                INSERT  INTO @dbcc_loginfo
                EXEC    (@sql)

                -- Résultat final, avec addition de quelques détails
                ;WITH
                        CTE AS
                        (
                                SELECT  fileid
                                        , COUNT(*) AS vlf_count
                                FROM    @dbcc_loginfo
                                GROUP   BY fileid
                        )
                SELECT          LS.database_name
                                , D.recovery_model_desc
                                , MF.name AS logical_name
                                , FDS.drive_letter AS transaction_log_file_stored_on
                                , FDS.free_space_MB AS volume_free_space_MB
                                , CAST(LS.log_size AS decimal(38,2)) AS transaction_log_file_size_MB
                                , LS.log_space_used_pct
                                , CAST((LS.log_space_used_pct / 100.0) * LS.log_size AS decimal(38,2)) AS log_used_size_MB
                                , CASE MF.is_percent_growth
                                        WHEN 1 THEN CAST(MF.growth AS varchar(3)) + ' %'
                                        ELSE CAST(MF.growth / 128 AS varchar(20)) + ' MB'
                                END AS growth
                                , D.log_reuse_wait_desc
                                , C.vlf_count AS VLF_count
                                , MF.physical_name
                FROM            sys.master_files AS MF
                INNER JOIN      @dbcc_sqlperf_logspace AS LS
                                        ON LS.database_name = DB_NAME(MF.database_id)
                INNER JOIN      @free_disk_space AS FDS
                                        ON FDS.drive_letter = LEFT(MF.physical_name, 1) COLLATE database_default
                INNER JOIN      sys.databases AS D
                                        ON D.name = LS.database_name COLLATE database_default
                INNER JOIN      CTE AS C
                                        ON C.fileid = MF.file_id
                WHERE           MF.type_desc = 'LOG'
                AND             LS.database_name = @_database_name
                AND             D.source_database_id IS NULL
                AND             D.state_desc = 'ONLINE'
                AND             D.user_access_desc = 'MULTI_USER'
        END
END
GO

-- On marque la procédure stockée comme objet système
-- Dès lors, on pourra l'exécuter dans n'importe quel contexte de base de données
EXEC sp_ms_marksystemobject 'sp__log_space_get'

Elle donne un résultat comme le suivant :

sp__log_space_get

à savoir :

– le nom de la base de données
– l’option de récupération de la base de données
– le nom logique du fichier du journal des transactions (utile pour un DBCC SHRINKFILE après que le journal ait explosé en taille)
– le volume sur lequel est hébergé le fichier du journal des transactions
– la quantité d’espace disque libre restant sur ce volume
– la taille actuelle du fichier du journal des transactions
– le pourcentage d’utilisation de ce fichier (taille occupée dans le fichier par rapport à la taille totale du fichier)
– la taille équivalente à ce pourcentage
– le paramètre de grossissement de ce fichier
– ce qui empêche actuellement la troncature de l’espace occupé dans ce fichier
– le chemin complet du fichier

On peut utiliser cette procédure stockée sous SQL Server 2005, 2008 et 2012.
Elle fonctionne également pour les bases de données ayant plusieurs fichiers du journal des transactions

Bonne gestion des fichiers du journal des transactions !

ElSüket ;)

Reprise d’activité étape par étapes : le poster de Paul Randal

Paul Randal est très connu dans la communauté SQL Server puisqu’il publie très régulièrement des détails sur le fonctionnement interne de SQL Server, dont certains ne sont pas documentés, à travers son site SQLSkills.com (son blog et ceux d’autres experts sont référencés en bas à droite de la page).

Paul Randal vient donc de mettre à la disposition de la communauté un poster qui détaille les étapes de la reprise d’activité par un diagramme décisionnel.

Ce diagramme contient certaines étapes qui référencent un numéro, et chaque numéro correspond à un article publié par Paul Randal. Ils sont tous référencés sur cette page.

Toujours bon à avoir sous la main !

Cadeau de Noël de Kalen Delaney : un livre sur le verrouillage, le blocage et le versionnement de ligne

Le site de RedGate, créé par Bard Mc Gehee, publie depuis quelques années déjà de nombreux ouvrages, dont certains abordent des thèmes que l’on voit rarement explorés en détails, parfois même par les mêmes auteurs chez d’autres éditeurs.

Vous n’avez plus grand chose à vous mettre sous la dent après avoir lu comment fonctionne l’optimiseur de requêtes ? On ne peut pas déclarer cela sérieusement avec la sortie récente de SQL Server 2012, et de ses nombreuses innovations ou améliorations :)

Malgré toutes ces nouveautés à décortiquer, les fondamentaux du fonctionnement de SQL Server demeurent. Donc pour Noël, Kalen Delaney nous fait le cadeau de son dernier ouvrage, SQL Server Concurrency: Locking, Blocking and Row Versioning, et que l’on peut déjà télécharger gratuitement en PDF (d’habitude RedGate ne le permet qu’après quelques mois).

Bonne lecture de Noël à tous !

Trouver et corriger les index uniques (non filtrés) sans contrainte

Exception faite des index unique filtrées, il n’y aucune différence en termes de performances entre l’ajout d’un index unique, et l’ajout d’une contrainte d’unicité (qui est elle-même supportée par un index unique).
La contrainte d’unicité permet de signifier que l’intégrité des données est le but, alors que le but premier d’un index est l’accélération des requêtes.
La majorité des index unique étant ajoutés pour renforcer l’intégrité des données, voici donc un script pour trouver tous les index unique non filtrés qui n’ont pas de contrainte d’unicité.

Lire la suite

Connaître la longueur maximale des lignes des tables, et calculs relatifs

Il est intéressant de connaître la longueur maximale qui peut être stockée par une table pour tenter de prévoir la taille d’une base de données lorsque le système sera arrivé à maturité.
Voici une petite requête pour nous y aider sous SQL Server 2005 et 2008…
Lire la suite

Une procédure stockée pour trouver sous quel compte de service s’exécute une instance SQL Server

Voici une petite procédure stockée qui affiche dans la console de SSMS le nom du compte de service sous lequel s’exécute une instance SQL Server :

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
CREATE PROCEDURE spGetSQLServerServiceAccountName
AS
BEGIN
  DECLARE @nomInstance SYSNAME,
    @nomMachine SYSNAME
   
  SELECT @nomInstance = CAST(SERVERPROPERTY('InstanceName') AS SYSNAME),
    @nomMachine = @@SERVERNAME
 
  DECLARE @cle NVARCHAR(256),
    @compteDeService NVARCHAR(128)
   
  SELECT @cle = CASE  
      WHEN @nomInstance IS NULL THEN N'SYSTEM\CurrentControlSet\Services\MSSQLServer'
      ELSE N'SYSTEM\CurrentControlSet\Services\MSSQL$' + @nomInstance
    END
 
  EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE',
       @cle,
       N'ObjectName',
       @compteDeService OUTPUT,
       N'no_output'
       
  PRINT 'Le compte de service est ' + @compteDeService
END

ElSuket

Connaître les caractéristiques d’une trace SQL Profiler

Sur un serveur à la configuration sous-dimensionnée que je surveille de temps en temps, j’ai trouvé une trace SQL Profiler en cours d’exécution.
J’ai voulu en savoir plus sur les caractéristiques de cette trace.
Voici la requête :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-------------------------------
-- Nicolas SOUQUET - 16/06/2010
-------------------------------
SELECT    C.name
    , FI.value
    , T.start_time
    , T.last_event_time
    , T.event_count
    , S.login_name
    , S.session_id
    , 'EXEC sp_trace_setstatus ' + CAST(T.id AS varchar(10)) + ', 0' AS stop_trace_stmt
FROM    sys.traces AS T
CROSS APPLY  sys.fn_trace_getfilterinfo (T.id) AS FI
INNER JOIN  sys.trace_columns AS C ON FI.columnid = C.trace_column_id
INNER JOIN  sys.dm_exec_sessions AS S ON S.session_id = T.reader_spid

On sait tout de suite quel filtres ont été utilisés à l’aide des deux premières colonnes.
La dernière colonne contient l’instruction qui permet d’arrêter la trace : le développeur a donc toujours ses résultats.

Pour savoir comment créer une trace côté serveur, c’est par ici

Bon traçage !

ElSüket