Préambule▲
Disons-le tout de suite, cet article ne concerne que les applications développées en MS SQL Server version 7 ou 2000. En effet depuis la version 2005, MS SQL Server possède un ordre qui rend caduc cet article et qui permet de désactiver un index quel qu'il soit :
ALTER
INDEX
{ <
nom_index>
|
ALL
}
ON
<
nom_objet>
DISABLE
-- suivi au choix d'un
ALTER
INDEX
... REBUILD
CREATE
INDEX
... WITH
DROP_EXISTING
Le Test▲
Pour bien se rendre compte de l'intérêt d'une telle manœuvre, je vous propose de tester le temps d'exécution d'une insertion de données dans une table test comportant différents index. Puis de renouveler cette opération en supprimant préalablement les index puis en les remettant à la fin de l'opération. Bien entendu, dans ce dernier cas, notre mesure de temps comportera les actions de suppression et de remise en place des index…
Voici la table avec laquelle nous allons faire nos tests : la table test
CREATE
TABLE
T_TEST_INSERT_TIS
(
TIS_ID INTEGER
NOT
NULL
-- son index primaire (clef)
CONSTRAINT
PK_TIS PRIMARY
KEY
IDENTITY
,
TIS_GUID UNIQUEIDENTIFIER
DEFAULT
NEWID()
,
TIS_DATA1 VARCHAR
(
32
)
,
TIS_DATEHEURE DATETIME
,
TIS_DATA2 VARCHAR
(
64
)
,
TIS_REEL FLOAT
,
TIS_DATA3 VARCHAR
(
128
))
GO
-- les index secondaires:
-- sur un DATETIME
CREATE
INDEX
X_TIS1 ON
T_TEST_INSERT_TIS (
TIS_DATEHEURE)
GO
-- sur deux colonnes VARCHAR
CREATE
INDEX
X_TIS2 ON
T_TEST_INSERT_TIS (
TIS_DATA1, TIS_DATA2)
GO
-- sur un réel
CREATE
INDEX
X_TIS3 ON
T_TEST_INSERT_TIS (
TIS_REEL)
GO
Le fichier que nous allons intégrer dans cette table comporte 100 000 lignes et « pèse » 19 218 432 octets.
Voici les conditions du test et les résultats sur un PC avec XPpro doté de 2 Go de RAM :
SET
STATISTICS TIME
ON
BULK INSERT
T_TEST_INSERT_TIS
FROM
'C:\input.txt'
WITH
(
FIELDTERMINATOR =
';'
, ROWTERMINATOR =
'\n'
)
SET
STATISTICS TIME
OFF
Temps d'exécution de SQL Server :
Temps UC = 0 ms, temps coulé = 0 ms.
(100000 ligne(s) affectée(s))
Temps d'
analyse
et de compilation de SQL
Server
:
temps UC =
0
ms, temps écoulé =
21
ms.
Temps d'exécution de SQL Server :
Temps UC = 8063 ms, temps coulé = 32918 ms.
Temps d'
exécution de SQL
Server
:
Temps UC =
8063
ms, temps coulé =
32919
ms.
Soit 8 secondes d'utilisation de l'UC et 32 secondes en tout.
Réalisons maintenant le même test (la table a été reconstruite à l'identique) avec suppression préalable des index et remise en place desdits index :
SET
STATISTICS TIME
ON
DROP
INDEX
T_TEST_INSERT_TIS.X_TIS1
PRINT
'DROP INDEX 1 EXECUTÉ'
DROP
INDEX
T_TEST_INSERT_TIS.X_TIS2
PRINT
'DROP INDEX 2 EXECUTÉ'
DROP
INDEX
T_TEST_INSERT_TIS.X_TIS3
PRINT
'DROP INDEX 3 EXECUTÉ'
BULK INSERT
T_TEST_INSERT_TIS
FROM
'C:\input.txt'
WITH
(
FIELDTERMINATOR =
';'
)
PRINT
'BULK INSERT EXECUTÉ'
CREATE
INDEX
X_TIS1 ON
T_TEST_INSERT_TIS (
TIS_DATEHEURE)
PRINT
'CREATE INDEX 1 EXECUTÉ'
CREATE
INDEX
X_TIS2 ON
T_TEST_INSERT_TIS (
TIS_DATA1, TIS_DATA2)
PRINT
'CREATE INDEX 2 EXECUTÉ'
CREATE
INDEX
X_TIS3 ON
T_TEST_INSERT_TIS (
TIS_REEL)
PRINT
'CREATE INDEX 3 EXECUTÉ'
SET
STATISTICS TIME
OFF
Temps d'exécution de SQL Server :
Temps UC = 0 ms, temps coulé = 0 ms.
Temps d'
exécution de SQL
Server
:
Temps UC =
0
ms, temps coulé =
0
ms.
DROP
INDEX
1
EXECUTÉ
Temps d'exécution de SQL Server :
Temps UC = 0 ms, temps coulé = 0 ms.
Temps d'
exécution de SQL
Server
:
Temps UC =
0
ms, temps coulé =
0
ms.
DROP
INDEX
2
EXECUTÉ
Temps d'exécution de SQL Server :
Temps UC = 0 ms, temps coulé = 0 ms.
Temps d'
exécution de SQL
Server
:
Temps UC =
0
ms, temps coulé =
0
ms.
DROP
INDEX
3
EXECUTÉ
Temps d'exécution de SQL Server :
Temps UC = 0 ms, temps coulé = 0 ms.
(100000 ligne(s) affectée(s))
Temps d'
analyse
et de compilation de SQL
Server
:
temps UC =
0
ms, temps écoulé =
32
ms.
Temps d'exécution de SQL Server :
Temps UC = 2109 ms, temps coulé = 10151 ms.
Temps d'
exécution de SQL
Server
:
Temps UC =
2109
ms, temps coulé =
10155
ms.
BULK INSERT
EXECUTÉ
Temps d'exécution de SQL Server :
Temps UC = 0 ms, temps coulé = 0 ms.
Temps d'
analyse
et de compilation de SQL
Server
:
temps UC =
31
ms, temps écoulé =
37
ms.
Temps d'exécution de SQL Server :
Temps UC = 188 ms, temps coulé = 189 ms.
Temps d'
exécution de SQL
Server
:
Temps UC =
188
ms, temps coulé =
196
ms.
CREATE
INDEX
1
EXECUTÉ
Temps d'exécution de SQL Server :
Temps UC = 0 ms, temps coulé = 0 ms.
Temps d'
analyse
et de compilation de SQL
Server
:
temps UC =
0
ms, temps écoulé =
1
ms.
Temps d'exécution de SQL Server :
Temps UC = 1531 ms, temps coulé = 1619 ms.
Temps d'
exécution de SQL
Server
:
Temps UC =
1531
ms, temps coulé =
1634
ms.
CREATE
INDEX
2
EXECUTÉ
Temps d'exécution de SQL Server :
Temps UC = 0 ms, temps coulé = 0 ms.
Temps d'
analyse
et de compilation de SQL
Server
:
temps UC =
0
ms, temps écoulé =
2
ms.
Temps d'exécution de SQL Server :
Temps UC = 250 ms, temps coulé = 254 ms.
Temps d'
exécution de SQL
Server
:
Temps UC =
250
ms, temps coulé =
265
ms.
CREATE
INDEX
3
EXECUTÉ
Temps d'exécution de SQL Server :
Temps UC = 0 ms, temps coulé = 0 ms.
Traitement |
UC (ms) |
Coulé (ms) |
---|---|---|
Suppression des index |
0 |
0 |
Bulk Insert |
2 109 |
10 155 |
Reconstruction des index |
1 969 |
2 095 |
Total |
4 078 |
12 250 |
Nous avons donc économisé 50 % de l'UC et divisé presque par trois le temps global du traitement.
Poussons le test encore plus loin en désactivant tous les index, c'est-à-dire en retirant la clef primaire…
SET
STATISTICS TIME
ON
DROP
INDEX
T_TEST_INSERT_TIS.X_TIS1
PRINT
'DROP INDEX 1 EXECUTÉ'
DROP
INDEX
T_TEST_INSERT_TIS.X_TIS2
PRINT
'DROP INDEX 2 EXECUTÉ'
DROP
INDEX
T_TEST_INSERT_TIS.X_TIS3
PRINT
'DROP INDEX 3 EXECUTÉ'
ALTER
TABLE
T_TEST_INSERT_TIS
DROP
CONSTRAINT
PK_TIS
PRINT
'DROP PK'
BULK INSERT
T_TEST_INSERT_TIS
FROM
'C:\input.txt'
WITH
(
FIELDTERMINATOR =
';'
)
PRINT
'BULK INSERT EXECUTÉ'
ALTER
TABLE
T_TEST_INSERT_TIS
ADD
CONSTRAINT
PK_TIS PRIMARY
KEY
(
TIS_ID)
PRINT
'ADD PK'
CREATE
INDEX
X_TIS1 ON
T_TEST_INSERT_TIS (
TIS_DATEHEURE)
PRINT
'CREATE INDEX 1 EXECUTÉ'
CREATE
INDEX
X_TIS2 ON
T_TEST_INSERT_TIS (
TIS_DATA1, TIS_DATA2)
PRINT
'CREATE INDEX 2 EXECUTÉ'
CREATE
INDEX
X_TIS3 ON
T_TEST_INSERT_TIS (
TIS_REEL)
PRINT
'CREATE INDEX 3 EXECUTÉ'
SET
STATISTICS TIME
OFF
Temps d'exécution de SQL Server :
Temps UC = 0 ms, temps coulé = 0 ms.
Temps d'
exécution de SQL
Server
:
Temps UC =
0
ms, temps coulé =
0
ms.
DROP
INDEX
1
EXECUTÉ
Temps d'exécution de SQL Server :
Temps UC = 0 ms, temps coulé = 0 ms.
Temps d'
exécution de SQL
Server
:
Temps UC =
0
ms, temps coulé =
0
ms.
DROP
INDEX
2
EXECUTÉ
Temps d'exécution de SQL Server :
Temps UC = 0 ms, temps coulé = 0 ms.
Temps d'
exécution de SQL
Server
:
Temps UC =
0
ms, temps coulé =
0
ms.
DROP
INDEX
3
EXECUTÉ
Temps d'exécution de SQL Server :
Temps UC = 0 ms, temps coulé = 0 ms.
Temps d'
exécution de SQL
Server
:
Temps UC =
0
ms, temps coulé =
1
ms.
DROP
PK
Temps d'exécution de SQL Server :
Temps UC = 0 ms, temps coulé = 0 ms.
(100000 ligne(s) affectée(s))
Temps d'
analyse
et de compilation de SQL
Server
:
temps UC =
15
ms, temps écoulé =
83
ms.
Temps d'exécution de SQL Server :
Temps UC = 1938 ms, temps coulé = 2602 ms.
Temps d'
exécution de SQL
Server
:
Temps UC =
1953
ms, temps coulé =
2636
ms.
BULK INSERT
EXECUTÉ
Temps d'exécution de SQL Server :
Temps UC = 0 ms, temps coulé = 0 ms.
Temps d'
analyse
et de compilation de SQL
Server
:
temps UC =
0
ms, temps écoulé =
1
ms.
Temps d'exécution de SQL Server :
Temps UC = 219 ms, temps coulé = 801 ms.
Temps d'
exécution de SQL
Server
:
Temps UC =
219
ms, temps coulé =
855
ms.
ADD
PK
Temps d'exécution de SQL Server :
Temps UC = 0 ms, temps coulé = 4 ms.
Temps d'
analyse
et de compilation de SQL
Server
:
temps UC =
0
ms, temps écoulé =
1
ms.
Temps d'exécution de SQL Server :
Temps UC = 175 ms, temps coulé = 175 ms.
Temps d'
exécution de SQL
Server
:
Temps UC =
188
ms, temps coulé =
183
ms.
CREATE
INDEX
1
EXECUTÉ
Temps d'exécution de SQL Server :
Temps UC = 0 ms, temps coulé = 0 ms.
Temps d'
analyse
et de compilation de SQL
Server
:
temps UC =
0
ms, temps écoulé =
1
ms.
Temps d'exécution de SQL Server :
Temps UC = 1546 ms, temps coulé = 1572 ms.
Temps d'
exécution de SQL
Server
:
Temps UC =
1546
ms, temps coulé =
1582
ms.
CREATE
INDEX
2
EXECUTÉ
Temps d'exécution de SQL Server :
Temps UC = 0 ms, temps coulé = 0 ms.
Temps d'
analyse
et de compilation de SQL
Server
:
temps UC =
0
ms, temps écoulé =
2
ms.
Temps d'exécution de SQL Server :
Temps UC = 235 ms, temps coulé = 253 ms.
Temps d'
exécution de SQL
Server
:
Temps UC =
235
ms, temps coulé =
265
ms.
CREATE
INDEX
3
EXECUTÉ
Temps d'exécution de SQL Server :
Temps UC = 0 ms, temps coulé = 0 ms.
Traitement |
UC (ms) |
Coulé (ms) |
---|---|---|
Suppression des index |
0 |
10 |
Bulk Insert |
1953 |
2636 |
Reconstruction des index |
2188 |
2885 |
Total |
4 141 |
5 522 |
Si le gain est nul en termes d'UC par rapport à la solution précédente (en fait notre clef primaire de type cluster à la chance de recevoir des données prétriées…), en temps coulé l'affaire se présente mieux. Mais cette solution de suppression des contraintes n'est pas toujours réaliste en solution d'exploitation. En effet si d'aventure la table cible est utilisée concurremment par des insertions ou des modifications, la suppression de la clef primaire comme de tout index unique risque d'entraîner des incohérences de données du fait que le garde fou constitué par l'impossibilité de violer les contraintes PRIMARY KEY et UNIQUE n'est plus actif.
La conclusion de l'ensemble de ces tests est simple : toute insertion massive de données est fortement pénalisée par le recalcul des index qui s'opère pour chaque ligne. Mon expérience m'a montré que le gain en exploitation sur de grandes tables avec de multiples index pouvait aller de 2 à … plus de 10 ! D'où l'intérêt de mettre en œuvre une solution souple permettant de retirer et replacer les index sans même en connaître préalablement la composition…
La solution▲
Le principe de notre module est le suivant :
- lister les index non clef et non unique d'une table ;
- enregistrer leurs caractéristiques dans une table ;
- les supprimer ;
- effectuer un quelconque traitement ;
- reconstruire tous les index d'après leurs caractéristiques originales.
métadonnées des index▲
L'obtention des caractéristiques des index d'objets SQL Server (table, vues indexées) n'est pas une chose aisée. Il faut piocher dans les tables système sysobjects, sysusers, sysindexkey, syscolumns, dans la vue normalisée INFORMATION_SCHEMA.COLUMNS et utiliser les fonctions INDEXPROPERTY et INDEXKEY_PROPERTY.
Pour croiser ces résultats avec les clefs primaires et les contraintes d'unicité, il nous faut en outre utiliser la vue normalisée INFORMATION_SCHEMA.TABLE_CONSTRAINTS.
C'est ainsi que j'ai créé la vue V_A_INDEX_DETAILS_IXD qui donne suffisamment d'informations sur les structures d'index :
CREATE
VIEW
dbo.V_A_INDEX_DETAILS_IXD
AS
SELECT
DISTINCT
TOP 100
PERCENT WITH
TIES
u.name
AS
IXD_SCHEMA_NAME,
o.name
AS
IXD_TABLE_NAME,
i.name
AS
IXD_INDEX_NAME,
CONSTRAINT_TYPE AS
IXD_CONSTRAINT_TYPE,
CASE
WHEN
i.indid =
0
THEN
'TABLE'
WHEN
i.indid =
1
THEN
'CLUSTER'
WHEN
i.indid BETWEEN
2
AND
254
THEN
'HEAP'
WHEN
i.indid =
255
THEN
'TXTEIMAGE'
END
AS
IXD_INDEX_TYPE,
INDEXPROPERTY(
o.id, i.name
, 'IsUnique'
)
AS
IXD_IS_UNIQUE,
INDEXPROPERTY(
o.id, i.name
, 'IndexFillFactor'
)
AS
IXD_FILL_FACTOR,
c.name
AS
IXD_COL_NAME,
DATA_TYPE +
'('
+
CAST
(
COALESCE
(
CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION)
AS
VARCHAR
(
16
))
+
COALESCE
(
', '
+
CAST
(
NULLIF
(
NUMERIC_SCALE, 0
)
AS
VARCHAR
(
16
))
, ''
)
+
')'
AS
IXD_COL_TYPE,
k.keyno AS
IXD_COL_IDX_ORDER,
CASE
WHEN
INDEXKEY_PROPERTY (
o.id , i.indid , k.keyno, 'IsDescending'
)
=
0
THEN
'ASC'
WHEN
INDEXKEY_PROPERTY (
o.id , i.indid , k.keyno, 'IsDescending'
)
=
1
THEN
'DESC'
WHEN
INDEXKEY_PROPERTY (
o.id , i.indid , k.keyno, 'IsDescending'
)
IS
NULL
THEN
''
END
AS
IXD_COL_DATA_ORDER,
INDEXPROPERTY(
o.id, i.name
, 'IsRowLockDisallowed'
)
AS
IXD_ROW_LOCK_DISALLOWED,
INDEXPROPERTY(
o.id, i.name
, 'IsPageLockDisallowed'
)
AS
IXD_PAGE_LOCK_DISALLOWED
FROM
dbo.sysindexes i
INNER
JOIN
dbo.sysobjects o
ON
i.id =
o.id
INNER
JOIN
dbo.sysusers u
ON
o.uid
=
u.uid
INNER
JOIN
dbo.sysindexkeys k
ON
o.id =
k.id
and
i.indid =
k.indid
INNER
JOIN
dbo.syscolumns c
ON
k.colid =
c.colid
and
o.id =
c.id
INNER
JOIN
INFORMATION_SCHEMA.COLUMNS
ISC
ON
u.name
=
ISC.TABLE_SCHEMA
AND
o.name
=
ISC.TABLE_NAME
AND
c.name
=
ISC.COLUMN_NAME
LEFT
OUTER
JOIN
INFORMATION_SCHEMA.TABLE_CONSTRAINTS TCT
ON
u.name
=
TCT.CONSTRAINT_SCHEMA
AND
i.name
=
TCT.CONSTRAINT_NAME
WHERE
i.status
&
amp; 64
<>
64
ORDER
BY
IXD_SCHEMA_NAME, IXD_TABLE_NAME, IXD_INDEX_NAME, IXD_COL_IDX_ORDER
GO
Stockage des informations sur les index▲
Deux tables vont permettre de stocker, l'une, les informations sur les index, l'autre, quel a été le demandeur de la manœuvre :
-- table mère du processsus
CREATE
TABLE
dbo.T_A_MAINTENANCE_PROCESS_MPC
(
MPC_ID INT
NOT
NULL
IDENTITY
CONSTRAINT
PK_MPC PRIMARY
KEY
,
MPC_ID_PRECEDENT INT
NULL
FOREIGN
KEY
REFERENCES
T_A_MAINTENANCE_PROCESS_MPC (
MPC_ID)
,
MPC_SQL_USER sysname DEFAULT
CURRENT_USER
,
MPC_SYS_USER sysname DEFAULT
SYSTEM_USER
,
MPC_SPID INT
,
MPC_DATE_TIME DATETIME
DEFAULT
CURRENT_TIMESTAMP
,
MPC_HOST_NAME NCHAR
(
128
)
,
MPC_APPLI NCHAR
(
128
)
,
MPC_NETNAME NCHAR
(
128
)
,
MPC_NETADR NCHAR
(
12
)
,
MPC_PROCESS VARCHAR
(
512
)
,
MPC_SUCCESS BIT
DEFAULT
0
)
GO
-- table fille de détail du processus lié à la désindexation/réindexation
CREATE
TABLE
dbo.T_A_MAINTENANCE_INDEX_MIX
(
MIX_ID INT
NOT
NULL
IDENTITY
CONSTRAINT
PK_MIX PRIMARY
KEY
,
MPC_ID INT
NOT
NULL
FOREIGN
KEY
REFERENCES
T_A_MAINTENANCE_PROCESS_MPC (
MPC_ID)
,
MIX_DATE_TIME DATETIME
DEFAULT
CURRENT_TIMESTAMP
,
MIX_OPERATION CHAR
(
5
)
CONSTRAINT
CK_MIX_OPR
CHECK
(
MIX_OPERATION IN
(
'DROP'
, 'RESET'
))
,
MIX_SCHEMA_NAME sysname,
MIX_TABLE_NAME sysname,
MIX_INDEX_NAME sysname,
MIX_UNIQUE BIT
DEFAULT
0
,
MIX_CLUSTER BIT
DEFAULT
0
,
MIX_COL_ORDER_LIST VARCHAR
(
2160
)
,
MIX_INDEX_WITH VARCHAR
(
200
)
,
MIX_NO_ROW_LOCK BIT
DEFAULT
0
,
MIX_NO_PAGE_LOCK BIT
DEFAULT
0
,
MIX_ON_FILE sysname NULL
,
MIX_COMPLETE BIT
DEFAULT
0
)
GO
La première table enregistre la demande de suppression des index. La seconde sert à décrire les index supprimés, donc ceux qui vont devoir être remis en place.
Pour alimenter ces tables, et opérer la manœuvre, deux procédures stockées sont créées. L'une P_A_MAINTENANCE_DROP_INDEXES retire les index d'une table appartenant à un schéma et au passage, notons que l'on a prévu de pouvoir recréer les index sur un nouveau groupe de fichiers si le besoin s'en fait sentir. L'autre P_MAINTENANCE_RESET_INDEXES les remet en place simplement en lui donnant en paramètre l'identifiant généré par la précédente, identifiant qui est en fait l'entrée (clef) de la table T_A_MAINTENANCE_PROCESS_MPC.
La procédure P_A_MAINTENANCE_DROP_INDEXES, utilise en outre une fonction SQL de nom F_A_INDEX_LIST_COL_ORDER qui permet d'obtenir la liste ordinale des colonnes composant l'index et le sens d'indexation (ASC ou DESC).
Voici le code Transact SQL de ces trois routines :
CREATE
FUNCTION
F_A_INDEX_LIST_COL_ORDER
(
@SCHEMA_NAME
sysname,
@TABLE_NAME
sysname,
@INDEX_NAME sysname)
RETURNS
VARCHAR
(
2160
)
AS
BEGIN
DECLARE
@RETVAL VARCHAR
(
2160
)
SET
@RETVAL =
''
SELECT
@RETVAL =
@RETVAL +
c.name
+
' '
+
CASE
WHEN
INDEXKEY_PROPERTY
(
o.id , i.indid , k.keyno , 'IsDescending'
)
=
0
THEN
'ASC'
WHEN
INDEXKEY_PROPERTY
(
o.id , i.indid , k.keyno , 'IsDescending'
)
=
1
THEN
'DESC'
WHEN
INDEXKEY_PROPERTY
(
o.id , i.indid , k.keyno , 'IsDescending'
)
IS
NULL
THEN
''
END
+
', '
FROM
sysindexes i
INNER
JOIN
sysobjects o
ON
i.id =
o.id
INNER
JOIN
sysusers u
ON
o.uid
=
u.uid
INNER
JOIN
sysindexkeys k
ON
o.id =
k.id
and
i.indid =
k.indid
INNER
JOIN
syscolumns c
ON
k.colid =
c.colid
and
o.id =
c.id
INNER
JOIN
INFORMATION_SCHEMA.COLUMNS
ISC
ON
u.name
=
ISC.TABLE_SCHEMA
AND
o.name
=
ISC.TABLE_NAME
AND
c.name
=
ISC.COLUMN_NAME
WHERE
u.name
=
@SCHEMA_NAME
AND
o.name
=
@TABLE_NAME
AND
i.name
=
@INDEX_NAME
ORDER
BY
k.keyno
IF
LEN(
@RETVAL)
>=
2
SET
@RETVAL =
SUBSTRING
(
@RETVAL, 1
, LEN(
@RETVAL)
-
1
)
RETURN
@RETVAL
END
GO
CREATE
PROCEDURE
dbo.P_A_MAINTENANCE_DROP_INDEXES
@SCHEMA_NAME
sysname =
'dbo'
,
@TABLE_NAME
sysname,
@CREATE_ON sysname =
'PRIMARY'
,
@MPC_ID INT
=
NULL
OUTPUT
AS
DECLARE
@ROWS
INT
, @ERROR
INT
SET
@MPC_ID =
NULL
-- RETURN NULL ON NULL INPUT (norme SQL)
IF
@TABLE_NAME
IS
NULL
OR
@CREATE_ON IS
NULL
OR
@SCHEMA_NAME
IS
NULL
BEGIN
RAISERROR (
'Entrée NULL détectée dans au moins un des paramètres de la procédure
P_A_MAINTENANCE_DROP_INDEXES. Abandon immédiat'
, 16
, 1
)
RETURN
END
-- schéma.table n'existe pas dans le catalogue présent
IF
NOT
EXISTS
(
SELECT
*
FROM
INFORMATION_SCHEMA.TABLES
WHERE
TABLE_SCHEMA =
@SCHEMA_NAME
AND
TABLE_NAME
=
@TABLE_NAME
)
BEGIN
RAISERROR (
'Le couple schéma.table : %s.%s,
n''existe pas dans le catalogue courant'
, 16
, 1
, @SCHEMA_NAME
, @TABLE_NAME
)
RETURN
END
-- vérification file group
IF
@CREATE_ON <>
'PRIMARY'
IF
NOT
EXISTS
(
SELECT
*
FROM
sysfilegroups
WHERE
groupname =
@CREATE_ON)
BEGIN
RAISERROR (
'Le groupe de fichier %s, n''existe pas dans la liste des groupe
de fichiers pour cette base de données'
, 16
, 1
, @CREATE_ON)
RETURN
END
-- les tables de maintenance existent-elles dans la base ?
DECLARE
@T1 sysname, @T2 sysname, @SC sysname
SET
@T1 =
'T_A_MAINTENANCE_INDEX_MIX'
SET
@T2 =
'T_A_MAINTENANCE_PROCESS_MPC'
SET
@SC =
'dbo'
IF
NOT
EXISTS
(
SELECT
*
FROM
INFORMATION_SCHEMA.TABLES
WHERE
TABLE_SCHEMA =
@SC
AND
TABLE_NAME
=
@T1
AND
EXISTS
(
SELECT
*
FROM
INFORMATION_SCHEMA.TABLES
WHERE
TABLE_SCHEMA =
@SC
AND
TABLE_NAME
=
@T2))
BEGIN
RAISERROR (
'Au moins l''une des tables de travail (%s.%s, %s.%s)_
n''existe pas dans la liste objets du
catalogue'
, 16
, 1
, @SC, @T1, @SC, @T2)
RETURN
END
BEGIN
TRANSACTION
-- insertion des informations de maintenance
INSERT
INTO
dbo.T_A_MAINTENANCE_PROCESS_MPC (
MPC_SPID, MPC_HOST_NAME, MPC_APPLI,
MPC_NETNAME, MPC_NETADR, MPC_PROCESS )
SELECT
spid, hostname, program_name, nt_username, net_address,
'Suppression des index non cluster et non unique
(dont clef) table '
+
@SCHEMA_NAME
+
'.'
+
@TABLE_NAME
FROM
master
.dbo.sysprocesses p
WHERE
p.spid =
@@SPID
IF
@@ERROR
<>
0
GOTO
LBL_ERROR
-- reprise de l'indentifiant autogénéré pour report dans table fille
SET
@MPC_ID =
@@IDENTITY
-- insertion des informations sur les index
INSERT
INTO
dbo.T_A_MAINTENANCE_INDEX_MIX (
MPC_ID, MIX_OPERATION,
MIX_SCHEMA_NAME, MIX_TABLE_NAME, MIX_INDEX_NAME,_
MIX_UNIQUE, MIX_CLUSTER, MIX_INDEX_WITH, MIX_COL_ORDER_LIST,
MIX_NO_ROW_LOCK, MIX_NO_PAGE_LOCK, MIX_ON_FILE)
SELECT
@MPC_ID, 'DROP'
, @SCHEMA_NAME
, @TABLE_NAME
, i.name
,
INDEXPROPERTY(
o.id, i.name
, 'IsUnique'
)
,
INDEXPROPERTY(
o.id, i.name
, 'IsClustered'
)
,
-- propriété PAD
CASE
WHEN
INDEXPROPERTY(
o.id, i.name
, 'IsPadIndex'
)
=
1
THEN
' PAD_INDEX '
ELSE
''
END
+
-- propriété FILL FACTOR
CASE
WHEN
INDEXPROPERTY(
o.id, i.name
, 'IndexFillFactor'
)
>
0
THEN
'FILLFACTOR = '
+
CAST
(
INDEXPROPERTY
(
o.id, i.name
, 'IndexFillFactor'
)
AS
VARCHAR
(
3
))
ELSE
NULL
END
,
dbo.F_A_INDEX_LIST_COL_ORDER (
@SCHEMA_NAME
, @TABLE_NAME
, i.name
)
,
INDEXPROPERTY(
o.id, i.name
, 'IsRowLockDisallowed'
)
,
INDEXPROPERTY(
o.id, i.name
, 'IsPageLockDisallowed'
)
,
NULLIF
(
@CREATE_ON, 'PRIMARY'
)
FROM
dbo.sysindexes i
INNER
JOIN
dbo.sysobjects o
ON
i.id =
o.id
INNER
JOIN
dbo.sysusers u
ON
o.uid
=
u.uid
WHERE
i.status
&
amp; 64
<>
64
-- sauf les index "stat"
AND
u.name
=
@SCHEMA_NAME
AND
o.name
=
@TABLE_NAME
AND
INDEXPROPERTY(
o.id, i.name
, 'IsUnique'
)
<>
1
AND
INDEXPROPERTY(
o.id, i.name
, 'IsClustered'
)
<>
1
SELECT
@ERROR
=
@@ERROR
, @ROWS
=
@@ROWCOUNT
IF
@ERROR
<>
0
GOTO
LBL_ERROR
-- aucun index secondaire n'est présent dans la table
IF
@ROWS
=
0
BEGIN
SET
@MPC_ID =
NULL
GOTO
LBL_ERROR
RETURN
END
-- suppression des index
DECLARE
@SQL
VARCHAR
(
8000
)
SET
@SQL
=
'DROP INDEX'
SELECT
@SQL
=
@SQL
+
' '
+
MIX_SCHEMA_NAME +
'.'
+
MIX_TABLE_NAME +
'.'
+
MIX_INDEX_NAME +
','
FROM
dbo.T_A_MAINTENANCE_INDEX_MIX
WHERE
MPC_ID =
@MPC_ID
AND
MIX_UNIQUE =
0
AND
MIX_CLUSTER =
0
IF
@@ERROR
<>
0
GOTO
LBL_ERROR
SET
@SQL
=
SUBSTRING
(
@SQL
, 1
, LEN(
@SQL
)
-
1
)
EXEC
(
@SQL
)
IF
@@ERROR
<>
0
GOTO
LBL_ERROR
UPDATE
dbo.T_A_MAINTENANCE_INDEX_MIX
SET
MIX_DATE_TIME =
CURRENT_TIMESTAMP
,
MIX_COMPLETE =
1
WHERE
MPC_ID =
@MPC_ID
UPDATE
dbo.T_A_MAINTENANCE_PROCESS_MPC
SET
MPC_SUCCESS =
1
WHERE
MPC_ID =
@MPC_ID
COMMIT
TRANSACTION
RETURN
LBL_ERROR:
ROLLBACK
TRANSACTION
RAISERROR(
'Maintenance d''index : opération inachevée. Veuillez prendre
connaissance des informations des tables dbo.T_A_MAINTENANCE_PROCESS_MPC (et
dbo.T_A_MAINTENANCE_INDEX_MIX) pour les entrées
d''identifiant %i.'
, 18
, 1
, @MPC_ID)
WITH
LOG
GO
CREATE
PROCEDURE
P_MAINTENANCE_RESET_INDEXES
@MPC_ID INTEGER
AS
-- RETURN NULL ON NULL INPUT (norme SQL)
IF
@MPC_ID IS
NULL
BEGIN
RAISERROR (
'Entrée NULL détectée dans au moins un des paramètres de la
procédure P_MAINTENANCE_RESET_INDEXES. Abandon immédiat'
, 16
, 1
)
RETURN
END
-- les tables de maintenance existent-elles dans la base ?
DECLARE
@T1 sysname, @T2 sysname, @SC sysname
SET
@T1 =
'T_A_MAINTENANCE_INDEX_MIX'
SET
@T2 =
'T_A_MAINTENANCE_PROCESS_MPC'
SET
@SC =
'dbo'
IF
NOT
EXISTS
(
SELECT
*
FROM
INFORMATION_SCHEMA.TABLES
WHERE
TABLE_SCHEMA =
@SC
AND
TABLE_NAME
=
@T1
AND
EXISTS
(
SELECT
*
FROM
INFORMATION_SCHEMA.TABLES
WHERE
TABLE_SCHEMA =
@SC
AND
TABLE_NAME
=
@T2))
BEGIN
RAISERROR (
'Au moins l''une des tables de travail (%s.%s, %s.%s) n''existe pas
dans la liste objets du catalogue'
, 16
, 1
, @SC, @T1, @SC, @T2)
RETURN
END
-- pas d'entrée dans la table T_A_MAINTENANCE_PROCESS_MPC
IF
NOT
EXISTS
(
SELECT
*
FROM
dbo.T_A_MAINTENANCE_PROCESS_MPC
WHERE
MPC_ID =
@MPC_ID)
BEGIN
RAISERROR (
'pas d''entrée pour la clef de valeur %i dans la
table T_A_MAINTENANCE_PROCESS_MPC. Abandon immédiat'
, 16
, 1
, @MPC_ID)
RETURN
END
-- pas d'entrée dans la table T_A_MAINTENANCE_INDEX_MIX
IF
NOT
EXISTS
(
SELECT
*
FROM
dbo.T_A_MAINTENANCE_INDEX_MIX
WHERE
MPC_ID =
@MPC_ID)
BEGIN
RAISERROR (
'pas d''entrée pour la clef étrangère de valeur %i
dans la table T_A_MAINTENANCE_INDEX_MIX. Abandon immédiat'
, 16
, 1
, @MPC_ID)
RETURN
END
-- informer la table dbo.T_A_MAINTENANCE_PROCESS_MPC avec chainage
DECLARE
@MPC_ID_NEW INT
INSERT
INTO
dbo.T_A_MAINTENANCE_PROCESS_MPC
(
MPC_ID_PRECEDENT, MPC.MPC_SPID, MPC_HOST_NAME, MPC_APPLI,
MPC_NETNAME, MPC_NETADR, MPC_PROCESS )
SELECT
DISTINCT
MPC.MPC_ID, spid, hostname, program_name,
nt_username, net_address, 'Réindexation de la table '
+
MIX_SCHEMA_NAME+
'.'
+
MIX_TABLE_NAME
FROM
dbo.T_A_MAINTENANCE_PROCESS_MPC MPC
INNER
JOIN
dbo.T_A_MAINTENANCE_INDEX_MIX MIX
ON
MPC.MPC_ID =
MIX.MPC_ID
CROSS
JOIN
master
.dbo.sysprocesses s
WHERE
s.spid =
@@SPID
AND
MPC.MPC_ID =
@MPC_ID
IF
@@ERROR
<>
0
GOTO
LBL_ERROR
SET
@MPC_ID_NEW =
@@IDENTITY
DECLARE
@SQL
NVARCHAR
(
4000
)
DECLARE
@MIX_ID INT
DECLARE
@NO_PG_LCK BIT
, @NO_RW_LCK BIT
DECLARE
@IDX_COMPOSITE_NAME NVARCHAR
(
257
)
DECLARE
@I INT
SET
@I =
256
-- au travail, sans curseur
WHILE
EXISTS
(
SELECT
*
FROM
dbo.T_A_MAINTENANCE_INDEX_MIX
WHERE
MPC_ID =
@MPC_ID
AND
MIX_OPERATION =
'DROP'
)
BEGIN
-- sélection du premier index à remettre
SELECT
TOP 1
@MIX_ID =
MIX_ID, @NO_PG_LCK =
MIX_NO_PAGE_LOCK , @NO_RW_LCK =
MIX_NO_ROW_LOCK ,
@IDX_COMPOSITE_NAME =
'['
+
MIX_TABLE_NAME +
'].['
+
MIX_INDEX_NAME+
']'
,
@SQL
=
'CREATE NONCLUSTERED INDEX '
+
MIX_INDEX_NAME +
' ON ['
+
MIX_SCHEMA_NAME +
'].['
+
MIX_TABLE_NAME+
'] ('
+
MIX_COL_ORDER_LIST +
') '
+
COALESCE
(
' WITH '
+
MIX_INDEX_WITH +
' '
, ''
)
+
' ON '
+
COALESCE
(
MIX_ON_FILE, '[PRIMARY]'
)
FROM
dbo.T_A_MAINTENANCE_INDEX_MIX
WHERE
MPC_ID =
@MPC_ID
AND
MIX_OPERATION =
'DROP'
-- exécution de la réindexation
EXEC
(
@SQL
)
IF
@@ERROR
=
0
-- succès on informe avec complète = 1
UPDATE
dbo.T_A_MAINTENANCE_INDEX_MIX
SET
MIX_OPERATION =
'RESET'
,
MIX_COMPLETE =
1
,
MIX_DATE_TIME =
CURRENT_TIMESTAMP
WHERE
MIX_ID =
@MIX_ID
ELSE
-- échec on informe avec complète = 0
UPDATE
dbo.T_A_MAINTENANCE_INDEX_MIX
SET
MIX_OPERATION =
'RESET'
,
MIX_COMPLETE =
0
,
MIX_DATE_TIME =
CURRENT_TIMESTAMP
WHERE
MIX_ID =
@MIX_ID
-- si @NO_PG_LCK = 1 replacer l'interdiction d'utiliser le verrou de page dans l'index
IF
@NO_PG_LCK =
1
EXEC
sp_indexoption @IDX_COMPOSITE_NAME, 'DisAllowPageLocks'
, '1'
-- si @NO_RW_LCK = 1 replacer l'interdiction d'utiliser le verrou de ligne dans l'index
IF
@NO_RW_LCK =
1
EXEC
sp_indexoption @IDX_COMPOSITE_NAME, 'DisAllowRowLocks'
, '1'
-- a ton bouclé indéfiniment ? (max 256 index)
SET
@I =
@I -
1
IF
@I =
0
BREAK
END
-- tous les index ont-ils été recréés ?
IF
EXISTS
(
SELECT
*
FROM
dbo.T_A_MAINTENANCE_INDEX_MIX
WHERE
MPC_ID =
@MPC_ID
AND
MIX_COMPLETE =
0
)
-- non : il en existe au moins 1 qui n'a pas été remis
GOTO
LBL_ERROR
-- oui : on met les pendules à l'heure dans la table dbo.T_A_MAINTENANCE_PROCESS_MPC
UPDATE
dbo.T_A_MAINTENANCE_PROCESS_MPC
SET
MPC_SUCCESS =
1
WHERE
MPC_ID =
@MPC_ID_NEW
RETURN
LBL_ERROR:
RAISERROR(
'Maintenance d''index : opération inachevée.
Veuillez prendre connaissance des informations des tables
dbo.T_A_MAINTENANCE_PROCESS_MPC (et dbo.T_A_MAINTENANCE_INDEX_MIX) pour
les entrées d''identifiant %i et %i'
, 18
, 1
, @MPC_ID, @MPC_ID_NEW)
WITH
LOG
GO
Utilisation▲
Dans notre exemple test, cet outil s'utilise comme suit :
DECLARE
@MPCID INT
EXEC
dbo.P_A_MAINTENANCE_DROP_INDEXES
@SCHEMA_NAME
=
'dbo'
,
@TABLE_NAME
=
'T_TEST_INSERT_TIS'
,
@CREATE_ON =
'PRIMARY'
,
@MPC_ID =
@MPCID output
BULK INSERT
T_TEST_INSERT_TIS
FROM
'C:\input.txt'
WITH
(
FIELDTERMINATOR =
';'
, ROWTERMINATOR =
'\n'
)
EXEC
dbo.P_MAINTENANCE_RESET_INDEXES @MPCID
Traitement |
UC (ms) |
Coulé (ms) |
---|---|---|
Suppression des index |
0 |
7 |
Bulk Insert |
2 125 |
8881 |
Reconstruction des index |
2000 |
2378 |
Total |
4 125 |
11 259 |
On constate que le temps rajouté par ce traitement générique de collecte des informations d'index, et traitement générique est parfaitement négligeable.
De plus amples informations vous sont nécessaires ?▲
Venez en discuter sur le forum public français de Microsoft SQL Server :
news:msnews.microsoft.com/microsoft.public.fr.sqlserver
Ou sur le forum SQL Server de developpez.com :
https://www.developpez.net/forums/forumdisplay.php?f=49