{"id":517,"date":"2012-06-12T00:00:45","date_gmt":"2012-06-11T22:00:45","guid":{"rendered":"http:\/\/www.sqlserver.fr\/blog\/?p=517"},"modified":"2026-05-02T14:31:11","modified_gmt":"2026-05-02T12:31:11","slug":"decoupage-de-gros-traitements","status":"publish","type":"post","link":"https:\/\/www.sqlserver.fr\/blog\/decoupage-de-gros-traitements\/","title":{"rendered":"D\u00e9coupage de gros traitements"},"content":{"rendered":"<p>Un point souvent remont\u00e9 lors de gros traitements d&rsquo;insertion ou de mise \u00e0 jour de donn\u00e9es r\u00e9side dans une expansion tr\u00e8s (trop) importante de l&rsquo;espace disque utilis\u00e9 par le journal de transactions de la base de donn\u00e9es.<\/p>\n<p>Nous allons voir ici comment limiter l&rsquo;expansion de ce fichier, dans le contexte du mode de restauration simple.<!--more--><\/p>\n<p>Tout d&rsquo;abord, il convient de savoir que chaque modification de donn\u00e9es dans une base SQL Server fait tout d&rsquo;abord l&rsquo;objet d&rsquo;une \u00e9criture dans le journal de transactions de la base de donn\u00e9es, et ce quelle que soit la configuration de la base de donn\u00e9es en terme de mode de r\u00e9cup\u00e9ration.<a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/06\/RecoveryModel.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-521\" title=\"RecoveryModel\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/06\/RecoveryModel.png\" alt=\"\" width=\"391\" height=\"62\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/06\/RecoveryModel.png 391w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/06\/RecoveryModel-300x47.png 300w\" sizes=\"auto, (max-width: 391px) 100vw, 391px\" \/><\/a><\/p>\n<p>Les \u00e9l\u00e9ments stock\u00e9s dans le journal de transaction sont suffisants pour faire et d\u00e9faire la modification.<\/p>\n<p>Une instruction est consid\u00e9r\u00e9e comme ex\u00e9cut\u00e9e d\u00e8s lors que les \u00e9l\u00e9ments correspondants ont \u00e9t\u00e9 \u00e9crit dans le fichier du journal de transactions. Les informations ne seront \u00e9crites dans le ou les autres fichiers de la base de donn\u00e9es (fichiers traditionnellement suffix\u00e9s MDF ou NDF) que plus tard, parfois apr\u00e8s plusieurs minutes, lors d&rsquo;op\u00e9rations appell\u00e9es Checkpoint.<\/p>\n<p>Les fichiers de journal de transactions sont donc vraiment au coeur du processus de modification des donn\u00e9es pour SQL Server.<\/p>\n<p>La modification des donn\u00e9es fonctionne toujours dans un contexte de Transactions, qui permettent de maintenir la coh\u00e9rence des donn\u00e9es. Toute modification est effectu\u00e9e via une transaction; notamment une instruction de modification qui n&rsquo;est pas encadr\u00e9e explicitement par une transaction est automatiquement encadr\u00e9e par une transaction implicite.<\/p>\n<p>L&rsquo;espace utilis\u00e9 dans le journal de transactions va de l&rsquo;ouverture \u00e0 la fin de la transaction (encadrante dans le cas de transactions imbriqu\u00e9es). Les informations stock\u00e9es dans cet espace peuvent notamment \u00eatre utilis\u00e9es si la transaction est annul\u00e9e au lieu d&rsquo;\u00eatre valid\u00e9e.<\/p>\n<p>Une fois la transaction termin\u00e9e (valid\u00e9e ou annul\u00e9e), l&rsquo;espace consomm\u00e9 dans le journal de transaction est marqu\u00e9 comme \u00ab\u00a0lib\u00e9r\u00e9 et utilisable pour un autre stockage\u00a0\u00bb si une op\u00e9ration de Checkpoint a couvert cet espace et :<\/p>\n<ul>\n<li>d\u00e8s la fin de la transaction si le mode de r\u00e9cup\u00e9ration est \u00ab\u00a0simple\u00a0\u00bb<\/li>\n<li>si une sauvegarde de journal de transaction (fichiers traditionnellement suffix\u00e9s TRN) a couvert cet espace<\/li>\n<\/ul>\n<p><span style=\"line-height: 19px;\">Cette br\u00e8ve pr\u00e9sentation \u00e9tant r\u00e9alis\u00e9e, attachons-nous maintenant \u00e0 comprendre comment limiter la croissance du journal de transaction lors d&rsquo;op\u00e9rations massives d&rsquo;insertion ou de mise \u00e0 jour de donn\u00e9es.<\/span><\/p>\n<p>Si nous sommes dans un mode de r\u00e9cup\u00e9ration \u00ab\u00a0complet\u00a0\u00bb, les donn\u00e9es seront conserv\u00e9es dans le journal tant que celui-ci n&rsquo;aura pas fait l&rsquo;objet d&rsquo;une sauvegarde. Il est donc envisageable de faire l&rsquo;insertion \u00ab\u00a0petit bout par petit bout\u00a0\u00bb, et de lancer une sauvegarde du journal entre 2 blocs. Mais ceci ne ferait que d\u00e9placer le probl\u00e8me d&rsquo;espace de stockage depuis la zone utilis\u00e9e pour le journal vers la zone utilis\u00e9e pour son backup. De plus, cela ralentirait consid\u00e9rablement la proc\u00e9dure d&rsquo;insertion. Pour ces raisons, ce proc\u00e9d\u00e9 n&rsquo;est pas utilis\u00e9 et ne sera donc pas d\u00e9taill\u00e9 ici.<\/p>\n<p>Dans la pratique, il est fr\u00e9quent de voir des bases de donn\u00e9es positionn\u00e9es en mode de r\u00e9cup\u00e9ration \u00ab\u00a0simple\u00a0\u00bb, sur lesquelles le besoin est d&rsquo;int\u00e9grer un grand nombre de donn\u00e9es tout en limitant l&rsquo;impact sur la taille du journal de transactions.<\/p>\n<p>M\u00eame si SQL Server g\u00e8re parfaitement bien les traitements massifs, il est dans ce cas d\u00e9conseill\u00e9 d&rsquo;effectuer le traitement en une instruction unique, car elle a un impact cons\u00e9quent sur la taille du journal de transactions, y compris en mode de r\u00e9cup\u00e9ration simple.<\/p>\n<p>S&rsquo;il est fonctionnellement possible de d\u00e9couper l&rsquo;insertion en plusieurs morceaux (et donc si l&rsquo;ensemble n&rsquo;a pas besoin de faire l&rsquo;objet d&rsquo;une transaction unique), l&rsquo;impact sur le journal de transaction peut en \u00eatre tr\u00e8s nettement am\u00e9lior\u00e9.<\/p>\n<p>Voici un petit exemple de d\u00e9monstration. Tout d&rsquo;abord la pr\u00e9paration des donn\u00e9es :<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">-- Cr\u00e9ation de la table source\r\nUSE [Source]\r\nGO\r\nCREATE TABLE [dbo].[TableSource](\r\n\t[Id] [int] IDENTITY(1,1) NOT NULL,\r\n\t[GUID] [uniqueidentifier] NULL,\r\n\t[Espaces] [char](200) NULL,\r\n CONSTRAINT [PK_TableSource] PRIMARY KEY CLUSTERED\r\n(\r\n\t[Id] ASC\r\n) ON [PRIMARY]\r\n) ON [PRIMARY]\r\nGO\r\nALTER TABLE [dbo].[TableSource] ADD  CONSTRAINT [DF_TableSource_GUID]  DEFAULT (newid()) FOR [GUID]\r\nGO\r\n\r\n-- Cr\u00e9ation de la table cible\r\nUSE [Cible]\r\nGO\r\nCREATE TABLE [dbo].[TableTransfert](\r\n\t[Id] [int] NOT NULL,\r\n\t[GUID] [uniqueidentifier] NULL,\r\n\t[Espaces] [char](200) NULL\r\n) ON [PRIMARY]\r\nGO\r\n\r\n-- Remplissage de la table source\r\nUSE [Source]\r\nGO\r\nset nocount off\r\ngo\r\ninsert into TableSource(Espaces) values ('')\r\ngo\r\ninsert into TableSource(Espaces) select Espaces from TableSource\r\ngo 20\r\nselect COUNT(*) from TableSource\r\n--&gt; 1048576 enregistrements<\/pre>\n<p>Lorsque l&rsquo;on utilise la copie en une seule instruction, on obtient :<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">insert into Cible.dbo.TableTransfert (Id,GUID,Espaces)\r\nselect Id,GUID,Espaces\r\nfrom Source.dbo.TableSource\r\n\r\nselect recovery_model_desc from sys.databases where database_id=DB_ID()\r\nselect type_desc,size from sys.database_files<\/pre>\n<p><a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/06\/Monobloc.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-526\" title=\"Monobloc\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/06\/Monobloc.png\" alt=\"\" width=\"162\" height=\"113\" \/><\/a><\/p>\n<p>Un petit coup de nettoyage &#8230;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">SET NOCOUNT ON\r\nGO\r\nUSE [Cible]\r\nGO\r\ntruncate table dbo.TableTransfert\r\nGO\r\nCHECKPOINT\r\nGO\r\nDBCC SHRINKFILE (N'Cible_log' , 1)\r\nDBCC SHRINKFILE (N'Cible' , 1)\r\nGO\r\nselect recovery_model_desc from sys.databases where database_id=DB_ID()\r\nselect type_desc,size from sys.database_files<\/pre>\n<p><a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/06\/Vidage.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-527\" title=\"Vidage\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/06\/Vidage.png\" alt=\"\" width=\"434\" height=\"298\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/06\/Vidage.png 434w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/06\/Vidage-300x205.png 300w\" sizes=\"auto, (max-width: 434px) 100vw, 434px\" \/><\/a><\/p>\n<p>Et on peut relancer le traitement par petits morceaux. Je pousse ici un peu \u00e0 l&rsquo;extr\u00eame, en faisant des blocs de 1000 enregistrements sur un lot de 1 million, mais c&rsquo;est juste pour la d\u00e9monstration.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">declare @Groupe int\r\n\r\ncreate table #Cles (Id int, Ordre int identity, Groupe AS (Ordre\/1000)+1 PERSISTED)\r\ncreate unique clustered index IX_Cle ON #Cles(Groupe, Ordre)\r\ninsert into #Cles (Id)\r\nselect Id\r\nfrom Source.dbo.TableSource\r\n\r\nset @Groupe=0\r\n\r\nwhile (@@ROWCOUNT&gt;0)\r\nBEGIN\r\n\tset @Groupe=@Groupe+1\r\n\r\n\tinsert into Cible.dbo.TableTransfert (Id,GUID,Espaces)\r\n\tselect ts.Id,ts.GUID,ts.Espaces\r\n\tfrom #Cles c\r\n\tjoin Source.dbo.TableSource ts on ts.Id=c.Id\r\n\twhere c.Groupe=@Groupe\r\nEND\r\n\r\ndrop table #Cles<\/pre>\n<p>Et l\u00e0, on s&rsquo;aper\u00e7oit que le fichier journal de transaction a beaucoup moins besoin de grossir&#8230;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">select type_desc,size from sys.database_files<\/pre>\n<p><a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/06\/Multiblocs.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-528\" title=\"Multiblocs\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/06\/Multiblocs.png\" alt=\"\" width=\"152\" height=\"67\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/06\/Multiblocs.png 152w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/06\/Multiblocs-150x67.png 150w\" sizes=\"auto, (max-width: 152px) 100vw, 152px\" \/><\/a><\/p>\n<p>Et lorsque l&rsquo;on cherche \u00e0 diminuer l&rsquo;expansion du journal de transaction, ce n&rsquo;est pas seulement pour des consid\u00e9rations d&rsquo;espace disque, mais aussi pour des raisons de performance. En effet, le grossissement automatique du journal a un co\u00fbt (car l&rsquo;espace disque correspondant doit \u00eatre purg\u00e9 avant utilisation par SQL Server). De plus, \u00e9tant donn\u00e9 que toute instruction de modification de donn\u00e9es passage n\u00e9cessairement par une \u00e9criture dans le journal, le fait qu&rsquo;il soit en cours d&rsquo;expansion bloque toutes les transactions, et pas seulement la transaction massive qui nous concerne ici.<\/p>\n<p>Au final, s&rsquo;il est envisageable d&rsquo;avoir une base de donn\u00e9es en mode de r\u00e9cup\u00e9ration simple et si le d\u00e9coupage est possible sans compromettre l&rsquo;int\u00e9grit\u00e9 des donn\u00e9es, il est vivement conseill\u00e9 de travailler par lots de taille contr\u00f4l\u00e9e plut\u00f4t qu&rsquo;en utilisant des traitements massifs.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Un point souvent remont\u00e9 lors de gros traitements d&rsquo;insertion ou de mise \u00e0 jour de donn\u00e9es r\u00e9side dans une expansion tr\u00e8s (trop) importante de l&rsquo;espace disque utilis\u00e9 par le journal de transactions de la base de donn\u00e9es. Nous allons voir &hellip; <a href=\"https:\/\/www.sqlserver.fr\/blog\/decoupage-de-gros-traitements\/\">Continuer la lecture <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":7,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-517","post","type-post","status-publish","format-standard","hentry","category-article_sql"],"_links":{"self":[{"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/posts\/517","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/comments?post=517"}],"version-history":[{"count":23,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/posts\/517\/revisions"}],"predecessor-version":[{"id":1952,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/posts\/517\/revisions\/1952"}],"wp:attachment":[{"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/media?parent=517"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/categories?post=517"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/tags?post=517"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}