<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>David Barbarin &#187; SQL</title>
	<atom:link href="https://blog.developpez.com/mikedavem/pcategory/sql-server-2005/sql/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.developpez.com/mikedavem</link>
	<description>MVP DataPlatform - MCM SQL Server</description>
	<lastBuildDate>Thu, 09 Sep 2021 21:19:50 +0000</lastBuildDate>
	<language>fr-FR</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.1.42</generator>
	<item>
		<title>Fragmentation des indexes et fragments : quesako ?</title>
		<link>https://blog.developpez.com/mikedavem/p10152/sql-server-2005/architecture/fragmentation_des_indexes_et_fragments_q</link>
		<comments>https://blog.developpez.com/mikedavem/p10152/sql-server-2005/architecture/fragmentation_des_indexes_et_fragments_q#comments</comments>
		<pubDate>Mon, 25 Jul 2011 20:56:50 +0000</pubDate>
		<dc:creator><![CDATA[mikedavem]]></dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[Pour ceux qui utilisent la DMV sys.dm_db_index_physical_stats depuis la version 2005 de SQL Server ont certainement vu une colonne nommée fragment_count. La documentation Microsoft nous donne la description suivante : Nombre de fragments dans le niveau feuille d&#8217;une unité d&#8217;allocation &#8230; <a href="https://blog.developpez.com/mikedavem/p10152/sql-server-2005/architecture/fragmentation_des_indexes_et_fragments_q">Lire la suite <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p><font size="2">Pour ceux qui utilisent la DMV <em>sys.dm_db_index_physical_stats</em> depuis la version 2005 de SQL Server ont certainement vu une colonne nommée fragment_count. La documentation Microsoft nous donne la description suivante : <em>Nombre de fragments dans le niveau feuille d&rsquo;une unité d&rsquo;allocation IN_ROW_DATA. </em>J&rsquo;ai déjà eu pas mal de questions à ce sujet car même avec la description fournie nous pouvons avoir du mal à visualiser ce que cette colonne représente exactement et quelle peut être la relation avec la fragmentation des indexes.</font></p>
<p><span id="more-13"></span></p>
<p><font size="2">Tout d&rsquo;abord une définition :  un fragment est en réalité une séquence de pages physique contigüe. Un index possède au minimum un fragment et il peut y avoir autant de fragments que de pages dans le pire des scénarios. Pourquoi le pire des scénarios ? Nous le découvrirons un peu plus tard mais pour le moment nous prendrons l&rsquo;exemple suivant :</font></p>
<blockquote><p><strong><font size="2">CREATE TABLE T          <br />(           <br />id INT IDENTITY(1,1),           <br />texte VARCHAR(100)           <br />); </font></strong></p>
<p><strong><font size="2">&#8211; Index cluster          <br />CREATE UNIQUE CLUSTERED INDEX PK_id           <br />ON T           <br />(           <br />id           <br />); </font></strong></p>
<p><strong><font size="2">INSERT T (texte) VALUES (REPLICATE(&lsquo;T&rsquo;, 100))          <br />GO 100</font></strong></p>
</blockquote>
<p><font size="2">Nous avons donc une table relativement simple avec un index cluster. On remplit la table avec 100 lignes de données ce qui représente environ 10Ko à la louche &#8230; autant dire que notre index ne sera composé que de quelques pages. Vérifions le :</font></p>
<blockquote><p><strong><font size="2">SELECT          <br />OBJECT_NAME(object_id) AS [object_name],           <br />index_id,           <br />avg_fragmentation_in_percent,           <br />avg_page_space_used_in_percent,           <br />avg_fragment_size_in_pages AS page_per_fragment,           <br />fragment_count,           <br />page_count,           <br />record_count           <br />FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID(&lsquo;T&rsquo;), NULL, NULL, &lsquo;DETAILED&rsquo;);</font></strong></p>
</blockquote>
<p><font size="2">&#8230; qui donne </font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/Fragmentationdesindexesettablesfaiblevol_91CC/image_2.png"><font size="2"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://blog.developpez.com/media/269/WindowsLiveWriter/Fragmentationdesindexesettablesfaiblevol_91CC/image_thumb.png" width="772" height="51" /></font></a><font size="2"> </font></p>
<p><font size="2">Effectivement nous avons trois pages dont une page racine et deux pages de niveau feuille qui composent l&rsquo;index cluster (index_id = 1). J&rsquo;ai également ajouté les colonnes <em>fragment_count</em> et <em>avg_fragment_size_in_pages</em>. On constate que pour le niveau feuille il existe deux fragments pour deux pages et que la fragmentation de l&rsquo;index est de 50%. Si deux fragments existent cela signifie que les pages composants le niveau feuille ne se suivent pas physiquement. On peut le vérifier avec la commande DBCC IND. Ici nous nous intéresserons uniquement qu&rsquo;au niveau feuille de l&rsquo;index.</font></p>
<blockquote><p><strong><font size="2">CREATE TABLE T_DBCC_IND          <br />(           <br />ID INT IDENTITY(1,1) PRIMARY KEY,           <br />PageFID TINYINT,           <br />PagePID INT,           <br />IAMFID TINYINT,           <br />IAMPID INT,           <br />ObjectID INT,           <br />IndexID TINYINT,           <br />PartitionNumber TINYINT,           <br />PartitionID BIGINT,           <br />iam_chain_type VARCHAR(30),           <br />PageType TINYINT,           <br />IndexLevel TINYINT,           <br />NextPageFID TINYINT,           <br />NextPagePID INT,           <br />PrevPageFID TINYINT,           <br />PrevPagePID INT           <br />);           <br />INSERT INTO T_DBCC_IND           <br />EXEC (&lsquo;DBCC IND (test, T, 1)&rsquo;);  </font></strong></p>
<p><strong><font size="2">SELECT *          <br />FROM T_DBCC_IND           <br />WHERE IndexLevel IS NOT NULL;</font></strong></p>
</blockquote>
<p><font size="2">&#8230; qui donne le plan d&rsquo;allocation de pages suivant :</font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/Fragmentationdesindexesettablesfaiblevol_91CC/image_8.png"><font size="2"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://blog.developpez.com/media/269/WindowsLiveWriter/Fragmentationdesindexesettablesfaiblevol_91CC/image_thumb_3.png" width="968" height="77" /></font></a><font size="2"> </font></p>
<p><font size="2">On constate effectivement que les numéros de pages ne se suivent pas. On peut le représenter schématiquement de la manière suivante :</font></p>
<p><font size="2"> </font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/Fragmentationdesindexesettablesfaiblevol_91CC/image_10.png"><font size="2"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://blog.developpez.com/media/269/WindowsLiveWriter/Fragmentationdesindexesettablesfaiblevol_91CC/image_thumb_4.png" width="438" height="290" /></font></a><font size="2"> </font></p>
<p><font size="2"></font></p>
<p><font size="2"></font></p>
<p><font size="2"></font></p>
<p><font size="2">Quelle est le lien avec la fragmentation ? Définissons d&rsquo;abord ce qu&rsquo;est la fragmentation. La documentation en ligne donne l&rsquo;explication suivante concernant la fragmentation logique : </font></p>
<blockquote><p><font size="2"><em>Pourcentage de pages hors service dans les pages de feuilles d&rsquo;un index.</em> <em>Une page non ordonnée est une page pour laquelle la page physique suivante allouée à l&rsquo;index n&rsquo;est pas la page désignée par le pointeur de page suivante dans la page feuille actuelle</em>. </font></p>
</blockquote>
<p><font size="2">Autant dire que cette explication reste plutôt abscons &#8230; Je préfère la définition suivante : </font></p>
<blockquote><p><font size="2">La fragmentation logique représente le nombre de pages dont la page suivante annoncée dans la page IAM (donc le plan d&rsquo;allocation) ne se trouve pas dans la même portion de fragment.</font></p>
</blockquote>
<p><font size="2">Nous sommes bien dans ce cas car la page 201 ne se trouve pas dans le même fragment que la page 206. Pour aller de la page 201 à 206 nous devons passer sur un fragment différent pour un lire un total de deux pages. Nous pouvons en déduire la formule suivante :</font></p>
<blockquote><p><strong><font size="2">Fragmentation = (Nb de fragments &#8211; 1) / Nb de pages </font></strong></p>
</blockquote>
<p><font size="2">Dans notre cas nous aurons :</font></p>
<blockquote><p><font size="2">Fragmentation = (2 &#8211; 1) / 2 = 50%</font></p>
</blockquote>
<p><font size="2">On peut vérifier notre raisonnement en ajoutant un jeu de données supplémentaire :</font></p>
<blockquote><p><strong><font size="2">INSERT T (texte) VALUES (REPLICATE(&lsquo;T&rsquo;, 100))          <br />GO 1000</font></strong></p>
</blockquote>
<p><font size="2">En utilisant la DMV sys.dm_db_index_physical_stats nous pouvons constater que le nombre de pages à bien évidement augmenté. </font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/Fragmentationdesindexesettablesfaiblevol_91CC/image_12.png"><font size="2"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://blog.developpez.com/media/269/WindowsLiveWriter/Fragmentationdesindexesettablesfaiblevol_91CC/image_thumb_5.png" width="848" height="60" /></font></a><font size="2"> </font></p>
<p><font size="2">Le nombre de fragments est maintenant égale à trois avec 16 pages pour le niveau feuille. En utilisant la formule ci-dessus la fragmentation est de (3 &#8211; 1) / 16 soit 12.5% .. On peut vérifier par la commande DBCC IND le nombre de fragments :</font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/Fragmentationdesindexesettablesfaiblevol_91CC/image_14.png"><font size="2"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://blog.developpez.com/media/269/WindowsLiveWriter/Fragmentationdesindexesettablesfaiblevol_91CC/image_thumb_6.png" width="844" height="296" /></font></a><font size="2"> </font></p>
<p><font size="2">On retrouve trois fragments composés de la manière suivante :</font></p>
<ul>
<li><font size="2">Fragment 1 : page 201 </font></li>
<li><font size="2">Fragment 2 : {page 206 à page 211} </font></li>
<li><font size="2">Fragment 3 : {page 336 à page 344} </font></li>
</ul>
<p><font size="2">Donc pour aller de la page 201 (niveau feuille) à la page 344 il faudra passer par 2 fragments :</font></p>
<p><font size="2"></font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/Fragmentationdesindexesettablesfaiblevol_91CC/image_16.png"><font size="2"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blog.developpez.com/media/269/WindowsLiveWriter/Fragmentationdesindexesettablesfaiblevol_91CC/image_thumb_1.png" width="717" height="278" /></font></a><font size="2"> </font></p>
<p><font size="2">Ajoutons encore quelques données à notre table T.</font></p>
<blockquote><p><strong><font size="2">INSERT T (texte) VALUES (REPLICATE(&lsquo;T&rsquo;, 100))         <br />GO 1000</font></strong></p>
</blockquote>
<p><font size="2">L&rsquo;état de notre index cluster est le suivant :</font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/Fragmentationdesindexesettablesfaiblevol_91CC/image_24.png"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blog.developpez.com/media/269/WindowsLiveWriter/Fragmentationdesindexesettablesfaiblevol_91CC/image_thumb_9.png" width="842" height="56" /></a> </p>
<p><font size="2">Le nombre de fragments n&rsquo;a pas augmenté. Le nombre de pages est passé de 16 à 31 soit 15 pages supplémentaires. La fragmentation a diminué puisque le ratio fragments / pages a également diminué. Le plan d&rsquo;allocation de pages est devenu le suivant :</font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/Fragmentationdesindexesettablesfaiblevol_91CC/image_30.png"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blog.developpez.com/media/269/WindowsLiveWriter/Fragmentationdesindexesettablesfaiblevol_91CC/image_thumb_12.png" width="830" height="145" /></a> </p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/Fragmentationdesindexesettablesfaiblevol_91CC/image_28.png"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blog.developpez.com/media/269/WindowsLiveWriter/Fragmentationdesindexesettablesfaiblevol_91CC/image_thumb_11.png" width="832" height="410" /></a> </p>
<p><font size="2">Aller un dernier petit calcul de fragmentation :</font></p>
<blockquote>
<p><font size="2">Fragmentation = (3 – 1) / 32 soit 6.25%</font></p>
</blockquote>
<p><font size="2"> Vous aurez donc compris que plus le ratio fragment / pages est petit mieux c&rsquo;est. Ce ratio évolue lorsque la table (et donc l&rsquo;index) est mis à jour. Mais ceci fera certainement l&rsquo;objet d&rsquo;un autre billet <img src="https://blog.developpez.com/mikedavem/wp-includes/images/smilies/icon_smile.gif" alt=":-)" class="wp-smiley" /></font></p>
<p><font size="2">David BARBARIN (Mikedavem)      <br />MVP SQL Server</font></p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Connaître le gain de compression d’une table avec SQL Server</title>
		<link>https://blog.developpez.com/mikedavem/p10142/sql-server-2005/architecture/connaitre_le_gain_de_compression_darsquo</link>
		<comments>https://blog.developpez.com/mikedavem/p10142/sql-server-2005/architecture/connaitre_le_gain_de_compression_darsquo#comments</comments>
		<pubDate>Tue, 19 Jul 2011 20:36:33 +0000</pubDate>
		<dc:creator><![CDATA[mikedavem]]></dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[Vous avez identifié une table candidate à la compression ? mais vous voulez savoir quelle sera la meilleure méthode de compression ROW ou PAGE. SQL Server met à disposition une procédure stockée sp_estimate_data_compression_savings. Cependant  l&#8217;exécution de cette dernière permet seulement &#8230; <a href="https://blog.developpez.com/mikedavem/p10142/sql-server-2005/architecture/connaitre_le_gain_de_compression_darsquo">Lire la suite <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p><font size="2">Vous avez identifié une table candidate à la compression ? mais vous voulez savoir quelle sera la meilleure méthode de compression ROW ou PAGE. SQL Server met à disposition une procédure stockée <em>sp_estimate_data_compression_savings</em>. Cependant  l&rsquo;exécution de cette dernière permet seulement de savoir le taux de compression pour l&rsquo;une ou pour l&rsquo;autre méthode pour une seule partition d&rsquo;une table à la fois. Le script suivant permet de connaître pour une table donnée quelle est la meilleure méthode de compression à utiliser pour l&rsquo;ensemble des partitions d&rsquo;une table sachant qu&rsquo;une table non partitionnée possède une seule partition.</font></p>
<p><span id="more-38"></span></p>
<p><font size="2">Petite précision : il se peut que le résultat du script affiche un gain négatif. Ceci est parfaitement normal car la compression peut être contre bénéfique dans certains cas. En effet le nouveau format de page utilisé dans ce cas requiert des octets de gestion supplémentaire. Si le gain de compression est nulle alors on perd tout l&rsquo;intérêt de la compression et on se retrouve avec un nombre de pages supérieure qu&rsquo;à l&rsquo;état initial.</font></p>
<blockquote><p><strong><font size="2">DECLARE @object_id INT;         <br />SET @object_id = OBJECT_ID(&lsquo;monschema.maTable&rsquo;); </font></strong></p>
<p><strong><font size="2">DECLARE @i INT = 1;         <br />DECLARE @max INT; </font></strong></p>
<p><strong><font size="2">IF EXISTS (SELECT * FROM tempdb.sys.objects WHERE [object_id] = OBJECT_ID(&lsquo;tempdb..#_estimate_data_compression_savings&rsquo;))         <br /> DROP TABLE  #_estimate_data_compression_savings; </font></strong></p>
<p><strong><font size="2">&#8211; Work table for compression         <br />CREATE TABLE #_estimate_data_compression_savings          <br />(          <br /> id INT IDENTITY(1,1),          <br /> [object_name] SYSNAME,          <br /> [schema_name] SYSNAME,          <br /> index_id INT,          <br /> partition_number INT,           <br /> partition_compression VARCHAR(50) NULL,          <br /> partition_value VARCHAR(50) NULL,          <br /> [size_with_current_compression_setting(KB)] INT,          <br /> [size_with_requested_compression_setting(KB)] INT,          <br /> [sample_size_with_current_compression_setting(KB)] INT,          <br /> [sample_size_with_requested_compression_setting(KB)] INT,          <br /> [data_compression] VARCHAR(10) NULL          <br />); </font></strong></p>
<p><strong><font size="2">&#8211; Table for the concerned object          <br />DECLARE @table_compression TABLE           <br />(          <br /> id INT IDENTITY(1,1),          <br /> [schema_name] SYSNAME,          <br /> [object_name] SYSNAME,          <br /> index_id INT,          <br /> partition_number INT,          <br /> partition_compression VARCHAR(50),          <br /> partition_value SQL_VARIANT           <br />); </font></strong></p>
<p><strong><font size="2">INSERT @table_compression ([schema_name], [object_name], index_id, partition_number, partition_compression, partition_value)         <br />SELECT           <br /> s.name,          <br /> o.name,          <br /> p.index_id,          <br /> p.partition_number,          <br /> p.data_compression_desc,          <br /> pv.value          <br />FROM sys.partitions AS p          <br />INNER JOIN sys.objects AS o          <br /> ON p.[object_id] = o.[object_id]          <br />INNER JOIN sys.schemas AS s          <br /> ON s.[schema_id] = o.[schema_id]          <br />LEFT JOIN sys.partition_range_values AS pv          <br /> ON pv.boundary_id = p.partition_number          <br />WHERE o.[object_id] = @object_id          <br /> AND index_id &lt; 2; &#8212; Table heap or with clustered index          <br />SELECT @max = MAX(id)          <br />FROM @table_compression; </font></strong></p>
<p><strong><font size="2">DECLARE @schema_name SYSNAME;         <br />DECLARE @object_name SYSNAME;          <br />DECLARE @index_id INT;          <br />DECLARE @partition_number INT;          <br />DECLARE @partition_compression VARCHAR(50)          <br />DECLARE @partition_value SQL_VARIANT; </font></strong></p>
<p><strong><font size="2">WHILE @i &lt;= @max         <br />BEGIN </font></strong></p>
<p><strong><font size="2"> SELECT          <br />  @schema_name = [schema_name],          <br />  @object_name = [object_name],          <br />  @index_id = index_id,          <br />  @partition_number = partition_number,          <br />  @partition_compression = partition_compression,          <br />  @partition_value = partition_value          <br /> FROM @table_compression          <br /> WHERE id = @i; </font></strong></p>
<p><strong><font size="2"> INSERT #_estimate_data_compression_savings ([object_name], [schema_name], index_id, partition_number, [size_with_current_compression_setting(KB)],          <br />                                             [size_with_requested_compression_setting(KB)], [sample_size_with_current_compression_setting(KB)],          <br />                                             [sample_size_with_requested_compression_setting(KB)])          <br /> EXEC sp_estimate_data_compression_savings @schema_name, @object_name, @index_id, @partition_number, &lsquo;PAGE';          <br /> UPDATE #_estimate_data_compression_savings           <br /> SET [data_compression] = &lsquo;PAGE&rsquo;,          <br />  partition_value = CAST(@partition_value AS VARCHAR(50)),          <br />  partition_compression = @partition_compression          <br /> WHERE id = SCOPE_IDENTITY()          <br /> INSERT #_estimate_data_compression_savings ([object_name], [schema_name], index_id, partition_number, [size_with_current_compression_setting(KB)],           <br />                                             [size_with_requested_compression_setting(KB)], [sample_size_with_current_compression_setting(KB)],          <br />                                             [sample_size_with_requested_compression_setting(KB)])          <br /> EXEC sp_estimate_data_compression_savings @schema_name, @object_name, @index_id, @partition_number, &lsquo;ROW';          <br /> UPDATE #_estimate_data_compression_savings           <br /> SET [data_compression] = &lsquo;ROW&rsquo;,          <br />  partition_value = CAST(@partition_value AS VARCHAR(50)),          <br />  partition_compression = @partition_compression          <br /> WHERE id = SCOPE_IDENTITY()          <br /> SET @i += @i;          <br />END </font></strong></p>
<p><strong><font size="2">&#8211; Show result         <br />;WITH estimate_data_compression_with_page          <br />AS          <br />(          <br /> SELECT           <br />  [schema_name],          <br />  [object_name],          <br />  index_id,          <br />  partition_number,          <br />  partition_value,          <br />  partition_compression,          <br />  [data_compression],          <br />  CASE [size_with_current_compression_setting(KB)]          <br />   WHEN 0 THEN 0          <br />   ELSE (1 &#8211; CAST([size_with_requested_compression_setting(KB)] * 1.           <br />            / [size_with_current_compression_setting(KB)] AS DECIMAL(5,2))) * 100           <br />  END AS ratio_size_compressed,          <br />  [size_with_current_compression_setting(KB)],          <br />  [size_with_requested_compression_setting(KB)],          <br />  CASE [sample_size_with_current_compression_setting(KB)]          <br />   WHEN 0 THEN 0          <br />   ELSE (1 &#8211; CAST([sample_size_with_requested_compression_setting(KB)] * 1.          <br />            / [sample_size_with_current_compression_setting(KB)] AS DECIMAL(5,2))) * 100           <br />  END AS ratio_sample_compressed,          <br />  [sample_size_with_current_compression_setting(KB)],          <br />  [sample_size_with_requested_compression_setting(KB)]          <br /> FROM #_estimate_data_compression_savings          <br /> WHERE [data_compression] = &lsquo;PAGE&rsquo;          <br />),          <br />estimate_data_compression_with_row          <br />AS          <br />(          <br /> SELECT           <br />  [schema_name],          <br />  [object_name],          <br />  index_id,          <br />  partition_number,          <br />  [data_compression],          <br />  CASE [size_with_current_compression_setting(KB)]          <br />   WHEN 0 THEN 0          <br />   ELSE (1 &#8211; CAST([size_with_requested_compression_setting(KB)] * 1.           <br />            / [size_with_current_compression_setting(KB)] AS DECIMAL(5,2))) * 100           <br />  END AS ratio_size_compressed,          <br />  [size_with_current_compression_setting(KB)],          <br />  [size_with_requested_compression_setting(KB)],          <br />  CASE [sample_size_with_current_compression_setting(KB)]          <br />   WHEN 0 THEN 0          <br />   ELSE (1 &#8211; CAST([sample_size_with_requested_compression_setting(KB)] * 1.          <br />            / [sample_size_with_current_compression_setting(KB)] AS DECIMAL(5,2))) * 100           <br />  END AS ratio_sample_compressed,          <br />  [sample_size_with_current_compression_setting(KB)],          <br />  [sample_size_with_requested_compression_setting(KB)]          <br /> FROM #_estimate_data_compression_savings          <br /> WHERE [data_compression] = &lsquo;ROW&rsquo;          <br />)          <br />SELECT           <br /> P.[schema_name],          <br /> P.[object_name],          <br /> P.index_id,          <br /> P.partition_number,          <br /> P.partition_compression,          <br /> P.partition_value,          <br /> CASE WHEN P.ratio_size_compressed &lt; R.ratio_size_compressed THEN &lsquo;ROW : &lsquo; + CAST(R.ratio_size_compressed AS VARCHAR(8)) + &lsquo; %&rsquo;          <br />      WHEN P.ratio_size_compressed &gt; R.ratio_size_compressed THEN &lsquo;PAGE : &lsquo; + CAST(P.ratio_size_compressed AS VARCHAR(8)) + &lsquo; %&rsquo;          <br />      ELSE &lsquo;ROW OR PAGE : &lsquo; + CAST(P.ratio_size_compressed AS VARCHAR(8)) + &lsquo; %&rsquo;          <br /> END AS preferred_method_compression,          <br /> CASE WHEN P.ratio_size_compressed &lt; R.ratio_size_compressed THEN R.[size_with_current_compression_setting(KB)]          <br />      WHEN P.ratio_size_compressed &gt; R.ratio_size_compressed THEN P.[size_with_current_compression_setting(KB)]          <br />      ELSE P.[size_with_current_compression_setting(KB)]          <br /> END AS [size_with_current_compression_setting(KB)],          <br /> CASE WHEN P.ratio_size_compressed &lt; R.ratio_size_compressed THEN R.[size_with_requested_compression_setting(KB)]          <br />      WHEN P.ratio_size_compressed &gt; R.ratio_size_compressed THEN P.[size_with_requested_compression_setting(KB)]          <br />      ELSE P.[size_with_requested_compression_setting(KB)]          <br /> END AS [size_with_current_compression_setting(KB)],          <br /> CASE WHEN P.ratio_size_compressed &lt; R.ratio_size_compressed THEN R.[sample_size_with_current_compression_setting(KB)]          <br />      WHEN P.ratio_size_compressed &gt; R.ratio_size_compressed THEN P.[sample_size_with_current_compression_setting(KB)]          <br />      ELSE P.[sample_size_with_current_compression_setting(KB)]          <br /> END AS [sample_size_with_requested_compression_setting(KB)],          <br /> CASE WHEN P.ratio_size_compressed  &lt; R.ratio_size_compressed THEN R.[sample_size_with_requested_compression_setting(KB)]          <br />      WHEN P.ratio_size_compressed &gt; R.ratio_size_compressed THEN P.[sample_size_with_requested_compression_setting(KB)]          <br />      ELSE P.[sample_size_with_requested_compression_setting(KB)]          <br /> END AS [sample_size_with_requested_compression_setting(KB)]          <br />FROM estimate_data_compression_with_page AS P          <br />INNER JOIN estimate_data_compression_with_row AS R          <br /> ON P.[schema_name] = R.[schema_name]          <br />  AND P.[object_name] = R.[object_name]          <br />   AND P.index_id = R.index_id          <br />    AND P.partition_number = R.partition_number;</font></strong></p>
</blockquote>
<p><font size="2">Bonne compression !!</font></p>
<p><font size="2">David BARBARIN (Mikedavem)      <br />MVP SQL Server</font></p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Auditer la sécurité des comptes de connexion, utilisateurs et rôle d’une instance SQL Server</title>
		<link>https://blog.developpez.com/mikedavem/p10137/sql-server-2005/sql/auditer_la_securite_des_comptes_de_conne</link>
		<comments>https://blog.developpez.com/mikedavem/p10137/sql-server-2005/sql/auditer_la_securite_des_comptes_de_conne#comments</comments>
		<pubDate>Mon, 18 Jul 2011 19:16:57 +0000</pubDate>
		<dc:creator><![CDATA[mikedavem]]></dc:creator>
				<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[Cela faisait un moment que je n&#8217;ai pas posté de billets !! Je recommence doucement en vous proposant une procédure qui permet d&#8217;auditer la sécurité (comptes de connexion et utilisateurs) d&#8217;une instance SQL Server. Cette dernière est valable pour les &#8230; <a href="https://blog.developpez.com/mikedavem/p10137/sql-server-2005/sql/auditer_la_securite_des_comptes_de_conne">Lire la suite <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>Cela faisait un moment que je n&rsquo;ai pas posté de billets !! Je recommence doucement en vous proposant une procédure qui permet d&rsquo;auditer la sécurité (comptes de connexion et utilisateurs) d&rsquo;une instance SQL Server. Cette dernière est valable pour les version 2000, 2005, 2008 et 2008 R2. </p>
<p><span id="more-75"></span></p>
<blockquote><p><strong>/***********************************       <br /> * @Author = BARBARIN DAVID        *        <br /> * @Date = 05/20/2011              *        <br /> * @Description =                  *        <br /> * Extract account privileges          *        <br /> * on SQL Server 2000, 2005  and 2008   *        <br /> ***********************************/  </strong></p>
<p><strong>&#8211; Drop temp tables if exists       <br />IF EXISTS (SELECT * FROM tempdb.dbo.sysobjects WHERE id = OBJECT_ID(&lsquo;tempdb.dbo.#tlogin&rsquo;))        <br />  DROP TABLE #tlogin        <br />IF EXISTS (SELECT * FROM tempdb.dbo.sysobjects WHERE id = OBJECT_ID(&lsquo;tempdb.dbo.#trole&rsquo;))        <br />  DROP TABLE #trole        <br />IF EXISTS (SELECT * FROM tempdb.dbo.sysobjects WHERE id = OBJECT_ID(&lsquo;tempdb.dbo.#tuser&rsquo;))        <br />  DROP TABLE #tuser        <br />CREATE TABLE #trole        <br />(        <br /> DBName VARCHAR(50),        <br /> RoleName SYSNAME NULL,        <br /> UserName SYSNAME NULL,        <br /> ObjectType VARCHAR(100),        <br /> ObjectName SYSNAME NULL,        <br /> Permission SYSNAME NULL        <br />) </strong></p>
<p><strong>&#8211; Create temp table for users       <br />CREATE TABLE #tuser         <br />(        <br /> DBName VARCHAR(50),        <br /> UserName SYSNAME,        <br /> ObjectType VARCHAR(100),        <br /> ObjectName SYSNAME NULL,        <br /> Permission SYSNAME NULL        <br />) </strong></p>
<p><strong>&#8211; Create temp table for logins       <br />CREATE TABLE #tlogin        <br />(        <br /> LoginName SYSNAME,        <br /> SrvRoleName SYSNAME        <br />) </strong></p>
<p><strong>IF (LEFT(CAST(SERVERPROPERTY(&lsquo;ProductVersion&rsquo;) AS VARCHAR(50)),1) = &lsquo;9&rsquo;<br />
         OR LEFT(CAST(SERVERPROPERTY(&lsquo;ProductVersion&rsquo;) AS VARCHAR(50)),2) = &rsquo;10&rsquo;)<br />
       <br />BEGIN        <br /> IF EXISTS (SELECT * FROM tempdb.dbo.sysobjects WHERE id = OBJECT_ID(&lsquo;tempdb.dbo.#tloginpermissions&rsquo;))        <br />  DROP TABLE #tloginpermissions        <br />  &#8212; Create temp table  for logins permissions        <br /> CREATE TABLE #tloginpermissions        <br /> (        <br />  LoginName SYSNAME,        <br />  LoginTypeDesc SYSNAME,        <br />  LoginPermission SYSNAME,        <br />  LoginPermissionState SYSNAME        <br /> )        <br />END </strong></p>
<p><strong>&#8211; Create temp tables for roles and user members       <br />/* Extract Server Login  */        <br />&#8211; SQL Server 2000        <br />IF LEFT(CAST(SERVERPROPERTY(&lsquo;ProductVersion&rsquo;) AS VARCHAR(50)),1) = &lsquo;8&rsquo;        <br />BEGIN        <br /> INSERT #tlogin        <br /> SELECT         <br />  loginname AS login_name,        <br />  &lsquo;sysadmin&rsquo; AS role_name        <br /> FROM master..syslogins        <br /> WHERE sysadmin = 1        <br /> UNION ALL        <br /> SELECT         <br />  loginname AS login_name,        <br />  &lsquo;securityadmin&rsquo;        <br /> FROM master..syslogins        <br /> WHERE securityadmin = 1        <br /> UNION ALL        <br /> SELECT         <br />  loginname AS login_name,        <br />  &lsquo;serveradmin&rsquo;        <br /> FROM master..syslogins        <br /> WHERE serveradmin = 1        <br /> UNION ALL        <br /> SELECT         <br />  loginname AS login_name,        <br />  &lsquo;setupadmin&rsquo;        <br /> FROM master..syslogins        <br /> WHERE setupadmin = 1        <br /> UNION ALL        <br /> SELECT         <br />  loginname AS login_name,        <br />  &lsquo;processadmin&rsquo;        <br /> FROM master..syslogins        <br /> WHERE processadmin = 1        <br /> UNION ALL        <br /> SELECT         <br />  loginname AS login_name,        <br />  &lsquo;diskadmin&rsquo;        <br /> FROM master..syslogins        <br /> WHERE diskadmin = 1        <br /> UNION ALL        <br /> SELECT         <br />  loginname AS login_name,        <br />  &lsquo;dbcreator&rsquo;        <br /> FROM master..syslogins        <br /> WHERE dbcreator = 1        <br /> UNION ALL        <br /> SELECT         <br />  loginname AS login_name,        <br />  &lsquo;bulkadmin&rsquo;        <br /> FROM master..syslogins        <br /> WHERE bulkadmin = 1        <br /> ORDER BY login_name, role_name        <br />END        <br />&#8211; SQL Server 2005 or more        <br />ELSE IF (LEFT(CAST(SERVERPROPERTY(&lsquo;ProductVersion&rsquo;) AS VARCHAR(50)),1) = &lsquo;9&rsquo;        <br />         OR LEFT(CAST(SERVERPROPERTY(&lsquo;ProductVersion&rsquo;) AS VARCHAR(50)),2) = &rsquo;10&rsquo;)        <br />BEGIN        <br />  INSERT #tlogin        <br />  SELECT         <br />   sp.name AS login_name,        <br />   sp2.name AS role_name        <br />  FROM sys.server_principals AS sp        <br />  LEFT JOIN sys.server_role_members srm        <br />   ON sp.principal_id = srm.member_principal_id         <br />  LEFT JOIN sys.server_principals AS sp2        <br />   ON sp2.principal_id = srm.role_principal_id         <br />  WHERE sp.type IN (&lsquo;S&rsquo;, &lsquo;U&rsquo;, &lsquo;G&rsquo;)        <br />   AND sp2.name IS NOT NULL        <br />  INSERT #tloginpermissions        <br />  SELECT         <br />   sp.name AS login_name,        <br />   sp.type_desc AS [login_type],        <br />   spm.permission_name,        <br />   spm.state_desc        <br />  FROM sys.server_principals AS sp        <br />  INNER JOIN sys.server_permissions AS spm        <br />   ON sp.principal_id = spm.grantee_principal_id        <br />  WHERE sp.type IN (&lsquo;S&rsquo;, &lsquo;U&rsquo;, &lsquo;G&rsquo;)        <br />  ORDER BY sp.name, spm.permission_name        <br />END </strong></p>
<p><strong>/* Extract Database Users Privileges */       <br />&#8211; SQL Server 2000        <br />IF LEFT(CAST(SERVERPROPERTY(&lsquo;ProductVersion&rsquo;) AS VARCHAR(50)),1) = &lsquo;8&rsquo;        <br />BEGIN         <br />    INSERT #tuser        <br />    EXEC sp_msforeachdb        <br />    &lsquo;USE ?        <br />     SELECT         <br />     &nbsp;&raquo;?&nbsp;&raquo; AS database_name,        <br />     u.name AS user_name,        <br />     CASE o.xtype        <br />      WHEN &nbsp;&raquo;C&nbsp;&raquo; THEN &nbsp;&raquo;CHECK constraint&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;D&nbsp;&raquo; THEN &nbsp;&raquo;Default or DEFAULT constraint&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;F&nbsp;&raquo; THEN &nbsp;&raquo;FOREIGN KEY constraint&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;L&nbsp;&raquo; THEN &nbsp;&raquo;Log&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;FN&nbsp;&raquo; THEN &nbsp;&raquo;Scalar function&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;IF&nbsp;&raquo; THEN &nbsp;&raquo;Inlined table-function&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;P&nbsp;&raquo; THEN &nbsp;&raquo;Stored procedure&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;PK&nbsp;&raquo; THEN &nbsp;&raquo;PRIMARY KEY constraint (type is K)&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;RF&nbsp;&raquo; THEN &nbsp;&raquo;Replication filter stored procedure&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;S&nbsp;&raquo; THEN &nbsp;&raquo;System table&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;TF&nbsp;&raquo; THEN &nbsp;&raquo;Table function&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;TR&nbsp;&raquo; THEN &nbsp;&raquo;Trigger&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;U&nbsp;&raquo; THEN &nbsp;&raquo;User table&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;UQ&nbsp;&raquo; THEN &nbsp;&raquo;UNIQUE constraint (type is K)&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;V&nbsp;&raquo; THEN &nbsp;&raquo;View&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;X&nbsp;&raquo; THEN &nbsp;&raquo;Extended stored procedure&nbsp;&raquo;        <br />     END AS ObjectType,        <br />     o.name AS object_name,        <br />     CASE pr.action         <br />      WHEN 26 THEN &nbsp;&raquo;REFERENCES&nbsp;&raquo;        <br />      WHEN 193 THEN &nbsp;&raquo;SELECT&nbsp;&raquo;        <br />      WHEN 195 THEN &nbsp;&raquo;INSERT&nbsp;&raquo;        <br />      WHEN 196 THEN &nbsp;&raquo;DELETE&nbsp;&raquo;        <br />      WHEN 197 THEN &nbsp;&raquo;UPDATE&nbsp;&raquo;        <br />      WHEN 224 THEN &nbsp;&raquo;EXECUTE&nbsp;&raquo;        <br />     ELSE &nbsp;&raquo;OTHERS&nbsp;&raquo;        <br />    END AS permission_name        <br />    FROM sysusers u        <br />    INNER JOIN syspermissions p        <br />     ON u.uid = p.grantee        <br />    INNER JOIN sysobjects o        <br />     ON p.id = o.id        <br />    INNER JOIN sysprotects pr        <br />     ON o.id = pr.id         <br />      AND u.uid = pr.uid        <br />    ORDER BY u.name, o.name&rsquo;        <br />END        <br />&#8211; SQL Server 2005 or more        <br />ELSE IF (LEFT(CAST(SERVERPROPERTY(&lsquo;ProductVersion&rsquo;) AS VARCHAR(50)),1) = &lsquo;9&rsquo;        <br />        OR LEFT(CAST(SERVERPROPERTY(&lsquo;ProductVersion&rsquo;) AS VARCHAR(50)),2) = &rsquo;10&rsquo;)        <br />BEGIN        <br />     PRINT &lsquo;SQL SERVER 2008&prime;        <br />     INSERT #tuser        <br />     EXEC sp_MSforeachdb &lsquo;        <br />     USE ?        <br />     SELECT         <br />      &nbsp;&raquo;?&nbsp;&raquo; AS database_name,        <br />      p.name AS [user_name],        <br />      pm.class_desc AS ObjectType,        <br />      CASE pm.class         <br />       WHEN 1 THEN o.name        <br />       WHEN 3 THEN s.name        <br />       WHEN 0 THEN DB_NAME()        <br />      END object_name,        <br />      pm.permission_name        <br />      FROM sys.database_principals AS p        <br />      INNER JOIN sys.database_permissions AS pm        <br />       ON pm.grantee_principal_id = p.principal_id        <br />      LEFT JOIN sys.objects AS o        <br />       ON pm.major_id = o.object_id        <br />      LEFT JOIN sys.schemas AS s        <br />       ON pm.major_id = s.schema_id        <br />      ORDER BY p.name, pm.class&rsquo;        <br />END </strong></p>
<p><strong>/* Extract role rights on databases */       <br />&#8211; SQL Server 2000        <br />IF LEFT(CAST(SERVERPROPERTY(&lsquo;ProductVersion&rsquo;) AS VARCHAR(50)),1) = &lsquo;8&rsquo;        <br />BEGIN        <br /> PRINT &lsquo;SQL SERVER 2000&prime;        <br /> INSERT #trole        <br /> EXEC sp_MSforeachdb &lsquo;        <br /> USE ?        <br /> SELECT         <br />     &nbsp;&raquo;?&nbsp;&raquo; AS database_name,        <br />     u.name AS role_name,        <br />     u2.name AS user_name,        <br />     CASE o.xtype        <br />      WHEN &nbsp;&raquo;C&nbsp;&raquo; THEN &nbsp;&raquo;CHECK constraint&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;D&nbsp;&raquo; THEN &nbsp;&raquo;Default or DEFAULT constraint&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;F&nbsp;&raquo; THEN &nbsp;&raquo;FOREIGN KEY constraint&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;L&nbsp;&raquo; THEN &nbsp;&raquo;Log&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;FN&nbsp;&raquo; THEN &nbsp;&raquo;Scalar function&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;IF&nbsp;&raquo; THEN &nbsp;&raquo;Inlined table-function&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;P&nbsp;&raquo; THEN &nbsp;&raquo;Stored procedure&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;PK&nbsp;&raquo; THEN &nbsp;&raquo;PRIMARY KEY constraint (type is K)&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;RF&nbsp;&raquo; THEN &nbsp;&raquo;Replication filter stored procedure&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;S&nbsp;&raquo; THEN &nbsp;&raquo;System table&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;TF&nbsp;&raquo; THEN &nbsp;&raquo;Table function&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;TR&nbsp;&raquo; THEN &nbsp;&raquo;Trigger&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;U&nbsp;&raquo; THEN &nbsp;&raquo;User table&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;UQ&nbsp;&raquo; THEN &nbsp;&raquo;UNIQUE constraint (type is K)&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;V&nbsp;&raquo; THEN &nbsp;&raquo;View&nbsp;&raquo;        <br />      WHEN &nbsp;&raquo;X&nbsp;&raquo; THEN &nbsp;&raquo;Extended stored procedure&nbsp;&raquo;        <br />     END AS ObjectType,        <br />     o.name AS object_name,        <br />     CASE pr.action         <br />      WHEN 26 THEN &nbsp;&raquo;REFERENCES&nbsp;&raquo;        <br />      WHEN 193 THEN &nbsp;&raquo;SELECT&nbsp;&raquo;        <br />      WHEN 195 THEN &nbsp;&raquo;INSERT&nbsp;&raquo;        <br />      WHEN 196 THEN &nbsp;&raquo;DELETE&nbsp;&raquo;        <br />      WHEN 197 THEN &nbsp;&raquo;UPDATE&nbsp;&raquo;        <br />      WHEN 224 THEN &nbsp;&raquo;EXECUTE&nbsp;&raquo;        <br />     ELSE &nbsp;&raquo;OTHERS&nbsp;&raquo;        <br />    END AS permission_name        <br />    FROM sysusers u        <br />    LEFT JOIN syspermissions p        <br />     ON u.uid = p.grantee        <br />    LEFT JOIN sysobjects o        <br />     ON p.id = o.id        <br />    LEFT JOIN sysprotects pr        <br />     ON o.id = pr.id         <br />      AND u.uid = pr.uid        <br />    LEFT JOIN sysmembers m        <br />     ON u.uid = m.groupuid        <br />    LEFT JOIN  sysusers u2        <br />     ON m.memberuid = u2.uid        <br />    WHERE u.issqlrole = 1        <br />    ORDER BY u.name, u2.name, o.name&rsquo;        <br />END        <br />&#8211; SQL Server 2005 or more        <br />ELSE IF (LEFT(CAST(SERVERPROPERTY(&lsquo;ProductVersion&rsquo;) AS VARCHAR(50)),1) = &lsquo;9&rsquo;        <br />         OR LEFT(CAST(SERVERPROPERTY(&lsquo;ProductVersion&rsquo;) AS VARCHAR(50)),2) = &rsquo;10&rsquo;)        <br />BEGIN        <br />  PRINT &lsquo;SQL SERVER 2008&prime;        <br />  INSERT #trole        <br />      EXEC sp_MSforeachdb &lsquo;        <br />      USE ?        <br />      SELECT         <br />       &nbsp;&raquo;?&nbsp;&raquo; as database_name,        <br />       dp.name AS role_name,        <br />       dp2.name AS user_name,        <br />       pm.class_desc AS ObjectType,        <br />       CASE pm.class         <br />        WHEN 1 THEN o.name        <br />        WHEN 3 THEN s.name        <br />        WHEN 0 THEN DB_NAME()        <br />       END object_name,        <br />       pm.permission_name        <br />      FROM sys.database_principals AS dp        <br />      LEFT JOIN sys.database_role_members AS rlm        <br />       ON rlm.role_principal_id = dp.principal_id        <br />      LEFT JOIN sys.database_principals AS dp2        <br />       ON dp2.principal_id = rlm.member_principal_id        <br />      LEFT JOIN sys.database_permissions AS pm        <br />       ON dp.principal_id = pm.grantee_principal_id        <br />      LEFT JOIN sysobjects AS o        <br />       ON pm.major_id = o.id        <br />     LEFT JOIN sys.schemas AS s        <br />      ON pm.major_id = s.schema_id        <br />     WHERE dp.type = &nbsp;&raquo;R&nbsp;&raquo;&rsquo;        <br />END </strong></p>
<p><strong>/* Show permissions */       <br />SELECT @@VERSION </strong></p>
<p><strong>&#8211; Logins and server roles       <br />SELECT &lsquo;Login and server roles&rsquo;        <br />SELECT *        <br />FROM #tlogin </strong></p>
<p><strong>&#8211; Server permissions was introduced with SQL Server 2005       <br />IF (LEFT(CAST(SERVERPROPERTY(&lsquo;ProductVersion&rsquo;) AS VARCHAR(50)),1) = &lsquo;9&rsquo;        <br />    OR LEFT(CAST(SERVERPROPERTY(&lsquo;ProductVersion&rsquo;) AS VARCHAR(50)),2) = &rsquo;10&rsquo;)        <br />BEGIN        <br /> SELECT &lsquo;Server login permissions&rsquo;        <br /> SELECT *        <br /> FROM #tloginpermissions        <br />END </strong></p>
<p><strong>&#8211; Role permissions        <br />SELECT &lsquo;Role permissions with users&rsquo;        <br />SELECT *        <br />FROM #trole </strong></p>
<p><strong>&#8211; User permissions       <br />SELECT &lsquo;User permissions&rsquo;        <br />SELECT * FROM #tuser</strong></p>
<p>   <strong></strong></p></blockquote>
<p> </p>
<p>A noter que je n&rsquo;ai pas réalisé de concaténation dans le cas où un login appartient à plusieurs rôles de serveur par exemple. Le but de cette extraction est de pouvoir facilement l?importer dans un fichier Excel. </p>
<p>Bon audit !!</p>
<p>David BARBARIN (Mikedavem)    <br />MVP SQL Server</p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Comment donner le rôle d’opérateur de sauvegarde au niveau serveur à un compte de connexion sur SQL Server</title>
		<link>https://blog.developpez.com/mikedavem/p9802/sql-server-2005/sql/comment_donner_le_role_darsquo_operateur</link>
		<comments>https://blog.developpez.com/mikedavem/p9802/sql-server-2005/sql/comment_donner_le_role_darsquo_operateur#comments</comments>
		<pubDate>Fri, 11 Mar 2011 21:00:39 +0000</pubDate>
		<dc:creator><![CDATA[mikedavem]]></dc:creator>
				<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[Suite à une discussion sur le forum Developpez.com, une demande intéressante a été émise par Ptit_Dje concernant la possibilité de donner à un compte de connexion la possibilité de sauvegarder l&#8217;ensemble des bases sur le serveur. Nativement il n&#8217;est pas &#8230; <a href="https://blog.developpez.com/mikedavem/p9802/sql-server-2005/sql/comment_donner_le_role_darsquo_operateur">Lire la suite <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p><font size="2">Suite à une discussion sur le forum </font><a href="http://www.developpez.net/forums/d1038583-3/bases-donnees/ms-sql-server/commentaires-quest-quil-manque-selon-sql-server/"><font size="2">Developpez.com</font></a><font size="2">, une demande intéressante a été émise par </font><a href="http://www.developpez.net/forums/u184604/ptit_dje/"><font size="2">Ptit_Dje</font></a><font size="2"> concernant la possibilité de donner à un compte de connexion la possibilité de sauvegarder l&rsquo;ensemble des bases sur le serveur. Nativement il n&rsquo;est pas possible de faire cela avec SQL Server. En effet, on ne peut octroyer le droit de sauvegarde qu&rsquo;au niveau base de données. </font></p>
<p> <span id="more-74"></span>
<p><font size="2">La seule possibilité qui s&rsquo;offre à nous est donc de lier pour chaque base de données un utilisateur à un compte de connexion. Chaque utilisateur de base de données doit ensuite être configurer afin de pouvoir sauvegarder la base de données concernant. Pour cela il est possible d&rsquo;utiliser l&rsquo;instruction <strong>GRANT</strong> ou d&rsquo;associer l&rsquo;utilisateur au rôle fixe de bases de données <strong>db_backupoperator</strong>. La question qui se pose maintenant est comment faire cela automatiquement lorsqu&rsquo;une base est créée ? Les triggers DDL peuvent être une solution dans notre cas. </font></p>
<p><font size="2">Le script suivant effectue les actions suivantes après qu&rsquo;une base de données soit créée :</font></p>
<p><font size="2">- Création d&rsquo;un utilisateur dans la base de données nouvellement créée (nom identique à celui du compte de connexion qui lui est associé)      <br />- Ajout du nouvel utilisateur au rôle fixe de bases de données <em>db_backupoperator</em></font></p>
<p><font size="2">Ce script peut bien entendu être amélioré ou modifié selon le contexte. Le but ici est simplement d&rsquo;en montrer la trame principale.</font></p>
<p><font size="2"> </font></p>
<blockquote><p><strong><font size="2">CREATE TRIGGER TR_backupoperator          <br />ON ALL SERVER           <br />AFTER CREATE_DATABASE           <br />AS </font></strong></p>
<p><strong><font size="2">SET NOCOUNT ON; </font></strong></p>
<p><strong><font size="2">DECLARE @user VARCHAR(100);          <br />DECLARE @sql VARCHAR(1000);           <br />DECLARE @xml_event XML; </font></strong></p>
<p><strong><font size="2">SET @xml_event = EVENTDATA();          <br />SET @user = &lsquo;user'; </font></strong></p>
<p><strong><font size="2">SELECT @sql =          <br />    &lsquo;USE &lsquo; +           <br />    CONVERT(SYSNAME, @xml_event.query(&lsquo;data(/EVENT_INSTANCE/DatabaseName)&rsquo;)) + &lsquo;; CREATE USER &lsquo; + @user +           <br />    &lsquo; FOR LOGIN &lsquo; + @user + &lsquo;; EXEC sp_addrolemember &nbsp;&raquo;db_backupoperator&nbsp;&raquo;, &nbsp;&raquo;&rsquo; + @user + &nbsp;&raquo;';';  </font></strong><strong><font size="2">         </p>
<p>BEGIN TRY    <br />    EXEC(@sql);           <br />END TRY           <br />BEGIN CATCH           <br />    PRINT &lsquo;The database can&nbsp;&raquo;t be created due to the following issue :';           <br />    PRINT ERROR_MESSAGE();           <br />END CATCH;</font></strong></p>
</blockquote>
<p><font size="2"> </font></p>
<p><font size="2">Je tiens quand même à préciser que ce genre de méthode, bien que pratique, doit être utilisée avec parcimonie. En effet, dans des environnements où les données peuvent être sensibles, il n&rsquo;est pas rare de vouloir isoler une base de données par rapport à une autre. Dans ce cas le fait de donner le droit de sauvegarde sur l&rsquo;ensemble du serveur à un compte de connexion peut compromettre la sécurité des données car celui-ci peut avoir accès complet aux données de l&rsquo;instance.</font></p>
<p><font size="2">David BARBARIN (Mikedavem)      <br />MVP SQL Server</font></p>
<p><font size="2"></font></p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Article : Data Tier Application avec SQL Server 2008 R2</title>
		<link>https://blog.developpez.com/mikedavem/p9673/sql-server-2005/sql/article_data_tier_application_avec_sql_s</link>
		<comments>https://blog.developpez.com/mikedavem/p9673/sql-server-2005/sql/article_data_tier_application_avec_sql_s#comments</comments>
		<pubDate>Fri, 14 Jan 2011 17:39:45 +0000</pubDate>
		<dc:creator><![CDATA[mikedavem]]></dc:creator>
				<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[SQL Server 2008 R2 offre une voie nouvelle en matière de développement dans le cycle de vie d&#8217;une application de base de données. DAC ou Data Tier Application intègre la notion d&#8217;application et de package qui rend plus facile le &#8230; <a href="https://blog.developpez.com/mikedavem/p9673/sql-server-2005/sql/article_data_tier_application_avec_sql_s">Lire la suite <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>SQL Server 2008 R2 offre une voie nouvelle en matière de développement dans le cycle de vie d&rsquo;une application de base de données. <strong><em>DAC</em></strong> ou <strong><em>Data Tier Application</em></strong> intègre la notion d&rsquo;application et de package qui rend plus facile le déploiement et la mise à jour une base de données. L&rsquo;intégration avec Visual Studio 2010 est également plus prépondérant. Bien que les contraintes d&rsquo;utilisation soient encore nombreuses et limitent le champ des possibilités, il est intéressant de voir la direction empruntée par Microsoft pour faciliter le processus d&rsquo;intégration des applications avec SQL Server &#8230;</p>
<p>>> <a title="http://msdn.microsoft.com/fr-fr/sqlserver/gg574334" href="http://msdn.microsoft.com/fr-fr/sqlserver/gg574334">http://msdn.microsoft.com/fr-fr/sqlserver/gg574334</a></p>
<p>David BARBARIN (Mikedavem)    <br />MVP SQL Server</p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Histoire de stockage : Modifications internes relative au changement de longueur d’une colonne de table</title>
		<link>https://blog.developpez.com/mikedavem/p9298/sql-server-2008-r2/histoire_de_stockage_modifications_inter</link>
		<comments>https://blog.developpez.com/mikedavem/p9298/sql-server-2008-r2/histoire_de_stockage_modifications_inter#comments</comments>
		<pubDate>Fri, 17 Sep 2010 19:10:08 +0000</pubDate>
		<dc:creator><![CDATA[mikedavem]]></dc:creator>
				<category><![CDATA[SQL]]></category>
		<category><![CDATA[SQL Server 2008 R2]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[La modification de la longueur d&#8217;une colonne de table est une opération plutôt courante dans la vie d&#8217;un administrateur de bases de données mais qu&#8217;en est il du stockage interne ? Beaucoup de gens pensent par exemple que diminuer la &#8230; <a href="https://blog.developpez.com/mikedavem/p9298/sql-server-2008-r2/histoire_de_stockage_modifications_inter">Lire la suite <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p><font size="2">La modification de la longueur d&rsquo;une colonne de table est une opération plutôt courante dans la vie d&rsquo;un administrateur de bases de données mais qu&rsquo;en est il du stockage interne ? Beaucoup de gens pensent par exemple que diminuer la longueur d&rsquo;une colonne permet de récupérer de l&rsquo;espace de stockage ou que d&rsquo;augmenter la longueur d&rsquo;une colonne n&rsquo;a que très peu d&rsquo;impact. Rien de ceci n&rsquo;est vrai et nous le verrons dans la suite de ce billet.</font></p>
<p> <span id="more-122"></span>
<p><font size="2">Commençons par créer une table T_TEST :</font></p>
<blockquote><p><font size="2"><strong>CREATE TABLE T_TEST          <br />(           <br />id SMALLINT NOT NULL,           <br />texte1 CHAR(50) NOT NULL           <br />);</strong></font></p>
</blockquote>
<p><font size="2">Insérons ensuite une ligne de données :</font></p>
<blockquote><p><font size="2"><strong>INSERT T_TEST VALUES (1, REPLICATE(&lsquo;T&rsquo;, 25));</strong></font></p>
</blockquote>
<p><font size="2">Dans un premier temps nous utiliserons une vue système <em>sys.system_internals_partition_columns</em>. Cette vue permet de visualiser les métas données de bas niveau du moteur SQL Server. Cette vue est bien entendu réservée à un usage interne et leur compatibilité n&rsquo;est pas garantie pour les versions futures de SQL Server. Vous trouverez une liste des vues </font><a href="http://msdn.microsoft.com/en-us/library/ms189600.aspx"><font size="2">ici</font></a><font size="2">.</font></p>
<p><font size="2">Le script suivant permet de visualiser les informations d&rsquo;offsets des colonnes de la table T_TEST :</font></p>
<blockquote><p><font size="2"><strong>SELECT          <br />    C.name AS column_name,           <br />    C.column_id,           <br />    PC.max_inrow_length,           <br />    PC.system_type_id,           <br />    PC.leaf_offset           <br />FROM sys.system_internals_partition_columns PC           <br />INNER JOIN sys.partitions AS P           <br />ON P.partition_id = PC.partition_id           <br />INNER JOIN sys.columns AS C           <br />ON C.column_id = PC.partition_column_id           <br />  AND C.object_id = P.object_id           <br />WHERE P.object_id = OBJECT_ID(&lsquo;T_TEST&rsquo;);</strong></font></p>
</blockquote>
<p><font size="2">Le résultat est le suivant :</font></p>
<p><img src="http://mikedavem.developpez.com/blog/alter_table/alter_table1.jpg" alt="alter table" title="alter table" /></p>
<p><font size="2">La colonne id est stocké sur 2 octets (SMALLINT = 2 octets) et commence au 4ème octet. La colonne texte1, quant à elle, est stocké sur 50 octets (CHAR(50) = 50 octets) et commence au 6ème octet (longueur de la colonne id + offset de départ). La longueur totale d&rsquo;une ligne fait donc 56 octets pour le moment.</font></p>
<p><font size="2"></font></p>
<p><u><font size="2">Augmentation de la longueur d&rsquo;une colonne</font></u></p>
<p><font size="2">Réduisons la longueur de la colonne id en changeant son type en INT. Le stockage d&rsquo;une colonne de type INT prend 4 octets.</font></p>
<blockquote><p><font size="2"><strong>ALTER TABLE T_TEST          <br />ALTER COLUMN id INT;           <br />GO</strong></font></p>
</blockquote>
<p><font size="2">Regardons l&rsquo;impact au niveau interne à l&rsquo;aide de notre script :</font></p>
<p><img src="http://mikedavem.developpez.com/blog/alter_table/alter_table2.jpg" alt="alter table" title="alter table" /></p>
<p><font size="2">On constate tout d&rsquo;abord que la colonne id ne commence plus à l&rsquo;offset 4 mais à l&rsquo;offset 56. Ensuite, on remarque que la longueur de la colonne a bien changé (mx_inrow_length = 4). Que sont devenus les données présentes tout à l&rsquo;heure à l&rsquo;offset 4 ? Vérifions le à l&rsquo;aide des commandes DBCC IND et DBCC PAGE :</font></p>
<blockquote><p><font size="2"><strong>DBCC IND (&lsquo;Internales&rsquo;, &lsquo;T_TEST&rsquo;, -1);          <br />GO</strong> </font></p>
</blockquote>
<p><img src="http://mikedavem.developpez.com/blog/alter_table/alter_table3.jpg" alt="alter table" title="alter table" /></p>
<p><font size="2">C&rsquo;est la page 153 qui nous intéresse ici.</font></p>
<blockquote><p><font size="2"><strong>DBCC TRACEON(3604);          <br />GO           <br />DBCC PAGE(&lsquo;Internales&rsquo;, 1, 153, 3);</strong></font></p>
</blockquote>
<p><img src="http://mikedavem.developpez.com/blog/alter_table/alter_table4.jpg" alt="alter table" title="alter table" /></p>
<p><font size="2"><strong>01</strong> : status bits A       <br /><strong>00</strong> : status bits B       <br /><strong>00 3C</strong> : Longueur de la portion fixe de la ligne de données. (003C correspond à 60 en décimal)       <br /><strong>00 01</strong> : Valeur de la colonne id avant changement (correspond à 1 en décimal sur 2 octets qui correspondent au type SMALLINT)       <br /><strong>20 20 20 20</strong> &#8230; <strong>54 54 54 54</strong> : Valeur de la colonne text1 (en prenant les valeurs ASCII on retrouve bien la chaîne : TTTTTTTTTTTTTTTTTTTTTTTTT)       <br /><strong>00 00 00 01</strong> : Valeur de la colonne id après changement (correspond à 1 en décimal sur 4 octets qui correspondent bien au type INTEGER)       <br /><strong>00 03</strong> : Nombre de colonnes (3 colonnes : id (avant modification) + texte1 + id (après modification))       <br /><strong>00</strong> : Matrice des NULL (Dans ce cas pas de colonnes NULL)</font></p>
<p><font color="#ff0000" size="2">>> Lorsqu&rsquo;une colonne voit sa longueur augmentée, SQL Server ne remplace pas cette colonne avec la nouvelle longueur mais crée une nouvelle colonne avec la nouvelle longueur. L&rsquo;ancienne colonne est toujours présente mais n&rsquo;est plus visible. Par conséquent, la longueur de la ligne de données augmente. On passe dans notre cas d&rsquo;une longueur de 56 octets à une longueur de 60 octets.</font></p>
<p><font color="#ff0000" size="2"></font></p>
<p><font color="#ff0000" size="2"></font></p>
<p><font color="#000000" size="2"><u>Réduction de la longueur d&rsquo;une colonne</u></font></p>
<p><font color="#000000" size="2">Prenons maintenant le cas inverse et réduisons la longueur de la colonne id en changeant le type de données à SMALLINT.</font></p>
<blockquote><p><font size="2"><strong>ALTER TABLE T_TEST          <br />ALTER COLUMN texte1 CHAR(25);           <br />GO</strong></font></p>
</blockquote>
<p><font size="2">Regardons l&rsquo;impact au niveau interne à l&rsquo;aide de notre script :</font></p>
<p><img src="http://mikedavem.developpez.com/blog/alter_table/alter_table5.jpg" alt="alter table" title="alter table" /></p>
<p><font size="2">La longueur de la colonne texte1 a bien changé (max_inrow_length = 25) mais l&rsquo;offset de la colonne id reste inchangé. Celui-ci est toujours égale à 56. Le stockage de la colonne texte1 n&rsquo;a en fait pas changé. Il est toujours de 50 octets (leaf offset colonne texte1 = 56 &#8211;  leaf offset colonne id = 6). </font></p>
<p><font color="#ff0000" size="2">>> Lorsque la longueur d&rsquo;une colonne est diminuée, SQL Server ne réduit pas le stockage en réalité. Un contrôle est simplement réalisé sur la longueur des données (colonne max_inrow_length).</font></p>
<p><font size="2"></font></p>
<p><font size="2">Comme vous pouvez le constater, l&rsquo;impact sur le stockage interne est tout autre de ce que l&rsquo;on pense à priori. Il est important de savoir que l&rsquo;augmentation de la longueur d&rsquo;une colonne a un fort impact sur le stockage interne et qu&rsquo;à l&rsquo;inverse aucun espace de stockage n&rsquo;est récupéré. La seule façon de remettre tout ça en ordre est de créer ou reconstruire l&rsquo;index cluster de la table.</font></p>
<p><font size="2">Bonne modification de colonnes !!!</font></p>
<p><font size="2">David BARBARIN (Mikedavem)      <br />Elève ingénieur CNAM       <br />MVP SQL Server       <br /></font></p>
<p><font color="#ff0000" size="2"></font></p>
<p><font color="#ff0000" size="2"></font></p>
<p><font size="2"></font></p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>SQLProfiler : Analyse avancée de traces</title>
		<link>https://blog.developpez.com/mikedavem/p9168/sql-server-2005/architecture/sqlprofiler</link>
		<comments>https://blog.developpez.com/mikedavem/p9168/sql-server-2005/architecture/sqlprofiler#comments</comments>
		<pubDate>Sun, 01 Aug 2010 13:17:47 +0000</pubDate>
		<dc:creator><![CDATA[mikedavem]]></dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[La création d&#8217;une trace profiler est un passage quasi obligatoire lorsqu&#8217;il s&#8217;agit d&#8217;auditer les performances et les ressources monopolisées  des requêtes, lots de requêtes ou des procédures stockées qui s&#8217;exécutent sur le serveur de bases de données lors d&#8217;un audit. &#8230; <a href="https://blog.developpez.com/mikedavem/p9168/sql-server-2005/architecture/sqlprofiler">Lire la suite <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p><font size="2">La création d&rsquo;une trace profiler est un passage quasi obligatoire lorsqu&rsquo;il s&rsquo;agit d&rsquo;auditer les performances et les ressources monopolisées  des requêtes, lots de requêtes ou des procédures stockées qui s&rsquo;exécutent sur le serveur de bases de données lors d&rsquo;un audit. Bien qu&rsquo;il existe les DMV depuis la version 2005, celles-ci ne peuvent être réellement utilisées qu&rsquo;après une période significative de fonctionnement du serveur. Par conséquent il est plus intéressant de les utiliser dans un contexte de production que dans celui d&rsquo;un audit ponctuel. </font></p>
<p><font size="2">De plus, il peut exister plusieurs exécutions d&rsquo;une même requête ou procédure dans une trace profiler mais avec des paramètres différents. Une question peut alors se poser : comment connaître les durées et consommations globales des différents modèles de requêtes (indépendamment de la valeur des paramètres utilisés) et pouvoir ainsi mettre l&rsquo;accent sur l&rsquo;optimisation de certains modèles de requêtes ?</font></p>
<p><font size="2">Lors de mon dernier audit, j&rsquo;ai également dû répondre à la problématique suivante : le serveur de bases de données comportait plusieurs instances SQL Server. Dans un tel cas, comment connaitre le ratio entre la consommation d&rsquo;une requête exécutée sur une instance et celle de l&rsquo;ensemble des instances présentes sur ce même serveur ? Cela peut être utile pour cibler les requêtes ou procédures les plus consommatrices à l&rsquo;échelle du serveur et pour lesquelles il est utile de revoir la conception.</font></p>
<p><font size="2">Le script suivant permet de répondre aux deux problématiques décrites ci-dessus.</font></p>
<p><span id="more-29"></span></p>
<p><font size="2">Ce script est divisé en plusieurs parties :</font></p>
<p><font size="2">- Création d&rsquo;une fonction SQL qui permettra de concevoir les modèles de requêtes en fonction des requêtes exécutés dans la trace profiler.      <br />- Création d&rsquo;une table temporaire de travail pour effectuer les analyses nécessaires       <br />- Import des données du ou des fichiers de trace profiler       <br />- Analyse des différentes consommations de requêtes</font></p>
<p><u><font size="2">Script :</font></u></p>
<blockquote><p><font size="2"><strong>/****************************************************************          <br />* @Auteur = BARBARIN DAVID (Mikedavem)                         *           <br />* @Description =                                               *           <br />* Analyse des fichiers de trace                                *           <br />* Eléments du script :                                         *           <br />* &#8211; Fonction dbo.SQLSig : Génération des patterns de requêtes  *           <br />* &#8211; #Workload : Table de travail pour les analyses de requêtes *           <br />****************************************************************/ </strong></font></p>
<p><font size="2"><strong>USE tempdb;          <br />GO </strong></font></p>
<p>
<font size="2"><strong>/******************************************************************<br />
        <br />* Fonction de génération des modèles de requêtes&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; * </p>
<p>* Paramètres d&rsquo;entrée :&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; * </p>
<p>* &#8211; @p1 : Requête ou procédure&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; * </p>
<p>******************************************************************/ </p>
<p>IF EXISTS (SELECT * FROM sys.sql_modules </p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE object_id = OBJECT_ID(&lsquo;dbo.SQLSig&rsquo;)) </p>
<p>&#160;&#160;&#160; DROP FUNCTION dbo.SQLSig; </p>
<p>GO </strong></font></p>
<p><font size="2"><strong>&#8211; Création de la fonction de traitement des patterns<br />
        <br />CREATE FUNCTION dbo.SQLSig </p>
<p>(@p1 NVARCHAR(MAX)) </p>
<p>RETURNS NVARCHAR(MAX) </p>
<p>WITH SCHEMABINDING </p>
<p>AS </p>
<p>&#8211; Fonction réalisée par la Team SQL Server Customer Advisory </p>
<p>&#8211; Auteur = Stuart Ozer </p>
<p>BEGIN </p>
<p>DECLARE @pos INT; </p>
<p>DECLARE @mode CHAR(10); </p>
<p>DECLARE @maxlength INT; </p>
<p>DECLARE @p2 NVARCHAR(MAX); </p>
<p>DECLARE @p2i NVARCHAR(MAX); </p>
<p>DECLARE @currchar CHAR(1); </p>
<p>DECLARE @nextchar CHAR(1); </p>
<p>DECLARE @p2len INT;&#160;&#160;&#160; </strong></font></p>
<p><font size="2"><strong>SET @maxlength = LEN(RTRIM(SUBSTRING(@p1, 1, 4000))); </strong></font></p>
<p>  <font size="2"><strong>SET @pos = 1;<br />
      <br />SET @p2i = &nbsp;&raquo;; </p>
<p>SET @p2 = &nbsp;&raquo;; </p>
<p>SET @p2len = 0; </p>
<p>SET @currchar = &nbsp;&raquo;; </p>
<p>SET @nextchar = &nbsp;&raquo;; </p>
<p>SET @mode = &lsquo;command'; </strong></font></p>
<p><font size="2"><strong>&#8211; Modification David BARBARIN (pour prendre en compte sp_executesql)<br />
        <br />IF (PATINDEX(&lsquo;%sp_executesql%&rsquo;, @p1)) &gt; 0 </p>
<p>BEGIN </p>
<p>&#160;&#160;&#160; SET @pos = PATINDEX(&lsquo;%&nbsp;&raquo;,@%&rsquo;,@p1) + 2; </p>
<p>&#160;&#160;&#160; SET @p2i = SUBSTRING(@p1, 1, PATINDEX(&lsquo;%&nbsp;&raquo;,@%&rsquo;,@p1) + 1); </p>
<p>&#160;&#160;&#160; SET @p2 =&#160; SUBSTRING(@p1, PATINDEX(&lsquo;%&nbsp;&raquo;,@%&rsquo;,@p1) + 2, LEN(@p1)); </p>
<p>END </strong></font></p>
<p><font size="2"><strong>WHILE (@pos &lt;= @maxlength)<br />
        <br />BEGIN </p>
<p>&#160; SET @currchar = SUBSTRING(@p1, @pos, 1); </p>
<p>&#160; SET @nextchar = SUBSTRING(@p1, @pos + 1, 1); </p>
<p>&#160; IF @mode = &lsquo;command&rsquo; </p>
<p>&#160; BEGIN </p>
<p>&#160;&#160; SET @p2 = LEFT(@p2, @p2len) + @currchar; </p>
<p>&#160;&#160; SET @p2len = @p2len + 1; </p>
<p>&#160;&#160; IF @currchar IN (&lsquo;,&rsquo;,'(&lsquo;,&rsquo; &lsquo;,&rsquo;=&rsquo;,'&lt;&lsquo;,&rsquo;&gt;&rsquo;,&rsquo;!&rsquo;) </p>
<p>&#160;&#160;&#160;&#160;&#160;&#160; AND @nextchar BETWEEN &lsquo;0&rsquo; AND &lsquo;9&rsquo; </p>
<p>&#160;&#160; BEGIN </p>
<p>&#160;&#160;&#160; SET @mode = &lsquo;number'; </p>
<p>&#160;&#160;&#160; SET @p2 = LEFT(@p2, @p2len) + &lsquo;#'; </p>
<p>&#160;&#160;&#160; SET @p2len = @p2len + 1; </p>
<p>&#160;&#160; END </p>
<p>&#160;&#160; IF @currchar = &nbsp;&raquo;&nbsp;&raquo; </p>
<p>&#160;&#160; BEGIN </p>
<p>&#160;&#160;&#160; SET @mode = &lsquo;literal'; </p>
<p>&#160;&#160;&#160; SET @p2 = LEFT(@p2, @p2len) + &lsquo;#&nbsp;&raquo;'; </p>
<p>&#160;&#160;&#160; SET @p2len = @p2len + 2; </p>
<p>&#160;&#160; END </p>
<p>&#160; END </p>
<p>&#160; ELSE IF @mode = &lsquo;number&rsquo; AND @nextchar IN (&lsquo;,&rsquo;,&rsquo;)&rsquo;,&rsquo; &lsquo;,&rsquo;=&rsquo;,'&lt;&lsquo;,&rsquo;&gt;&rsquo;,&rsquo;!&rsquo;) </p>
<p>&#160;&#160; SET @mode = &lsquo;command'; </p>
<p>&#160; ELSE IF @mode = &lsquo;literal&rsquo; AND @currchar = &nbsp;&raquo;&nbsp;&raquo; </p>
<p>&#160;&#160; SET @mode = &lsquo;command&rsquo; </strong></font></p>
<p><font size="2"><strong>&#160; SET @pos = @pos + 1;<br />
        <br />END </p>
<p>RETURN @p2i + @p2; </p>
<p>END; </p>
<p>GO </strong></font>
</p>
<p><font size="2"><strong>/******************************************************************          <br />* Création de la table de travail pour les analyses              *           <br />******************************************************************/           <br />IF EXISTS(SELECT * FROM tempdb.sys.objects           <br />          WHERE object_id = OBJECT_ID(&lsquo;tempdb.dbo.#workload&rsquo;))           <br />    DROP TABLE #workload; </strong></font></p>
<p><font size="2"><strong>CREATE TABLE #workload          <br />(           <br />ServerName VARCHAR(50),           <br />TextData VARCHAR(MAX),           <br />DatabaseName VARCHAR(100),           <br />ApplicationName VARCHAR(100),           <br />Duration INT,           <br />Reads INT,           <br />Writes INT,           <br />CPU INT,           <br />Template AS dbo.SQLSig(TextData) PERSISTED,           <br />CheckSum_Template AS CHECKSUM(dbo.SQLSig(TextData)) PERSISTED           <br />);           <br />GO </strong></font></p>
<p><font size="2"><strong>&#8211; Création index cluster sur la table de travail          <br />CREATE CLUSTERED INDEX idx_clust_workload           <br />ON #workload           <br />(           <br />CheckSum_Template           <br />);           <br />GO </strong></font></p>
<p><font size="2"><strong>/******************************************************************          <br />* Import des données des fichiers de trace                       *           <br />* Paramètres d&rsquo;entrée :                                          *           <br />* &#8211; @path_trc : Chemin du fichier de trace à importer            *           <br />******************************************************************/           <br />DECLARE @path_trc VARCHAR(100);           <br />SET @path_trc = &lsquo;path_fichier_trace'; </strong></font></p>
<p><font size="2"><strong>INSERT INTO #workload (ServerName, TextData, DatabaseName, ApplicationName, Duration, Reads, Writes, CPU)          <br />SELECT ServerName, TextData, DatabaseName, ApplicationName, Duration, Reads, Writes, CPU           <br />FROM fn_trace_gettable(@path_trc, NULL);           <br />GO </strong></font></p>
<p><font size="2"><strong>/******************************************************************          <br />* Requête d&rsquo;analyse par pattern des données du fichier de trace  *           <br />* Paramètres de tri : @classement                                *           <br />* = reads    = Classement IO lectures                            *           <br />* = writes   = Classement IO écritures                           *           <br />* = CPU      = Classement par consommation CPU                   *           <br />* = Duration = Classement par durée d&rsquo;exécution                  *           <br />******************************************************************/ </strong></font></p>
<p><font size="2"><strong>DECLARE @classement VARCHAR(10)          <br />SET @classement = &lsquo;duration'; &#8212; Type de classement : duration, cpu, reads or writes </strong></font></p>
<p><font size="2"><strong>WITH total_workload_by_server_checksum_template          <br />AS           <br />(           <br />    SELECT           <br />        CheckSum_Template,           <br />        ServerName,           <br />        COUNT(*) AS nb_executions,           <br />        SUM(CAST(duration AS BIGINT)) AS total_duration_by_template_server,           <br />        SUM(CAST(CPU AS BIGINT)) AS total_cpu_by_template_server,           <br />        SUM(CAST(Reads AS BIGINT)) AS total_reads_by_template_server,           <br />        SUM(CAST(Writes AS BIGINT)) AS total_writes_by_template_server           <br />    FROM #workload           <br />    GROUP BY ServerName, CheckSum_Template           <br />),           <br />total_workload_total_servers           <br />AS           <br />(           <br />     SELECT           <br />        W.*,           <br />        W.total_duration_by_template_server * 100.0 / S.duration_total_server AS percent_duration_template_server,           <br />        W.total_cpu_by_template_server * 100.0 / S.total_cpu_total_server AS percent_cpu_template_server,           <br />        W.total_reads_by_template_server * 100.0 / S.total_reads_server AS percent_reads_template_server,           <br />        W.total_writes_by_template_server * 100.0 / S.total_writes_server AS percent_writes_template_server,           <br />        W.total_duration_by_template_server * 100.0 / T.duration_total AS percent_duration_total,           <br />        W.total_cpu_by_template_server * 100.0 / T.cpu_total AS percent_cpu_total,           <br />        W.total_reads_by_template_server * 100.0 / T.reads_total AS percent_reads_total,           <br />        W.total_writes_by_template_server * 100.0 / T.writes_total AS percent_writes_total           <br />     FROM total_workload_by_server_checksum_template AS W           <br />     INNER JOIN           <br />     (           <br />        SELECT           <br />            ServerName,           <br />            SUM(CAST(duration AS BIGINT)) AS duration_total_server,           <br />            SUM(CAST(CPU AS BIGINT)) AS total_cpu_total_server,           <br />            SUM(CAST(Reads AS BIGINT))  AS total_reads_server,           <br />            SUM(CAST(Writes AS BIGINT)) AS total_writes_server           <br />        FROM #workload           <br />        GROUP BY ServerName           <br />     ) AS S           <br />     ON S.ServerName = W.ServerName           <br />     CROSS JOIN           <br />     (           <br />        SELECT           <br />            SUM(CAST(duration AS BIGINT)) AS duration_total,           <br />            SUM(CAST(CPU AS BIGINT)) AS cpu_total,           <br />            SUM(CAST(Reads AS BIGINT)) AS reads_total,           <br />            SUM(CAST(Writes AS BIGINT))  AS writes_total           <br />        FROM #workload           <br />     ) AS T           <br />),           <br />rank_template_workload_by_type           <br />AS           <br />(           <br />SELECT           <br />    *,           <br />    RANK() OVER(ORDER BY percent_duration_total DESC) AS rank_duration,           <br />    RANK() OVER(ORDER BY percent_cpu_total DESC) AS rank_cpu,           <br />    RANK() OVER(ORDER BY percent_reads_total DESC) AS rank_reads,           <br />    RANK() OVER(ORDER BY percent_writes_total DESC) AS rank_writes,           <br />    RANK() OVER(ORDER BY percent_duration_template_server DESC) AS rank_duration_server,           <br />    RANK() OVER(ORDER BY percent_cpu_template_server DESC) AS rank_cpu_server,           <br />    RANK() OVER(ORDER BY percent_reads_template_server DESC) AS rank_reads_server,           <br />    RANK() OVER(ORDER BY percent_writes_template_server DESC) AS rank_writes_server           <br />FROM total_workload_total_servers           <br />)           <br />SELECT           <br />W.ServerName,           <br />W.Template,           <br />R.nb_executions,           <br />R.percent_duration_total,           <br />R.percent_duration_template_server,           <br />R.percent_cpu_total,           <br />R.percent_cpu_template_server,           <br />R.percent_reads_total,           <br />R.percent_reads_template_server,           <br />R.percent_writes_total,           <br />R.percent_writes_template_server,           <br />R.total_duration_by_template_server AS total_Duration,           <br />R.total_cpu_by_template_server AS total_CPU,           <br />R.total_reads_by_template_server AS total_Reads,           <br />R.total_writes_by_template_server AS total_Writes,           <br />W.TextData,           <br />W.Duration,           <br />W.CPU,           <br />W.Reads,           <br />W.Writes           <br />FROM #workload AS W           <br />INNER JOIN rank_template_workload_by_type AS R           <br />ON W.CheckSum_Template = R.CheckSum_Template           <br />AND W.ServerName = R.ServerName           <br />&#8211;Récupération des requêtes les plus consommatrices (>= 5%)           <br />WHERE (@classement = &lsquo;duration&rsquo; AND R.percent_duration_total >= 5)           <br />OR (@classement = &lsquo;cpu&rsquo; AND R.percent_cpu_total >=5)           <br />  OR (@classement = &lsquo;reads&rsquo; AND R.percent_reads_total >= 5)           <br />   OR (@classement = &lsquo;writes&rsquo; AND R.percent_writes_total >= 5)           <br />ORDER BY CASE @classement           <br />          WHEN &lsquo;duration&rsquo; THEN R.rank_duration           <br />          WHEN &lsquo;cpu&rsquo; THEN R.rank_cpu           <br />          WHEN &lsquo;reads&rsquo; THEN R.rank_reads           <br />          WHEN &lsquo;writes&rsquo; THEN R.rank_writes           <br />         END,           <br />         CASE @classement           <br />          WHEN &lsquo;duration&rsquo; THEN R.rank_duration_server           <br />          WHEN &lsquo;cpu&rsquo; THEN R.rank_cpu_server           <br />          WHEN &lsquo;reads&rsquo; THEN R.rank_reads_server           <br />          WHEN &lsquo;writes&rsquo; THEN R.rank_writes_server           <br />         END,           <br />         CASE @classement           <br />          WHEN &lsquo;duration&rsquo; THEN W.Duration           <br />          WHEN &lsquo;cpu&rsquo; THEN W.CPU           <br />          WHEN &lsquo;reads&rsquo; THEN W.Reads           <br />          WHEN &lsquo;writes&rsquo; THEN W.writes           <br />         END DESC;</strong></font></p>
</blockquote>
<p><u><font size="2">Remarque :</font></u></p>
<p><font size="2">La fonction de traitement des requêtes pour générer les modèles associés peut poser un problème si le fichier de trace à important est trop important. Par habitude, je préfère importer plusieurs fichiers de trace à volumétrie moyenne qu&rsquo;un seul gros fichier à la fois.</font></p>
<p><font size="2">Bon audit de trace !</font></p>
<p><font size="2">David BARBARIN (Mikedavem)      <br />Elève ingénieur CNAM Lyon       <br />MVP SQL Server</font></p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Histoire de journal : Fichiers multiples et journal des transactions</title>
		<link>https://blog.developpez.com/mikedavem/p9135/sql-server-2005/architecture/histoire_de_journal_fichiers_multiples_e</link>
		<comments>https://blog.developpez.com/mikedavem/p9135/sql-server-2005/architecture/histoire_de_journal_fichiers_multiples_e#comments</comments>
		<pubDate>Tue, 20 Jul 2010 19:50:12 +0000</pubDate>
		<dc:creator><![CDATA[mikedavem]]></dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[Dans la plupart des cas, une base de données comporte un seul fichier journal. Il peut arriver qu&#8217;il soit nécessaire de rajouter un ou plusieurs fichiers au journal des transactions à cause d&#8217;un manque d&#8217;espace libre sur une partition par &#8230; <a href="https://blog.developpez.com/mikedavem/p9135/sql-server-2005/architecture/histoire_de_journal_fichiers_multiples_e">Lire la suite <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p><font size="2">Dans la plupart des cas, une base de données comporte un seul fichier journal. Il peut arriver qu&rsquo;il soit nécessaire de rajouter un ou plusieurs fichiers au journal des transactions à cause d&rsquo;un manque d&rsquo;espace libre sur une partition par exemple. Comment se remplit le journal dans ce cas ? Comment s&rsquo;effectue l&rsquo;allocation de nouveaux VLF dans plusieurs fichiers ? C&rsquo;est ce que nous verrons dans ce billet.</font></p>
<p><span id="more-28"></span></p>
<p><font size="2">Revenons un bref instant sur les VLF (Virtual Log Files). Un VLF est l&rsquo;unité de travail pour le journal des transactions. Sa taille est déterminée par SQL Server en fonction de la taille totale du journal des transactions et de sa valeur d&rsquo;incrément. A la création d&rsquo;une base de données, le nombre de VLF d&rsquo;un fichier journal est toujours compris entre 2 et 16. Nous pouvons le vérifier en créant une base de données nommée <em>test_jnl</em> avec le script suivant :</font></p>
<blockquote><p><strong><font size="2">USE [master];          <br />GO </font></strong></p>
<p><strong><font size="2">IF  EXISTS (SELECT name FROM sys.databases WHERE name = N&rsquo;test_jnl&rsquo;)          <br />DROP DATABASE test_jnl;           <br />GO </font></strong></p>
<p><strong><font size="2">CREATE DATABASE test_jnl          <br />ON  PRIMARY           <br />( NAME = N&rsquo;test&rsquo;, FILENAME = N&rsquo;E:\MSSQLSERVER\MSSQL10.MSSQLSERVER\MSSQL\DATA\test.mdf&rsquo; ,           <br />  SIZE = 10MB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB)           <br />LOG ON           <br />( NAME = N&rsquo;test_log&rsquo;, FILENAME = N&rsquo;F:\MSSQLSERVER\test_log.LDF&rsquo; ,           <br />  SIZE = 576KB , FILEGROWTH = 0),           <br />( NAME = N&rsquo;test_log_2&prime;, FILENAME = N&rsquo;F:\MSSQLSERVER\test_log_2.ldf&rsquo; ,           <br />  SIZE = 5MB , MAXSIZE = 2048GB , FILEGROWTH = 0)           <br />GO</font></strong></p>
</blockquote>
<p><font size="2"></font></p>
<p><font size="2"></font></p>
<p><font size="2">Le script précédent créé une base de données composée d&rsquo;un fichier de données de 10MB et de deux fichiers journaux. Le premier possède une taille de 576KB et l&rsquo;accroissement automatique est désactivé. Le deuxième fichier possède une taille de 5MB avec un accroissement automatique également désactivé.</font></p>
<p><font size="2">Nous pouvons voir le nombre de VLF créé pour chaque fichier journal à l&rsquo;aide de la commande DBCC LOGINFO :</font></p>
<blockquote><p><strong><font size="2">DBCC LOGINFO(&lsquo;test_jnl&rsquo;);</font></strong></p>
</blockquote>
<p><font size="2">qui donne le résultat suivant :</font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredejournalJournauxdetransactionse_136B4/vlf_1_2.jpg"><font size="2"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="vlf_1" border="0" alt="vlf_1" src="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredejournalJournauxdetransactionse_136B4/vlf_1_thumb.jpg" width="454" height="149" /></font></a><font size="2"> </font></p>
<p><font size="2">Le 1er fichier (FileId=2) est composé de 2 VLF tandis que le 2ème fichier est composé, quant à lui, de 4 VLF. Une colonne intéressante est la colonne <strong><em>status</em></strong>. Celle-ci nous indique si un VLF est libre (valeur 0) ou utilisé (valeur 2).</font></p>
<p><font size="2">Créons à présent une table nommée <em>test</em> :</font></p>
<blockquote><p><strong><font size="2">USE test_jnl; </font></strong></p>
<p><strong><font size="2">CREATE TABLE test          <br />(           <br />id INT NOT NULL,           <br />texte VARCHAR(50) NOT NULL           <br />);</font></strong></p>
</blockquote>
<p><font size="2">Effectuons une sauvegarde de la base de données <em>test_jnl</em> pour avoir un point de référence servant aux LSN des transactions du journal. La base de données est en mode de récupération complet.</font></p>
<blockquote><p><strong><font size="2">BACKUP DATABASE test_jnl TO DISK = &lsquo;F:\MSSQLSERVER\BACKUP\TEST_JNL.BAK';          <br />GO</font></strong></p>
</blockquote>
<p><font size="2">Insérons une première ligne de données dans la table <em>test</em> :</font></p>
<blockquote><p><font size="2"><strong>INSERT test VALUES (1, REPLICATE(&lsquo;T&rsquo;, 50));</strong> </font></p>
</blockquote>
<p><font size="2">Le jeu de données suivant pourra être joué plusieurs fois afin de pouvoir observer le remplissage du journal et l&rsquo;utilisation des VLF :</font></p>
<blockquote><p><strong><font size="2">INSERT test          <br />SELECT * FROM test;</font></strong></p>
</blockquote>
<p><font size="2">Voici le résultat de la commande DBCC LOG après avoir joué le script d&rsquo;insertion de données avec un total de 1024 lignes :</font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredejournalJournauxdetransactionse_136B4/vlf_2_2.jpg"><font size="2"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="vlf_2" border="0" alt="vlf_2" src="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredejournalJournauxdetransactionse_136B4/vlf_2_thumb.jpg" width="457" height="150" /></font></a><font size="2"> </font></p>
<p><font size="2">Le 2ème VLF du premier fichier est maintenant utilisé (status = 2). Continuons le remplissage de la table en jouant à nouveau le script d&rsquo;insertion. Voici maintenant le résultat de la commande DBCC LOG après l&rsquo;insertion de 2048 lignes de données :</font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredejournalJournauxdetransactionse_136B4/vlf_3_2.jpg"><font size="2"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="vlf_3" border="0" alt="vlf_3" src="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredejournalJournauxdetransactionse_136B4/vlf_3_thumb.jpg" width="455" height="152" /></font></a><font size="2"> </font></p>
<p><font size="2">Continuons encore à remplir la table <em>test</em> avec notre script d&rsquo;insertion. Après l&rsquo;insertion de 8192 lignes de données le message d&rsquo;erreur suivant apparaît pour indiquer que le journal des transactions est plein. </font></p>
<p><font color="#ff0000" size="1">Msg 9002, Niveau 17, État 2, Ligne 1      <br />Le journal des transactions de la base de données &lsquo;test_jnl&rsquo; est plein. Pour savoir pourquoi il est impossible de réutiliser de l&rsquo;espace dans le journal, consultez la colonne log_reuse_wait_desc dans sys.databases.</font></p>
<p><font size="2">Nous avons limité la croissance des 2 fichiers journaux en désactivant leur expansion automatique. Ceci explique pourquoi notre journal des transactions est plein. Regardons l&rsquo;état des VLF :</font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredejournalJournauxdetransactionse_136B4/vlf_4_2.jpg"><font size="2"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="vlf_4" border="0" alt="vlf_4" src="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredejournalJournauxdetransactionse_136B4/vlf_4_thumb.jpg" width="443" height="148" /></font></a><font size="2"> </font></p>
<p><font size="2">On constate que tous les VLF sont utilisés (status=2), ce qui confirme une nouvelle fois le message d&rsquo;erreur précédent. </font></p>
<p><font size="2">On constate après cette série de test que le remplissage du journal s&rsquo;effectue toujours de manière séquentielle et dans un seul fichier à la fois. SQL Server considère le journal des transactions comme un conteneur unique même lorsque celui-ci est composé de plusieurs fichiers. Un seul VLF est utilisé à la fois. Il en va de même pour les fichiers : le 1er fichier journal est d&rsquo;abord utilisé. Lorsque celui-ci est rempli, SQL Server passe au 2ème fichier et ainsi de suite si d&rsquo;autres fichiers existent. Nous n&rsquo;avons pas d&rsquo;écritures parallèles. C&rsquo;est la raison pour laquelle qu&rsquo;il est inutile de multiplier les fichiers journaux pour des raisons de performances. </font></p>
<p><font size="2">Nous avons vu précédemment le cas où la taille de chaque fichier était limitée, sans possibilité d&rsquo;accroissement. Mais que se passe t&rsquo;il maintenant lorsque nous autorisons l&rsquo;expansion automatique de ces fichiers ? Le script suivant initialise le paramètre FILEGROWTH de chaque fichier journal à 10%.</font></p>
<blockquote><p><strong><font size="2">ALTER DATABASE test_jnl          <br />MODIFY FILE (NAME = test_log, FILEGROWTH = 10%);           <br />GO           <br />ALTER DATABASE test_jnl           <br />MODIFY FILE (NAME = test_log_2, FILEGROWTH = 10%);           <br />GO</font></strong></p>
</blockquote>
<p><font size="2">Continuons à remplir la table <em>test</em> avec la requête suivante :</font></p>
<blockquote><p><strong><font size="2">INSERT test          <br />SELECT TOP 10 * FROM test;</font></strong></p>
</blockquote>
<p><font size="2">Voici maintenant le résultat de la commande DBCC LOG après l&rsquo;insertion des 10 lignes de données dans la table <em>test</em> :</font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredejournalJournauxdetransactionse_136B4/vlf_5_4.jpg"><font size="2"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="vlf_5" border="0" alt="vlf_5" src="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredejournalJournauxdetransactionse_136B4/vlf_5_thumb_1.jpg" width="478" height="161" /></font></a><font size="2"> </font></p>
<p><font size="2">Un nouveau VLF a été alloué pour le 1er fichier (FileId=2). Après une autre série d&rsquo;insertion de données dans la table <em>test, </em>on peut remarquer ceci :</font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredejournalJournauxdetransactionse_136B4/vlf_6_4.jpg"><font size="2"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="vlf_6" border="0" alt="vlf_6" src="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredejournalJournauxdetransactionse_136B4/vlf_6_thumb_1.jpg" width="475" height="198" /></font></a><font size="2"> </font></p>
<p><font size="2">Le VLF précédemment alloué au 1er fichier est utilisé et plein. SQL Server doit donc à nouveau allouer d&rsquo;autres VLF et il le fait maintenant sur le deuxième fichier. 2 nouveaux VLF sont alloués dans le 2ème fichier. </font></p>
<p><font size="2">On continue toujours une série d&rsquo;insertion de données dans la table <em>test.</em> L&rsquo;état des VLF est maintenant le suivant :</font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredejournalJournauxdetransactionse_136B4/vlf_7_2.jpg"><font size="2"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="vlf_7" border="0" alt="vlf_7" src="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredejournalJournauxdetransactionse_136B4/vlf_7_thumb.jpg" width="478" height="217" /></font></a><font size="2"> </font></p>
<p><font size="2">Les VLF alloués au deuxième fichier ont été utilisés et une nouvelle allocation de VLF est effectuée mais dans le premier fichier.</font></p>
<p><font size="2">Vous l&rsquo;aurez compris, lorsqu&rsquo;aucune limitation d&rsquo;expansion de fichier n&rsquo;est paramétrée, SQL Server alloue et utilise les VLF des fichiers tour à tour. Encore une fois, il n&rsquo;y a pas d&rsquo;écriture parallèle dans les fichiers journaux. Chaque fichier est utilisé l&rsquo;un après l&rsquo;autre. </font></p>
<p><font size="2">Avec la requête suivante on peut voir l&rsquo;ordre d&rsquo;utilisation des fichiers et de leur VLF :</font></p>
<blockquote><p><strong><font size="2">USE test_jnl; </font></strong></p>
<p><strong><font size="2">IF EXISTS (SELECT * FROM tempdb.sys.objects AS o WHERE o.object_id = OBJECT_ID(&lsquo;tempdb.dbo.#t&rsquo;))          <br />DROP TABLE #t; </font></strong></p>
<p><strong><font size="2">CREATE TABLE #t          <br />(           <br />FileId INT,           <br />FileSize BIGINT,           <br />StartOffset INT,           <br />FSeqNo INT,           <br />STATUS TINYINT,           <br />Parity INT,           <br />CreateLSN NUMERIC(38)           <br />);           <br />INSERT INTO #t           <br />EXEC(&lsquo;DBCC LOGINFO(&nbsp;&raquo;test_jnl&nbsp;&raquo;)&rsquo;);           <br />SELECT           <br />*           <br />FROM #t           <br />ORDER BY FSeqNo;</font></strong></p>
</blockquote>
<p><font size="2">Le résultat est le suivant :</font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredejournalJournauxdetransactionse_136B4/image_2.png"><font size="2"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredejournalJournauxdetransactionse_136B4/image_thumb.png" width="527" height="239" /></font></a><font size="2"> </font></p>
<p><font size="2"></font></p>
<p><font size="2"></font></p>
<p><font size="2"></font></p>
<p><font size="2"></font></p>
<p><font size="2"></font></p>
<p><font size="2"></font></p>
<p><font size="2"></font></p>
<p><font size="2"></font></p>
<p><font size="2">On retrouve ce que l&rsquo;on a vu précédemment. Le premier fichier journal (FileId = 2) a été utilisé suivi du 2ème fichier journal. Après avoir autorisé l&rsquo;auto-expansion des fichiers journaux, un nouveau VLF a été alloué et utilisé pour le premier fichier. Enfin, une nouvelle allocation de VLF a été réalisée pour le second fichier. Vous pouvez faire le tester pour voir si la future allocation s&rsquo;effectue bien dans le premier fichier journal.</font></p>
<p><font size="2">David BARBARIN (Mikedavem)     <br />Elève ingénieur CNAM      <br />MVP SQL Server</font></p>
<p><font size="2"></font></p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Log shipping : Comment déplacer les fichiers journaux d’une base de données ?</title>
		<link>https://blog.developpez.com/mikedavem/p9132/sql-server-2005/architecture/log_shipping_comment_deplacer_les_fichie</link>
		<comments>https://blog.developpez.com/mikedavem/p9132/sql-server-2005/architecture/log_shipping_comment_deplacer_les_fichie#comments</comments>
		<pubDate>Mon, 19 Jul 2010 09:32:45 +0000</pubDate>
		<dc:creator><![CDATA[mikedavem]]></dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[Il est parfois nécessaire de déplacer certains fichiers journaux d&#8217;une base de données. Les causes peuvent être multiples : changement dans l&#8217;architecture du sous-système disque, espace disque insuffisant . Cette opération est, dans un cas classique, relativement simple mais lorsqu&#8217;il &#8230; <a href="https://blog.developpez.com/mikedavem/p9132/sql-server-2005/architecture/log_shipping_comment_deplacer_les_fichie">Lire la suite <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p><font size="2">Il est parfois nécessaire de déplacer certains fichiers journaux d&rsquo;une base de données. Les causes peuvent être multiples : changement dans l&rsquo;architecture du sous-système disque, espace disque insuffisant . Cette opération est, dans un cas classique, relativement simple mais lorsqu&rsquo;il s&rsquo;agit d&rsquo;une topologie log-shipping cela peut compliquer un peu les choses. Nous verrons dans ce billet comment déplacer les fichiers journaux d&rsquo;une base de données selon si l&rsquo;on se trouve sur le primaire ou le secondaire.</font></p>
<p><span id="more-27"></span></p>
<p><font size="2">Tout d&rsquo;abord il est important de préciser qu&rsquo;il n&rsquo;est absolument pas nécessaire de reconstruire une configuration log-shipping lorsqu&rsquo;il s&rsquo;agit de déplacer des fichiers de bases de données en général. En effet, l<font size="2">e log-shipping est indépendant de la topologie des fichiers de bases de données. </font>Dans ce billet nous prendrons simplement l&rsquo;exemple des fichiers journaux d&rsquo;une base mais la manipulation reste la même pour n&rsquo;importe quel type de fichier.</font></p>
<p><font size="2">1- Dans le cas d&rsquo;une base sur le primaire les étapes à suivre sont les suivantes :</font><font size="2">      </p>
<p>- Arrêter le job de sauvegarde du journal de la base de données concernée       <br />- Détacher la base de données avec la procédure stockée système <strong>sp_detach</strong>       <br />- Déplacer le(s) fichier(s) journaux concerné(s)       <br />- Rattacher la base de données concernée avec la procédure stockée système <strong>sp_attach</strong>       <br />- Réactiver le job de sauvegarde du journal de la base de données concernée </font></p>
</p>
<p><font size="2">2- Dans le cas d&rsquo;une base de données sur le secondaire, la manipulation est un peu différente car la base de données est dans un mode de fonctionnement différent (NORECOVEY ou STANDBY). Par conséquent, on ne peut pas utiliser les procédures stockées systèmes permettant de détacher et de rattacher une base de données. Les étapes à suivre dans ce cas sont les suivantes :</font></p>
<p><font size="2">- Si la base de données est en mode de fonctionnement STANDBY, il faut la passer en mode <strong>NORECOVERY</strong>.       <br />- Arrêter les jobs de copie et de restauration des fichiers journaux pour la base de données concernée       <br />- Utiliser la commande <strong>ALTER DATABASE MODIFY FILE (NAME = logicalName, FILENAME = &lsquo;filePath&rsquo;)</strong>       <br />- Arrêter le secondaire (instance)       <br />- Déplacer les fichiers journaux concernés dans le chemin défini avec la commande </font><font size="2"><strong>ALTER DATABASE        <br /></strong>- Redémarrer le secondaire (instance)       <br />- Vérifier les nouveaux emplacements de fichier à l&rsquo;aide de la DMV <strong>sys.master_files</strong>       <br />- Si la base de données était en mode de fonctionnement STANDBY, il faudra exécuter la commande <strong>RESTORE DATABASE</strong> avec l&rsquo;option <strong>STANDBY</strong>       <br />- Réactiver les jobs de copie et de restauration des journaux de la base de données concernée       <br /></font></p>
<p><font size="2">Bon déplacement de fichiers !!</font></p>
<p><font size="2">David BARBARIN (Mikedavem)      <br />Elève ingénieur CNAM LYON       <br />MVP SQL Server</font></p>
<p><font size="2"></font></p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Histoire d&#8217;index : Stockage interne des index avec colonnes incluses</title>
		<link>https://blog.developpez.com/mikedavem/p9088/sql-server-2005/architecture/histoire_d_index_stockage_interne_des_in</link>
		<comments>https://blog.developpez.com/mikedavem/p9088/sql-server-2005/architecture/histoire_d_index_stockage_interne_des_in#comments</comments>
		<pubDate>Tue, 06 Jul 2010 12:07:42 +0000</pubDate>
		<dc:creator><![CDATA[mikedavem]]></dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[Un collègue m&#8217;a demandé il y a quelques temps, quelle était la différence entre un index non cluster classique et un index non cluster avec colonnes incluses ? Cette question est venue suite à la création de ce type d&#8217;index &#8230; <a href="https://blog.developpez.com/mikedavem/p9088/sql-server-2005/architecture/histoire_d_index_stockage_interne_des_in">Lire la suite <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p><font size="2">Un collègue m&rsquo;a demandé il y a quelques temps, quelle était la différence entre un index non cluster classique et un index non cluster avec colonnes incluses ? Cette question est venue suite à la création de ce type d&rsquo;index (avec colonnes incluses) pour couvrir certaines colonnes demandées par une requête et éviter ainsi une recherche par pointeur sur l&rsquo;index cluster qui pouvait être contre performant dans ce cas car elle concernait une table très volumineuse. Dans ce billet, on verra quelle est la différence entre ces deux types d&rsquo;index et comment SQL Server stocke en interne les index avec colonnes incluses.</font></p>
<p><span id="more-26"></span></p>
<p><font size="2">On prendra comme exemple une table nommée <strong><em>Person.Address</em></strong> de la base de données bien connue <strong><em>AdventureWorks</em></strong>. Nous voulons satisfaire la requête suivante :</font></p>
<blockquote><p><font size="2"><strong>USE AdventureWorks;          <br />GO </strong></font></p>
<p><font size="2"><strong>SELECT          <br />    AddressLine1,           <br />    AddressLine2,           <br />    City,           <br />    StateProvinceID,           <br />    PostalCode           <br />FROM Person.Address           <br />WHERE PostalCode BETWEEN N&rsquo;98000&prime; and N&rsquo;99999&prime;;</strong></font></p>
</blockquote>
<p><font size="2">Pour cela nous allons créé deux index :</font></p>
<p><font size="2">- Un index non cluster classique :</font></p>
<blockquote><p><font size="2"><strong>&#8211; Index non cluster classique          <br />CREATE INDEX IX_Address_PostalCode     <br />ON Person.Address (PostalCode,AddressLine1, AddressLine2, City, StateProvinceID);           <br />GO</strong> </font></p>
</blockquote>
<p><font size="2">- Un index non cluster avec colonnes incluses :</font></p>
<blockquote><p><font size="2"><strong>&#8211; Index non cluster avec colonnes incluses          <br />CREATE INDEX IX_Address_PostalCode_Included       <br />ON Person.Address (PostalCode)       <br />INCLUDE (AddressLine1, AddressLine2, City, StateProvinceID);           <br />GO</strong> </font></p>
</blockquote>
<p><font size="2">Je vous laisse vérifier via le plan d&rsquo;exécution si ces 2 index permettent de satisfaire à la requête ci-dessus sans passer par l&rsquo;index cluster de la table.</font></p>
<p><font size="2">Regardons maintenant la taille de chaque index créé via la DMV <em>sys.dm_db_index_physical_stats</em> :</font></p>
<blockquote><p><font size="2"><strong>SELECT          <br />    i.name,           <br />    SUM(ips.page_count * 8. / 1024) AS size_in_kb           <br />FROM sys.dm_db_index_physical_stats(DB_ID(&lsquo;AdventureWorks&rsquo;), OBJECT_ID(&lsquo;Person.Address&rsquo;), NULL, NULL, &lsquo;DETAILED&rsquo;) AS ips           <br />INNER JOIN sys.indexes AS i           <br />ON i.index_id = ips.index_id           <br />AND i.object_id = ips.object_id           <br />  AND i.name LIKE &lsquo;IX_Address_PostalCode%&rsquo;           <br />GROUP BY i.name;</strong></font></p>
</blockquote>
<p><font size="2">qui donne le résultat suivant :</font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredindexStockageinternedesindexave_A999/index_included_columns_2.jpg"><font size="2"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="index_included_columns" border="0" alt="index_included_columns" src="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredindexStockageinternedesindexave_A999/index_included_columns_thumb.jpg" width="355" height="74" /></font></a><font size="2"> </font></p>
<p><font size="2"> </font></p>
<p><font size="2">La requête suivante donne plus de détails sur les 2 index créées :</font></p>
<blockquote><p><font size="2"><strong>SELECT          <br />    i.index_id,           <br />    i.name,           <br />    i.type_desc,           <br />    ips.index_depth,           <br />    ips.page_count,           <br />    ips.record_count           <br />FROM sys.dm_db_index_physical_stats(DB_ID(&lsquo;AdventureWorks&rsquo;), OBJECT_ID(&lsquo;Person.Address&rsquo;), NULL, NULL, &lsquo;DETAILED&rsquo;) AS ips           <br />INNER JOIN sys.indexes AS i           <br />ON i.index_id = ips.index_id           <br />AND i.object_id = ips.object_id           <br />  AND i.name LIKE &lsquo;IX_Address_PostalCode%';</strong></font></p>
</blockquote>
<p><font size="2">qui donne le résultat suivant :</font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredindexStockageinternedesindexave_A999/index_included_columns_2_4.jpg"><font size="2"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="index_included_columns_2" border="0" alt="index_included_columns_2" src="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredindexStockageinternedesindexave_A999/index_included_columns_2_thumb_1.jpg" width="561" height="110" /></font></a><font size="2"> </font></p>
<p><font size="2"><u>Première constatation</u> : </font></p>
<p><font size="2">L&rsquo;index non cluster (1.67 Ko) est plus volumineux que l&rsquo;index non cluster avec colonnes incluses (1.65 Ko). Un niveau supplémentaire existe pour l&rsquo;index non cluster classique. (3 contre 2 pou l&rsquo;index non cluster avec colonnes incluses).</font></p>
<p><font size="2">Pour comprendre cette différence il va falloir regarder en détails chaque type de page de chaque index à l&rsquo;aide des commandes habituelles DBCC IND et DBCC PAGE.</font></p>
<p><font size="2">1- <u>Pour les pages racines de chaque type d&rsquo;index</u> :</font></p>
<p><font size="2">- <strong>Index non cluster classique</strong> :</font></p>
<blockquote><p><font size="2"><strong>DBCC IND (&lsquo;AdventureWorks&rsquo;, &lsquo;Person.Address&rsquo;, 6);          <br />GO           <br />&#8211; Root : 11568           <br />DBCC TRACEON(3604);           <br />GO           <br />&#8211; Index IX_Address_PostalCode           <br />DBCC PAGE(&lsquo;AdventureWorks&rsquo;, 1, 11570, 3);           <br />GO</strong></font></p>
</blockquote>
<p><font size="2">qui donne le résultat suivant :</font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredindexStockageinternedesindexave_A999/index_included_columns_3_2.jpg"><font size="2"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="index_included_columns_3" border="0" alt="index_included_columns_3" src="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredindexStockageinternedesindexave_A999/index_included_columns_3_thumb.jpg" width="866" height="72" /></font></a><font size="2"> </font></p>
<p><font size="2">La page racine comporte toutes les colonnes qui composent l&rsquo;index. Ceci est le comportement attendu pour un index non cluster classique. Nous retrouvons également les pointeurs vers les pages suivantes ainsi que les signets vers la clés de l&rsquo;index cluster (AddressID). </font></p>
<p><font size="2">- <strong>Index non cluster avec colonnes incluses</strong> :</font></p>
<blockquote><p><font size="2"><strong>DBCC IND (&lsquo;AdventureWorks&rsquo;, &lsquo;Person.Address&rsquo;, 7);          <br />GO           <br />&#8211; Root : 11632           <br />&#8211; Index IX_Address_PostalCode_Included           <br />DBCC PAGE(&lsquo;AdventureWorks&rsquo;, 1, 11632, 3);           <br />GO</strong></font></p>
</blockquote>
<p><font size="2"></font></p>
<p><font size="2">qui donne le résultat suivant :</font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredindexStockageinternedesindexave_A999/index_included_columns_4_2.jpg"><font size="2"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="index_included_columns_4" border="0" alt="index_included_columns_4" src="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredindexStockageinternedesindexave_A999/index_included_columns_4_thumb.jpg" width="505" height="90" /></font></a><font size="2"> </font></p>
<p><font size="2">Ici nous n&rsquo;avons que la colonne qui compose directement l&rsquo;index non cluster. Les colonnes incluses, quant à elles, ne sont pas présentes. Nous retrouvons comme pour un index non cluster classique les pointeurs vers les pages suivantes ainsi que les signets vers la clés de l&rsquo;index cluster (AddressID).</font></p>
<p><font size="2">2- <u>Pour les pages de niveaux feuilles de chaque type d&rsquo;index</u> : (Il suffit de prendre n&rsquo;importe quel ID de page au niveau 0).</font></p>
<p><font size="2">- <strong>Index non cluster classique</strong></font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredindexStockageinternedesindexave_A999/index_included_columns_5_4.jpg"><font size="2"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="index_included_columns_5" border="0" alt="index_included_columns_5" src="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredindexStockageinternedesindexave_A999/index_included_columns_5_thumb_1.jpg" width="845" height="91" /></font></a><font size="2"> </font></p>
<p><font size="2">On retrouve un schéma classique d&rsquo;un contenu de page de niveau feuille. Il n&rsquo;existe évidemment plus de pointeurs de page car nous sommes au niveau feuille. </font></p>
<p><font size="2">- <strong>Index non cluster avec colonnes incluses</strong></font></p>
<p><a href="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredindexStockageinternedesindexave_A999/index_included_columns_6_2.jpg"><font size="2"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="index_included_columns_6" border="0" alt="index_included_columns_6" src="http://blog.developpez.com/media/269/WindowsLiveWriter/HistoiredindexStockageinternedesindexave_A999/index_included_columns_6_thumb.jpg" width="847" height="121" /></font></a><font size="2"> </font></p>
<p><font size="2">Nous avons exactement la même structure de contenu de page au niveau feuille pour un index non cluster avec colonnes incluses. Pour ce dernier, nous avons cette fois ci toutes les colonnes composants l&rsquo;index plus les colonnes incluses. </font></p>
<p><font size="2">Voilà, nous avons donc vu les différentes notoires qu&rsquo;il existait entre un index non cluster classique et un index non cluster avec colonnes incluses. Pour ce dernier seul les pages de niveau feuilles hébergent l&rsquo;ensemble des colonnes de l&rsquo;index (clé(s) d&rsquo;index + colonnes incluses), ce qui explique une quantité de stockage plus faible. Dans le cas des tables très volumineuses cela peut être très intéressant car le stockage de l&rsquo;index sera considérablement réduit et la mise à jour d&rsquo;une des colonnes incluses n&rsquo;impactera que le niveau feuille de cet index.</font></p>
<p><font size="2">Bien sûr la question qui se pose logiquement par la suite est la suivante : dans ce cas pourquoi ne pas utiliser uniquement des index non cluster avec colonnes incluses ? Je répondrais par ceci : il ne faut pas oublier une règle de base qui stipule qu&rsquo;un index doit être le plus court possible. En effet, avec un index avec colonnes incluses, même s&rsquo;il est possible de dépasser la limite des 900 octets, il est cependant déconseiller d&rsquo;inclure un trop grand nombre de colonnes. La raison en est simple : plus vous ajoutez de colonnes dans votre index plus il faudra de pages pour pouvoir héberger les données de ces mêmes colonnes bien que celles-ci se situent uniquement au niveau feuille. Il y aura donc forcément plus de pages à parcourir pour pouvoir lire les données et selon le cas il risque d&rsquo;y voir plus de split de pages lors des insertions ou mises à jour de données. Vous l&rsquo;aurez compris les performances I/O risquent d&rsquo;être impactées. La création d&rsquo;un tel index doit, à mon sens, faire l&rsquo;objet d&rsquo;une étude approfondie !!!</font></p>
<p><font size="2">David BARBARIN (Mikedavem)     <br />Elève ingénieur CNAM      <br />MVP SQL Server</font></p>
<p><font size="2"></font></p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
