<?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>Cap Data Team SGBD Blog : Oracle, SQL Server, MySQL, Sybase... &#187; SQL Server</title>
	<atom:link href="http://blog.capdata.fr/index.php/category/sqlserver/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.capdata.fr</link>
	<description>Le blog technique sur les bases de données de CAP DATA Consulting</description>
	<lastBuildDate>Wed, 01 Feb 2012 17:21:53 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Techdays !!!</title>
		<link>http://blog.capdata.fr/index.php/techdays/</link>
		<comments>http://blog.capdata.fr/index.php/techdays/#comments</comments>
		<pubDate>Wed, 01 Feb 2012 17:20:10 +0000</pubDate>
		<dc:creator>David BAFFALEUF</dc:creator>
				<category><![CDATA[SQL Server]]></category>

		<guid isPermaLink="false">http://blog.capdata.fr/?p=3153</guid>
		<description><![CDATA[Hello,
Un petit mot pour vous dire que je serai présent aux Techdays le mercredi 8 février avec Christophe Laporte (blog&#124;twitter) et François Jehl (blog) sur les stands GUSS et SQL Server pour répondre à toutes vos questions entre 2 sessions.

Venez nombreux !!
David B.Continuez votre lecture sur le blog :

Journées SQL Server 12/13 décembre (suite) (David [...]]]></description>
			<content:encoded><![CDATA[<p>Hello,</p>
<p>Un petit mot pour vous dire que je serai présent aux Techdays le mercredi 8 février avec Christophe Laporte (<a href="http://conseilit.wordpress.com/">blog</a>|<a href="http://twitter.com/Conseilit">twitter</a>) et François Jehl (<a href="http://fjehl.blogspot.com/">blog</a>) sur les stands GUSS et SQL Server pour répondre à toutes vos questions entre 2 sessions.</p>
<p style="text-align: center;"><a href="http://www.microsoft.com/france/mstechdays/"><img class="size-full wp-image-3157 aligncenter" title="FY12_techdays-kit-partenaire-inscription-250x250" src="http://blog.capdata.fr/wp-content/uploads/2012/02/FY12_techdays-kit-partenaire-inscription-250x2501.png" alt="" width="250" height="250" /></a></p>
<p>Venez nombreux !!</p>
<p>David B.<strong>Continuez votre lecture sur le blog :</strong>
<ul class="similar-posts">
<li><a href="http://blog.capdata.fr/index.php/journees-sql-server-1213-decembre-suite/" rel="bookmark" title="27 décembre 2011">Journées SQL Server 12/13 décembre (suite)</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/journees-sql-server-a-paris-les-12-et-13-decembre-2011/" rel="bookmark" title="25 octobre 2011">Journées SQL Server à Paris les 12 et 13 décembre 2011 !</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/reperer-un-package-ssis-lors-de-son-execution/" rel="bookmark" title="30 juin 2011">Repérer un package SSIS lors de son exécution</a> (Louis HOCHBERG) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/modifier-la-collation-dune-base-sql-2005/" rel="bookmark" title="30 septembre 2009">Modifier la Collation d&#8217;une base SQL 2005</a> (Louis HOCHBERG) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/reunion-guss-le-1er-juillet/" rel="bookmark" title="23 juin 2010">Réunion GUSS le 1er juillet !</a> (David BAFFALEUF) [SQL Server]</li>
</ul>
<p><!-- Similar Posts took 3.126 ms -->
<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Ftechdays%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Ftechdays%2F&amp;style=normal&amp;b=2" height="61" width="50" /><br />
			</a>
		</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.capdata.fr/index.php/techdays/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Journées SQL Server 12/13 décembre (suite)</title>
		<link>http://blog.capdata.fr/index.php/journees-sql-server-1213-decembre-suite/</link>
		<comments>http://blog.capdata.fr/index.php/journees-sql-server-1213-decembre-suite/#comments</comments>
		<pubDate>Tue, 27 Dec 2011 14:51:10 +0000</pubDate>
		<dc:creator>David BAFFALEUF</dc:creator>
				<category><![CDATA[SQL Server]]></category>

		<guid isPermaLink="false">http://blog.capdata.fr/?p=3125</guid>
		<description><![CDATA[Bilan des journées SQL Server des 12 et 13 décembre : 310 participants, 27 speakers (dont MVPs, personnes du support PSS, le Program Manager de la répli JY Devant et Bruno Aziza responsable marketing monde sur SQL Server), un gros succès qui montre que la communauté bouge aussi en France. Un gros merci aux sponsors [...]]]></description>
			<content:encoded><![CDATA[<p>Bilan des journées SQL Server des 12 et 13 décembre : 310 participants, 27 speakers (dont MVPs, personnes du support PSS, le Program Manager de la répli JY Devant et Bruno Aziza responsable marketing monde sur SQL Server), un gros succès qui montre que la communauté bouge aussi en France. Un gros merci aux sponsors et aux organisateurs <a href="http://blogs.codes-sources.com/christian/">Christian</a>, <a href="http://www.datafly.fr/">Arian </a>et <a href="http://blog.djeepy1.net/">Djeepy</a>, qui ont abattu un sacré boulot pour trouver des sponsors, des speakers et organiser tout ça. Un engouement qui devrait permettre en marge des TechDays de lancer l&#8217;idée d&#8217;une à deux manifestations de ce style par an.</p>
<p>Ce fut l&#8217;occasion aussi de permettre aux MVP et aux personnes du support (EE, PFE) de se rencontrer. On a eu la chance également de pouvoir échanger avec Jean-Yves Devant, responsable de la partie Réplication + Change tracking + CDC au niveau du dev à Redmond. La rencontre a été immortalisée dans le hall principal de MS (Merci à Marc Biarnes):</p>
<p style="text-align: center;"><a href="http://blog.capdata.fr/wp-content/uploads/2011/12/MVP_SUPPORT_SQL_Days1.jpg"><img class="aligncenter size-large wp-image-3127" title="MVP_SUPPORT_SQL_Days" src="http://blog.capdata.fr/wp-content/uploads/2011/12/MVP_SUPPORT_SQL_Days1-1024x682.jpg" alt="" width="717" height="477" /></a></p>
<p>Les sessions sont presque toutes disponibles <a href="http://www.microsoft.com/fr-fr/showcase/search.aspx?rvuuid=ffdf8aff-82cf-4378-8c6d-c509d1e1981b">en webcast </a>, ainsi que les deux keynotes (<a href="http://www.microsoft.com/fr-fr/showcase/details.aspx?uuid=b9b60a7f-fa6c-4450-88ca-09d74740c2a3">1</a> et <a href="http://www.microsoft.com/fr-fr/showcase/details.aspx?uuid=9e72c1be-0aa0-4c43-b532-4b5d79bc9c6d">2</a>). Quelques unes des sessions que je vous recommande sur la partie  moteur:</p>
<ul>
<li>Philippe Geiger (MVP): <a href="http://www.microsoft.com/fr-fr/showcase/details.aspx?uuid=a96753d8-e118-4706-b854-de8b9e53fac9">SQL Server: ses fonctionnalités méconnues donc indispensables</a>.</li>
<li>Pascal Belaud (MS): <a href="http://www.microsoft.com/fr-fr/showcase/details.aspx?uuid=c676ed0b-6c39-4fa2-9d62-f66e841781ac">Always On </a></li>
<li>Arian Papillon : <a href="http://www.microsoft.com/fr-fr/showcase/details.aspx?uuid=5523745e-6c3b-414d-9169-ef543f4ca1ac">Réussir sa migration avec SQL Server 2008 R2 ou SQL Server 2012</a>.</li>
<li>Jean-Yves Devant (MS): <a href="http://www.microsoft.com/fr-fr/showcase/details.aspx?uuid=d2ca5515-6322-49b4-997f-cee7e6bc0ea9">Synchronisation des données / réplication</a></li>
<li>Christophe Laporte (MVP,MCM): <a href="http://www.microsoft.com/fr-fr/showcase/details.aspx?uuid=557197e6-2ca5-44ec-8a0b-b91aed1519cd">Server Core et Virtualisation</a>.</li>
<li>Frédéric Pichaud (MS): <a href="http://www.microsoft.com/fr-fr/showcase/details.aspx?uuid=06e5f376-77be-489b-8089-9cefb8ad00e6">Optimisation et Troubleshooting</a>.</li>
<li>Frédéric Brouard (MVP): <a href="http://www.microsoft.com/fr-fr/showcase/details.aspx?uuid=3ed1f167-a444-418b-b032-8fe0100a9068">Contraintes et performances</a></li>
<li>David Barbarin (MVP): <a href="http://www.microsoft.com/fr-fr/showcase/details.aspx?uuid=64622d95-e07d-4200-ae8d-64139754281c">Sécurité dans SQL Server 2012</a></li>
<li>Et ma session sur le moteur XE <a href="http://www.microsoft.com/fr-fr/showcase/details.aspx?uuid=ffdf8aff-82cf-4378-8c6d-c509d1e1981b">ici</a>.</li>
</ul>
<p>A+<strong>Continuez votre lecture sur le blog :</strong>
<ul class="similar-posts">
<li><a href="http://blog.capdata.fr/index.php/journees-sql-server-a-paris-les-12-et-13-decembre-2011/" rel="bookmark" title="25 octobre 2011">Journées SQL Server à Paris les 12 et 13 décembre 2011 !</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/techdays/" rel="bookmark" title="1 février 2012">Techdays !!!</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/reperer-un-package-ssis-lors-de-son-execution/" rel="bookmark" title="30 juin 2011">Repérer un package SSIS lors de son exécution</a> (Louis HOCHBERG) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/comment-limiter-les-alertes-liees-a-la-journalisation-checkpoint-not-completed-et-compagnie/" rel="bookmark" title="8 juillet 2011">Checkpoint not complete: Comment limiter les alertes liées à la journalisation</a> (Louis PROU) [Oracle]</li>
<li><a href="http://blog.capdata.fr/index.php/msg-2508-level-16-state-1-the-in-row-data-for-object-is-incorrect/" rel="bookmark" title="10 mai 2011">Msg 2508, Level 16, State 1: the In-Row data %% for object %% is incorrect</a> (David BAFFALEUF) [SQL Server]</li>
</ul>
<p><!-- Similar Posts took 3.258 ms -->
<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fjournees-sql-server-1213-decembre-suite%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fjournees-sql-server-1213-decembre-suite%2F&amp;style=normal&amp;b=2" height="61" width="50" /><br />
			</a>
		</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.capdata.fr/index.php/journees-sql-server-1213-decembre-suite/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Journées SQL Server à Paris les 12 et 13 décembre 2011 !</title>
		<link>http://blog.capdata.fr/index.php/journees-sql-server-a-paris-les-12-et-13-decembre-2011/</link>
		<comments>http://blog.capdata.fr/index.php/journees-sql-server-a-paris-les-12-et-13-decembre-2011/#comments</comments>
		<pubDate>Tue, 25 Oct 2011 16:10:32 +0000</pubDate>
		<dc:creator>David BAFFALEUF</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[conférences]]></category>

		<guid isPermaLink="false">http://blog.capdata.fr/?p=3096</guid>
		<description><![CDATA[Un petit mot pour vous encourager vivement à venir assister aux journées SQL Server qui auront lieu les 12 et 13 décembre prochains à Paris dans les locaux de MS. Cet évènement complètement gratuit est organisé par le GUSS et paraîné par HP, Microsoft, Quest, Waisso et IT -Pro.
Nous serons une petite vingtaine de MVP, [...]]]></description>
			<content:encoded><![CDATA[<p>Un petit mot pour vous encourager vivement à venir assister aux<strong> </strong>journées SQL Server qui auront lieu<strong> les 12 et 13 décembre</strong> <strong>prochains </strong>à Paris dans les locaux de MS. Cet évènement complètement gratuit est organisé par le GUSS et paraîné par HP, Microsoft, Quest, Waisso et IT -Pro.</p>
<p>Nous serons une petite vingtaine de MVP, consultants et intervenants MS pour évoquer différents sujets autour du moteur relationnel et de la BI, et des nouveautés de la version 2012 qui sort l&#8217;année prochaine.</p>
<p>Je présenterai une session sur les XEvents en Denali CTP3 le lundi matin et serai présent sur le salon les deux jours. Ce sera surtout l&#8217;occasion de se rencontrer et d&#8217;échanger autour de SQL Server. Tous les détails sur le planning des sessions et l&#8217;organisation sont sur le site du <a href="http://www.guss.fr/accueil/les-journ%C3%A9es-sql-server.aspx">GUSS</a>.</p>
<p>Alors à très bientôt !</p>
<p>David B.</p>
<div id="_mcePaste" style="overflow: hidden; position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px;">http://www.guss.fr/accueil/les-journ%C3%A9es-sql-server.aspx</div>
<p><strong>Continuez votre lecture sur le blog :</strong>
<ul class="similar-posts">
<li><a href="http://blog.capdata.fr/index.php/journees-sql-server-1213-decembre-suite/" rel="bookmark" title="27 décembre 2011">Journées SQL Server 12/13 décembre (suite)</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/reunion-guss-le-1er-juillet/" rel="bookmark" title="23 juin 2010">Réunion GUSS le 1er juillet !</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/techdays/" rel="bookmark" title="1 février 2012">Techdays !!!</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/reperer-un-package-ssis-lors-de-son-execution/" rel="bookmark" title="30 juin 2011">Repérer un package SSIS lors de son exécution</a> (Louis HOCHBERG) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/oracle-les-rpms-et-les-dependances-avec-yum/" rel="bookmark" title="6 novembre 2009">Oracle, les Rpms plus de souci avec YUM</a> (Thierry GASCARD) [Oracle]</li>
</ul>
<p><!-- Similar Posts took 3.145 ms -->
<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fjournees-sql-server-a-paris-les-12-et-13-decembre-2011%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fjournees-sql-server-a-paris-les-12-et-13-decembre-2011%2F&amp;style=normal&amp;b=2" height="61" width="50" /><br />
			</a>
		</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.capdata.fr/index.php/journees-sql-server-a-paris-les-12-et-13-decembre-2011/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Regénérer le DDL des indexes FULL TEXT</title>
		<link>http://blog.capdata.fr/index.php/regenerer-le-ddl-des-indexes-full-text/</link>
		<comments>http://blog.capdata.fr/index.php/regenerer-le-ddl-des-indexes-full-text/#comments</comments>
		<pubDate>Wed, 12 Oct 2011 15:18:12 +0000</pubDate>
		<dc:creator>David BAFFALEUF</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[fulltext]]></category>

		<guid isPermaLink="false">http://blog.capdata.fr/?p=3052</guid>
		<description><![CDATA[Comme il n&#8217;y a pas d&#8217;outil pour le faire et que j&#8217;ai dû le faire pour un client récemment (ne fonctionne qu&#8217;à partir de SQL Server 2008+):
use mabase ;

create table #ftsDDL (catalogname sysname, objname sysname, objtype varchar(20),
colname sysname, type_column_id sysname NULL, langid int, indexname sysname,
changetracking varchar(10), stoplistname sysname)

insert into #ftsDDL
select FTC.name 'Catalog Name', T.name 'Object [...]]]></description>
			<content:encoded><![CDATA[<p>Comme il n&#8217;y a pas d&#8217;outil pour le faire et que j&#8217;ai dû le faire pour un client récemment (ne fonctionne qu&#8217;à partir de SQL Server 2008+):</p>
<pre><span style="color: #0000ff;">use mabase ;

</span><span style="color: #0000ff;">create table #ftsDDL (catalogname sysname, objname sysname, objtype varchar(20),
colname sysname, type_column_id sysname NULL, langid int, indexname sysname,
changetracking varchar(10), stoplistname sysname)

insert into #ftsDDL
select FTC.name '<span style="color: #ff0000;">Catalog Name</span>', T.name '<span style="color: #ff0000;">Object name</span>', '<span style="color: #ff0000;">Table</span>' '<span style="color: #ff0000;">Object Type</span>',
C.name '<span style="color: #ff0000;">colname</span>', FTIC.type_column_id, FTIC.language_id, I.name '<span style="color: #ff0000;">UQIndexName</span>',
FTI.change_tracking_state_desc, STP.name
from sys.fulltext_indexes FTI
inner join sys.fulltext_catalogs FTC on FTC.fulltext_catalog_id = FTI.fulltext_catalog_id
inner join sys.tables T on FTI.object_id = T.object_id
inner join sys.indexes I on I.object_id = T.object_id
inner join sys.fulltext_index_columns FTIC on FTIC.object_id = FTI.object_id
inner join sys.columns C on C.column_id = FTIC.column_id and C.object_id = FTI.object_id
inner join sys.fulltext_stoplists STP on STP.stoplist_id = FTI.stoplist_id
where I.is_unique = 1 and I.type = 1
and FTIC.type_column_id is NULL

	 UNION 

select FTC.name '<span style="color: #ff0000;">Catalog Name</span>', V.name '<span style="color: #ff0000;">Object name</span>', '<span style="color: #ff0000;">Indexed View</span>' '<span style="color: #ff0000;">Object Type</span>',
C.name '<span style="color: #ff0000;">colname</span>', FTIC.type_column_id, FTIC.language_id, I.name '<span style="color: #ff0000;">UQIndexName</span>',
FTI.change_tracking_state_desc, STP.name
from sys.fulltext_indexes FTI
inner join sys.fulltext_catalogs FTC on FTC.fulltext_catalog_id = FTI.fulltext_catalog_id
inner join sys.views V on FTI.object_id = V.object_id
inner join sys.indexes I on I.object_id = V.object_id
inner join sys.fulltext_index_columns FTIC on FTIC.object_id = FTI.object_id
inner join sys.columns C on C.column_id = FTIC.column_id and C.object_id = FTI.object_id
inner join sys.fulltext_stoplists STP on STP.stoplist_id = FTI.stoplist_id
where I.is_unique = 1 and I.type = 1
and FTIC.type_column_id is NULL
order by 1;

with cte as
 (select p1.catalogname, p1.objname, (select colname
 +case
when p2.type_column_id is NULL then '' else ' <span style="color: #ff0000;">TYPE COLUMN *put type column here*</span>' end+
' <span style="color: #ff0000;">language </span>'+cast(p2.langid as char(5))+'<span style="color: #ff0000;">,</span>'
from #ftsDDL p2 where p1.objname = p2.objname FOR XML PATH ('') ) as '<span style="color: #ff0000;">Columns</span>',
p1.indexname, p1.changetracking, p1.stoplistname from #ftsDDL p1 group by catalogname,
objname, indexname, changetracking, stoplistname
)

select '<span style="color: #ff0000;">CREATE FULLTEXT INDEX ON </span>'+objname+'<span style="color: #ff0000;">(</span>'+substring(Columns,1,len(Columns)-1)+
'<span style="color: #ff0000;">)</span> <span style="color: #ff0000;">KEY INDEX [</span>'+indexname+'<span style="color: #ff0000;">] on [</span>'+catalogname+'<span style="color: #ff0000;">] WITH CHANGE_TRACKING </span>'+changetracking+
'<span style="color: #ff0000;">, STOPLIST = [</span>'+stoplistname+'<span style="color: #ff0000;">]</span>' from cte;

drop table #ftsDDL;</span></pre>
<pre><span style="color: #008000;">/*</span></pre>
<pre><span style="color: #008000;">CREATE FULLTEXT INDEX ON V_ContentItmListGermany(abstr language 1031 ,desc language 1031 ,
subt language 1031 ,title language 1031 ) KEY INDEX [UI_V_ContentItmsListGermany]
on [Catalog_1] WITH CHANGE_TRACKING AUTO, STOPLIST = [SYSTEM]</span></pre>
<pre><span style="color: #008000;">CREATE FULLTEXT INDEX ON V_GenAtttGermany(blob language 1031 ) KEY INDEX [UI_V_GenAtttGermany]
on [Catalog_1] WITH CHANGE_TRACKING AUTO</span><span style="color: #008000;">, STOPLIST = [SYSTEM]</span><span style="color: #008000;">

</span><span style="color: #008000;">CREATE FULLTEXT INDEX ON V_GenAttachmentDe(blob TYPE COLUMN <span style="color: #ff0000;">*put type column here*</span>
language 1031 ) KEY INDEX  [UI_V_GenAttachmentDe] on [Catalog_LanDe]
WITH CHANGE_TRACKING AUTO</span><span style="color: #008000;">, STOPLIST = [SYSTEM]</span></pre>
<pre><span style="color: #008000;">CREATE FULLTEXT INDEX ON V_GenAtttUS(blob language 1033 ) KEY INDEX [UI_V_GenAtttUS]
on [Catalog_2] WITH CHANGE_TRACKING AUTO</span><span style="color: #008000;">, STOPLIST = [SYSTEM]</span></pre>
<pre><span style="color: #008000;">CREATE FULLTEXT INDEX ON V_GenAttachmentEn(blob TYPE COLUMN <span style="color: #ff0000;">*put type column here*</span>
language 1033 ) KEY INDEX  [UI_V_GenAttachmentEn] on [Catalog_LanEn]
WITH CHANGE_TRACKING AUTO</span><span style="color: #008000;">, STOPLIST = [SYSTEM]</span><span style="color: #008000;">

CREATE FULLTEXT INDEX ON V_GenAtttFrance(blob language 1036 ) KEY INDEX [UI_V_GenAtttFrance]
on [Catalog_3] WITH CHANGE_TRACKING AUTO</span><span style="color: #008000;">, STOPLIST = [SYSTEM]</span></pre>
<p><span style="color: #008000;">*/</span></p>
<p>Les indexes FTS, contraitement aux catalogues ne peuvent pas être scriptés directement dans SSMS. Evidemment il faudra recréer les catalogues avant. Attention, si des colonnes indexées sont de type varbinary(max) ou image, il faudra préciser le nom de la colonne qui stocke l&#8217;extension (cf <a href="http://msdn.microsoft.com/en-us/library/ms187317.aspx">http://msdn.microsoft.com/en-us/library/ms187317.aspx</a>) à la place de<span style="color: #ff0000;"> <em>*put type column here*</em><br />
<span style="color: #000000;">(je ne peux pas les deviner)</span><br />
</span></p>
<p>Petite note intéressante, dans le cas de ce client, les indexes FTS étaient créés sur des vues indexées, d&#8217;où l&#8217;UNION entre les indexes liés à sys.tables et ceux liés à sys.views dans la table temporaire. Et pour transposer et concaténer les colonnes de l&#8217;index sur une ligne on utilise la technique avec FOR XML PATH(&nbsp;&raquo;) dans la CTE.</p>
<p>A+</p>
<div id="_mcePaste" style="overflow: hidden; position: absolute; left: -10000px; top: 2px; width: 1px; height: 1px;">
<pre><span style="color: #008000;">ContentItmListGermany</span></pre>
</div>
<p><strong>Continuez votre lecture sur le blog :</strong>
<ul class="similar-posts">
<li><a href="http://blog.capdata.fr/index.php/msg-2508-level-16-state-1-the-in-row-data-for-object-is-incorrect/" rel="bookmark" title="10 mai 2011">Msg 2508, Level 16, State 1: the In-Row data %% for object %% is incorrect</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/error-8976-8978-problemes-de-chainage-comment-recuperer-les-donnees/" rel="bookmark" title="30 mai 2011">Error 8976 / 8978, problèmes de chaînage, comment récupérer les données</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/suppression-accidentelle-de-ligne-comment-retrouver-le-coupable/" rel="bookmark" title="6 octobre 2011">Suppression accidentelle de ligne : comment retrouver le coupable ?</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/openrowset-episode-1/" rel="bookmark" title="13 juillet 2011">OPENROWSET, épisode 1</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/fragmentation-sur-des-tables-stockees-en-s-gam/" rel="bookmark" title="20 août 2010">Fragmentation sur des tables stockées en S-GAM</a> (David BAFFALEUF) [SQL Server]</li>
</ul>
<p><!-- Similar Posts took 3.523 ms -->
<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fregenerer-le-ddl-des-indexes-full-text%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fregenerer-le-ddl-des-indexes-full-text%2F&amp;style=normal&amp;b=2" height="61" width="50" /><br />
			</a>
		</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.capdata.fr/index.php/regenerer-le-ddl-des-indexes-full-text/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Suppression accidentelle de ligne : comment retrouver le coupable ?</title>
		<link>http://blog.capdata.fr/index.php/suppression-accidentelle-de-ligne-comment-retrouver-le-coupable/</link>
		<comments>http://blog.capdata.fr/index.php/suppression-accidentelle-de-ligne-comment-retrouver-le-coupable/#comments</comments>
		<pubDate>Thu, 06 Oct 2011 10:50:28 +0000</pubDate>
		<dc:creator>David BAFFALEUF</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[backup]]></category>
		<category><![CDATA[journal de transactions]]></category>

		<guid isPermaLink="false">http://blog.capdata.fr/?p=3019</guid>
		<description><![CDATA[C&#8217;est vrai qu&#8217;il y a des outis, Change Data Capture et tout l&#8217;arsenal des Database Audit Specifications. Mais pour retrouver qui a supprimé les lignes dans la table T1 le 17 septembre dernier entre 12h00 et 14h00 il y a encore plus simple, pour peu qu&#8217;il y ait des backups de transactions.
La fonction qui tue:
Dans [...]]]></description>
			<content:encoded><![CDATA[<p>C&#8217;est vrai qu&#8217;il y a des outis, Change Data Capture et tout l&#8217;arsenal des Database Audit Specifications. Mais pour retrouver qui a supprimé les lignes dans la table T1 le 17 septembre dernier entre 12h00 et 14h00 il y a encore plus simple, pour peu qu&#8217;il y ait des backups de transactions.</p>
<h2>La fonction qui tue:</h2>
<p>Dans un article <a href="http://blog.capdata.fr/index.php/point-in-time-recovery-et-fn_dump_dblog/">précédent</a>, nous avons couvert une fonction table valuée fort intéressante qui permet de relire à l&#8217;intérieur d&#8217;une sauvegarde de journal: <strong>fn_dump_dblog()</strong>. Nous allons nous en servir pour connaître cette fois l&#8217;identité du coupable. La table concernée par le DELETE est la table T1, il faut commencer par récupérer sa valeur de allocation_unit_id:</p>
<pre><span style="color: #0000ff;">select allocation_unit_id from sys.allocation_units AU inner join sys.partitions P
on P.hobt_id = AU.container_id where P.object_id = object_id('T1')</span></pre>
<pre><span style="color: #3366ff;"><em>allocation_unit_id
---------------------
72057594043301888
</em></span></pre>
<p>Ensuite, rechercher dans les backups transactionnels des opérations de suppression de lignes sur cet objet:</p>
<pre><span style="color: #0000ff;">select [Current LSN] ,Operation ,[Transaction ID] ,AllocUnitId
from fn_dump_dblog(DEFAULT, DEFAULT,DEFAULT, DEFAULT,
'<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery.TRAN2.bak</span>',
DEFAULT,DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT,DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT,DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT,DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT,DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT) where Operation  = '<span style="color: #ff0000;">LOP_DELETE_ROWS</span>' and AllocUnitId = <span style="color: #ff0000;">72057594043301888

</span></span><span style="color: #3366ff;"><em>Current LSN             Operation          Transaction ID AllocUnitId         
----------------------- ------------------ -------------- --------------------
00000025:00000032:0003  LOP_DELETE_ROWS    <span style="color: #ff0000;">0000:00000a9d </span> 72057594043301888   
00000025:00000032:0005  LOP_DELETE_ROWS    <span style="color: #ff0000;">0000:00000a9d</span>  72057594043301888   
00000025:00000032:0006  LOP_DELETE_ROWS    <span style="color: #ff0000;">0000:00000a9d</span>  72057594043301888   
00000025:00000032:0007  LOP_DELETE_ROWS    <span style="color: #ff0000;">0000:00000a9d</span>  72057594043301888   
00000025:00000032:0008  LOP_DELETE_ROWS    <span style="color: #ff0000;">0000:00000a9d</span>  72057594043301888   </em></span>
<span style="color: #3366ff;">...</span></pre>
<p>On récupère le LSN de la transaction pour retrouver le BEGIN TRAN correspondant:  <span style="color: #ff0000;"><em>0000:00000a9d</em>:</span></p>
<pre><span style="color: #0000ff;">select [Current LSN] ,Operation ,[Transaction ID] ,AllocUnitId
 ,[Begin Time] ,[Transaction Name] ,[End Time] ,[Description]
 ,[Transaction SID]
from fn_dump_dblog(DEFAULT, DEFAULT,DEFAULT, DEFAULT,
'<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery.TRAN2.bak</span>',
DEFAULT,DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT)
where Operation  = '<span style="color: #ff0000;">LOP_BEGIN_XACT</span>' and [Transaction ID] = '<span style="color: #ff0000;">0000:00000a9d</span>'</span>

<span style="color: #3366ff;">Current LSN             Operation                       Transaction ID AllocUnitId        
----------------------- ------------------------------- -------------- --------------------
00000025:00000032:0001  LOP_BEGIN_XACT                  0000:00000a9d  NULL                

</span></pre>
<pre><span style="color: #3366ff;"> Begin Time               Transaction Name                  Description                                  
------------------------ --------------------------------- -----------------------------------------------
2011/10/05 15:21:50:510  tralalala                         tralalala;<span style="color: #ff0000;">0x75a770d73d6bf54bb0dc07725254ae57</span>    </span><span style="color: #ff0000;">

</span></pre>
<pre><span style="color: #3366ff;">Transaction SID
------------------------------------
<span style="color: #ff0000;">0x75A770D73D6BF54BB0DC07725254AE57</span></span></pre>
<p>Remarque intéressante, [Transaction SID] (en rouge) correspond au SID de l&#8217;utilisateur dans la base&#8230;</p>
<h2>Le dénouement&#8230;</h2>
<p>Il ne reste plus qu&#8217;à dégainer la requête sur sys.database_principals:</p>
<pre><span style="color: #0000ff;">select name from sys.database_principals where sid = <span style="color: #ff0000;">0x75A770D73D6BF54BB0DC07725254AE57</span></span></pre>
<pre><em><span style="color: #3366ff;">name
</span><span style="color: #3366ff;">-------------------
user1

</span></em><em></em></pre>
<p>A+<strong>Continuez votre lecture sur le blog :</strong>
<ul class="similar-posts">
<li><a href="http://blog.capdata.fr/index.php/point-in-time-recovery-et-fn_dump_dblog/" rel="bookmark" title="13 juillet 2011">Point-in-time recovery et fn_dump_dblog()</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/msg-2508-level-16-state-1-the-in-row-data-for-object-is-incorrect/" rel="bookmark" title="10 mai 2011">Msg 2508, Level 16, State 1: the In-Row data %% for object %% is incorrect</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/error-8976-8978-problemes-de-chainage-comment-recuperer-les-donnees/" rel="bookmark" title="30 mai 2011">Error 8976 / 8978, problèmes de chaînage, comment récupérer les données</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/how-to-reduire-la-taille-du-journal-de-transactions-sur-disque/" rel="bookmark" title="11 juillet 2011">How-To: réduire la taille du journal de transactions sur disque</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/regenerer-le-ddl-des-indexes-full-text/" rel="bookmark" title="12 octobre 2011">Regénérer le DDL des indexes FULL TEXT</a> (David BAFFALEUF) [SQL Server]</li>
</ul>
<p><!-- Similar Posts took 3.609 ms -->
<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fsuppression-accidentelle-de-ligne-comment-retrouver-le-coupable%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fsuppression-accidentelle-de-ligne-comment-retrouver-le-coupable%2F&amp;style=normal&amp;b=2" height="61" width="50" /><br />
			</a>
		</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.capdata.fr/index.php/suppression-accidentelle-de-ligne-comment-retrouver-le-coupable/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Point-in-time recovery et fn_dump_dblog()</title>
		<link>http://blog.capdata.fr/index.php/point-in-time-recovery-et-fn_dump_dblog/</link>
		<comments>http://blog.capdata.fr/index.php/point-in-time-recovery-et-fn_dump_dblog/#comments</comments>
		<pubDate>Wed, 13 Jul 2011 15:59:05 +0000</pubDate>
		<dc:creator>David BAFFALEUF</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[backup]]></category>
		<category><![CDATA[journal de transaction]]></category>

		<guid isPermaLink="false">http://blog.capdata.fr/?p=2871</guid>
		<description><![CDATA[Point-in-time recovery désigne une restauration de base de données consistante à un point précis soit dans le temps (STOPAT), soit dans une séquence de transactions (STOPATMARK, STOPBEFOREMARK). On ne peut faire du PIT recovery que lorsque la base est en mode de restauration complet.
Le problème inhérent à ce genre de restauration, c&#8217;est qu&#8217;on ne sait [...]]]></description>
			<content:encoded><![CDATA[<p><em>Point-in-time recovery</em> désigne une restauration de base de données consistante à un point précis soit dans le temps (STOPAT), soit dans une séquence de transactions (STOPATMARK, STOPBEFOREMARK). On ne peut faire du PIT recovery que lorsque la base est en mode de restauration complet.</p>
<p>Le problème inhérent à ce genre de restauration, c&#8217;est qu&#8217;on ne sait pas exactement jusqu&#8217;où restaurer. Lorsque votre collègue vous dit qu&#8217;il a supprimé accidentellement les données de la table principale vers 13h45, c&#8217;est assez vague comme réponse. Si vous restaurez à 13h44, que fait-on des transactions validées entre 13h44 et le moment précis où le delete a été exécuté ? Il faut jouer avec RESTORE LOG WITH STOPAT, STANDBY pour déterminer le point dans le temps où les données ne sont plus dans la table. Milliseconde par milliseconde ? Bof bof bof&#8230;</p>
<p>L&#8217;alternative est d&#8217;utiliser des marques de transactions:</p>
<pre><span style="color: #0000ff;">BEGIN TRAN <span style="color: #ff0000;">MiseAJourProduits
</span>GO
DELETE FROM </span><span style="color: #0000ff;">PRODUITS <span style="color: #008000;">-- WHERE ID_PRODUIT = @id_produit</span></span> <span style="color: #008000;">(aïeuuu)</span>
<span style="color: #0000ff;">GO
...
COMMIT TRAN <span style="color: #ff0000;">MiseAJourProduits</span>
GO</span></pre>
<p>Dès lors il est possible de restaurer avant la transaction qui pose problème en utilisant l&#8217;option STOPBEFOREMARK de RESTORE LOG:</p>
<pre><span style="color: #0000ff;">RESTORE LOG maBASE FROM DISK='C:\UTRECHT\MSSQL.1\MSSQL\Backup\maBase.20110712.trn'
WITH RECOVERY, STOPBEFOREMARK = '<span style="color: #ff0000;">MiseAJourProduits</span>'
GO</span></pre>
<p>Seul problème, c&#8217;est à l&#8217;initiative du développeur de le faire, or la plupart du temps les transactions sont non marquées, je ne vous parle même pas des progiciels&#8230;</p>
<h2>Fiat lux:</h2>
<p>J&#8217;ai longtemps regretté l&#8217;absence d&#8217;un outil built-in tel que <a href="http://download.oracle.com/docs/cd/B19306_01/server.102/b14215/logminer.htm">LogMiner</a> pour pouvoir lire le contenu d&#8217;un backup log, et pouvoir effectuer une restauration précise à la transaction près. En effet,  STOPATMARK / STOPBEFOREMARK permettent d&#8217;utiliser une syntaxe telle que :</p>
<pre><span style="color: #0000ff;">RESTORE LOG ... WITH STOPBEFOREMARK = 'lsn:&lt;LSN number&gt;'</span>.</pre>
<p>Seulement voilà, comment connaître les différentes transactions embarquées dans un backup log ? fn_dblog() permet d&#8217;obtenir cette information sur le journal lui-même, mais quid des backups&#8230;</p>
<p>Et il y a quelques jours, je joue avec <a href="http://blog.capdata.fr/index.php/openrowset-episode-1/">OPENROWSET()</a> et là je tombe sur une fonction système non-documentée dans la base resource : <span style="color: #000000;"><strong>fn_dump_dblog()</strong></span>. En lisant le nom, l&#8217;émotion m&#8217;étreint. Le souffle court, je jette un coup d&#8217;oeil au source de la fonction:</p>
<pre><span style="color: #0000ff;">create function sys.fn_dump_dblog  
 (  
    @start    nvarchar (25) = NULL,  
    @end      nvarchar (25) = NULL,  
<span style="color: #ff0000;">    @devtype  nvarchar (260) = NULL, <span style="color: #008000;">-- NULL(DISK) | DISK | TAPE | VIRTUAL_DEVICE  </span></span>
    @seqnum   Int         = 1,  
    @fname1   nvarchar (260) = NULL,  
    @fname2   nvarchar (260) = NULL,  
    @fname3   nvarchar (260) = NULL, 
    ...
    @fname64   nvarchar (260) = NULL  
 )  
returns table  
as  
 return select  
     [Current LSN],  [Operation],  [Context],  [Transaction ID],   [Tag Bits],  
     [Log Record Fixed Length],  [Log Record Length],   [Previous LSN],  
     (...)
     from OpenRowset (DBLog, @start, @end, @devtype, @seqnum,  
     @fname1,  @fname2,  @fname3,  @fname4,  @fname5, 
     (...)
     @fname62,  @fname63,  @fname64)</span></pre>
<p>En lisant le paramètre <em>@devtype</em> j&#8217;avale mon sandwich indien poulet-curry de travers. Ça fait dix mois que je bosse sur un provider OLEDB qui permette de lire dans un backup full (un peu l&#8217;équivalent de l&#8217;<a href="http://www.sybase.com/files/Product_Overviews/Sybase-ISUG-101707.pdf">Archive Database Access </a>sous Sybase ASE), et là je découvre qu&#8217;il existe une fonction qui fait la même chose sur les backups log. Depuis la 2005 RTM. Argl.</p>
<p>Et puis en gouglant un peu, je tombe sur <a href="http://blogs.msdn.com/b/dfurman/archive/2009/11/05/reading-database-transaction-log-with-fn-dump-dblog.aspx">cet article de Dimitri Furman</a>, une personne de MS Consulting Services à New-York qui décrit brièvement la fonction et ses arguments. Je ne peux pas résister, il va falloir tester ça.</p>
<h2>Scénario de test:</h2>
<p>On va donc créer une base de démo, peupler une table et supprimer des intervalles de valeurs en les entrelaçant de backup logs. On arrive à une suppression par erreur, et on souhaite remonter les données juste à la transaction d&#8217;avant, sachant que les transactions ne sont pas marquées.</p>
<pre><span style="color: #008000;">/*************************************************************************************
 Obj:       DEMO RESTAURATION SUITE A ERREUR MANUELLE
            UTILISATION DU MODE STANDBY POUR EFFECUTER UN POINT-IN-TIME RECOVERY
 Aut:       dbaffaleuf@capdata
 Crdate:    2011/07/13
*/</span>

<span style="color: #0000ff;"><span style="color: #008000;">-- Population de la base</span>
use master
GO
if exists (select 1 from sys.databases where name='<span style="color: #ff0000;">demorecovery</span>')
 drop database demorecovery
GO
create database demorecovery
GO

use demorecovery
GO
create table T1(
 a numeric identity,
 b char(4000) default replicate('<span style="color: #ff0000;">b</span>',4000),
 c bigint default round(rand()*100,0))
GO
insert into T1 default values
GO 1000
create unique clustered index IDX_T1C on T1(a)
GO
create index IDX_T1NC__c on T1(c)
GO

<span style="color: #008000;">-- Activation du mode de récupération complet</span>
alter database demorecovery set recovery full
GO
backup database demorecovery to disk='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery_FULL.bak</span>' with init, stats
GO

<span style="color: #008000;">-- Quelques transactions et quelques backups de transactions</span>
delete from T1 where a &lt; 10
GO
backup log demorecovery to disk='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery_LOG1.bak</span>' with init, stats
GO
delete from T1 where a &lt; 20
GO
backup log demorecovery to disk='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery_LOG2.bak</span>' with init, stats
GO
delete from T1 where a &lt; 30
GO
backup log demorecovery to disk='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery_LOG3.bak</span>' with init, stats
GO
<span style="color: #008000;">
-- Trois autres transactions à suivre, la dernière (a&lt;60) est une erreur manuelle</span>
delete from T1 where a &lt; 40
GO
delete from T1 where a &lt; 50
GO
delete from T1 where a &lt; 60  <span style="color: #008000;">-- &lt;-- CES DONNEES SONT EFFACEES PAR ERREUR !!!</span>
GO
backup log demorecovery to disk='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery_LOG4.bak</span>' with init, stats
GO

<span style="color: #008000;">-- Une autre transaction valide passe entre-temps</span>
delete from T1 where a &gt; 990
GO

<span style="color: #008000;">-- Le téléphone sonne, l'utilisateur demande s'il est possible de récupérer les données avant le delete where a &lt; 60</span>
use master
GO
<span style="color: #008000;">-- tail log backup</span>
backup log demorecovery to disk='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery_TAILLOG.bak</span>' with init, stats, NO_TRUNCATE, NORECOVERY
GO
select state_desc from sys.databases where name='demorecovery'
GO
<span style="color: #33cccc;"><em><span style="color: #3366ff;">
state_desc
------------------------------------------------------------
RESTORING</span>
</em></span><span style="color: #008000;">
-- On identifie le backup de journal qui embarque l'erreur: demorecovery_LOG4.bak
-- on recherche les différentes transactions contenues à l'intérieur avec fn_dump_dblog():</span>
select *, [Current LSN]
 ,Operation
 ,[Transaction ID]
 ,AllocUnitId
 ,[Begin Time]
 ,[Transaction Name]
 ,[End Time]
 ,[Description]
from fn_dump_dblog(DEFAULT, DEFAULT,DEFAULT, DEFAULT,
'<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery_LOG4.bak</span>',
DEFAULT,DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT)
where Operation  = '<span style="color: #ff0000;">LOP_BEGIN_XACT</span>'

<span style="color: #3366ff;"><em>Current LSN             Operation         Transaction ID  Begin Time               Transaction Name    Description
----------------------- ----------------- --------------  ------------------------ ------------------- -----------------------------------------------------------------------
00000041:00000338:000e  LOP_BEGIN_XACT    0000:000008b8   2011/07/13 15:59:43:580  DELETE              DELETE;0x0105000000000005150000006aa3aebb1b5c3491642b62e2e8030000
00000042:00000021:0001  LOP_BEGIN_XACT    0000:000008b9   2011/07/13 15:59:43:587  DELETE              DELETE;0x0105000000000005150000006aa3aebb1b5c3491642b62e2e8030000
00000042:00000076:0001  LOP_BEGIN_XACT    0000:000008ba   2011/07/13 15:59:43:630  DELETE              DELETE;0x0105000000000005150000006aa3aebb1b5c3491642b62e2e8030000</em></span>

<span style="color: #008000;">
-- On restaure jusqu'au N-1 backup log avant pb</span>
restore database demorecovery from disk='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery_FULL.bak</span>' with stats, norecovery
GO
restore log demorecovery from disk='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery_LOG1.bak</span>' with stats, norecovery
GO
restore log demorecovery from disk='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery_LOG2.bak</span>' with stats, norecovery
GO
restore log demorecovery from disk='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery_LOG3.bak</span>' with stats, norecovery
GO

<span style="color: #008000;">-</span><span style="color: #008000;"><span style="color: #008000;">- </span>On restaure en standby mode à la première transaction pour déterminer si les données entre 50 et 60 sont toujours là</span>
restore log demorecovery from disk='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery_LOG4.bak</span>' with stats,
standby='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\stdby_files.bak</span>', stopbeforemark='<span style="color: #ff0000;">lsn:0x00000042:00000021:0001</span>'
GO

select state_desc, is_in_standby from sys.databases where name='demorecovery'
GO

<span style="color: #3366ff;"><em>state_desc    is_in_standby
------------- --------------
ONLINE        1
</em></span>
use demorecovery
GO
select * from T1 where a &lt; 60
GO

<em><span style="color: #3366ff;">a      b                            c
------ ---------------------------- ----
40     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 31
41     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 28
42     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 77
43     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 18
44     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 12
45     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 42
46     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 2
47     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 46
48     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 39
49     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 74
50     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 45
51     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 2
52     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 32
53     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 10
54     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 2
55     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 99
56     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 71
57     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 38
58     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 2
59     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 41</span></em>

<span style="color: #008000;">-- OK, allons voir à la transaction suivante</span>
use master
GO
restore log demorecovery from disk='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery_LOG4.bak</span>' with stats,
standby='C<span style="color: #ff0000;">:\UTRECHT\MSSQL.1\MSSQL\Backup\stdby_files.bak</span>', stopbeforemark='<span style="color: #ff0000;">lsn:0x00000042:00000076:0001</span>'
GO

use demorecovery
GO
select * from T1 where a &lt; 60
GO

<span style="color: #3366ff;"><em>a      b                            c
------ ---------------------------- ----
50     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 45
51     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 2
52     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 32
53     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 10
54     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 2
55     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 99
56     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 71
57     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 38
58     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 2
59     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 41</em></span>

<span style="color: #008000;">-- On dirait bien qu'on est dans l'état attendu. Allons voir à la dernière transaction pour confirmer:</span>
use master
GO
restore log demorecovery from disk='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery_LOG4.bak</span>' with stats,
standby='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\stdby_files.bak</span>'
GO

use demorecovery
GO
select * from T1 where a &lt; 60
GO

<span style="color: #3366ff;"><em>a      b                            c
------ ---------------------------- ----</em></span>

<span style="color: #008000;">-- Ouaip, donc il faut revenir au point précédent, en rechargeant tout depuis le backup FULL:</span>
use master
GO
restore database demorecovery from disk='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery_FULL.bak</span>' with stats, norecovery
GO
restore log demorecovery from disk='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery_LOG1.bak</span>' with stats, norecovery
GO
restore log demorecovery from disk='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery_LOG2.bak</span>' with stats, norecovery
GO
restore log demorecovery from disk='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery_LOG3.bak</span>' with stats, norecovery
GO

restore log demorecovery from disk='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery_LOG4.bak</span>' with stats,
stopbeforemark='<span style="color: #ff0000;">lsn:0x00000042:00000076:0001</span>', recovery
GO

use demorecovery
GO
select * from T1 where a &lt; 60
GO
<span style="color: #3366ff;"><em>
a      b                            c
------ ---------------------------- ----
50     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 45
51     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 2
52     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 32
53     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 10
54     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 2
55     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 99
56     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 71
57     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 38
58     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 2
59     bbbbbbbbbbbbbbbbbbbbbbbbbbbb 41</em></span>

<span style="color: #008000;">-- Note: la transaction suivante (a&gt;990) est perdue. On ne peut pas la recharger car il y aurait une cassure dans la chaîne de LSNs.
-- Si on avait tenté de restaurer le tail-log à la suite:</span>
restore log demorecovery from disk='<span style="color: #ff0000;">C:\UTRECHT\MSSQL.1\MSSQL\Backup\demorecovery_TAILLOG.bak</span>' with stats, recovery
GO

<em><span style="color: #3366ff;">Msg 4305, Niveau 16, État 1, Ligne 1
Le journal dans ce jeu de sauvegarde commence au numéro de séquence d'enregistrement 66000000017600001, ce qui est trop récent
pour une application à la base de données. Une sauvegarde de fichier journal antérieure qui inclut le numéro de séquence
d'enregistrement 66000000009200001 peut être restaurée.
Msg 3013, Niveau 16, État 1, Ligne 1
RESTORE LOG s'est terminé anormalement.</span></em>

</span></pre>
<p>Donc voilà enfin une méthode pour récupérer une base pile à l&#8217;endroit où on veut la récupérer, encore une fois grâce à une fonction non documentée qui mériterait d&#8217;être plus connue.</p>
<p>A+. David B.<strong>Continuez votre lecture sur le blog :</strong>
<ul class="similar-posts">
<li><a href="http://blog.capdata.fr/index.php/suppression-accidentelle-de-ligne-comment-retrouver-le-coupable/" rel="bookmark" title="6 octobre 2011">Suppression accidentelle de ligne : comment retrouver le coupable ?</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/how-to-reduire-la-taille-du-journal-de-transactions-sur-disque/" rel="bookmark" title="11 juillet 2011">How-To: réduire la taille du journal de transactions sur disque</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/pourquoi-il-faut-sauvegarder-les-bases-systemes/" rel="bookmark" title="3 juillet 2011">Pourquoi il faut sauvegarder les bases systèmes</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/sql-server-principes-dune-sauvegarde-a-chaud/" rel="bookmark" title="12 décembre 2008">Principes d&#8217;une sauvegarde à chaud</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/openrowset-episode-1/" rel="bookmark" title="13 juillet 2011">OPENROWSET, épisode 1</a> (David BAFFALEUF) [SQL Server]</li>
</ul>
<p><!-- Similar Posts took 4.283 ms -->
<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fpoint-in-time-recovery-et-fn_dump_dblog%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fpoint-in-time-recovery-et-fn_dump_dblog%2F&amp;style=normal&amp;b=2" height="61" width="50" /><br />
			</a>
		</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.capdata.fr/index.php/point-in-time-recovery-et-fn_dump_dblog/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>OPENROWSET, épisode 1</title>
		<link>http://blog.capdata.fr/index.php/openrowset-episode-1/</link>
		<comments>http://blog.capdata.fr/index.php/openrowset-episode-1/#comments</comments>
		<pubDate>Wed, 13 Jul 2011 06:02:40 +0000</pubDate>
		<dc:creator>David BAFFALEUF</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[oledb]]></category>
		<category><![CDATA[openrowset]]></category>

		<guid isPermaLink="false">http://blog.capdata.fr/?p=2877</guid>
		<description><![CDATA[Alors là c&#8217;est un puits sans fond. OPENROWSET() est tellement vaste qu&#8217;on peut se demander si on touchera un jour les limites de la chose.
En gros c&#8217;est une fonction qui appelle un provider OLEDB, et qui retourne un résultat au format table-valué. Par exemple on l&#8217;utilise pour exécuter des requêtes sur des serveurs liés en [...]]]></description>
			<content:encoded><![CDATA[<p>Alors là c&#8217;est un puits sans fond. <a href="http://msdn.microsoft.com/fr-fr/library/ms190312.aspx">OPENROWSET()</a> est tellement vaste qu&#8217;on peut se demander si on touchera un jour les limites de la chose.</p>
<p>En gros c&#8217;est une fonction qui appelle un provider OLEDB, et qui retourne un résultat au format table-valué. Par exemple on l&#8217;utilise pour exécuter des requêtes sur des serveurs liés en passant le nom d&#8217;un provider (MSDAORA pour oracle, SQLNCLI pour le client natif MSSQL, etc&#8230;). Par exemple:</p>
<pre><span style="color: #0000ff;">SELECT RemoteDepts.*
FROM OPENROWSET('SQLNCLI', 'Server=MS2K8-Win2008-1;Trusted_Connection=yes;',
     'SELECT * FROM CAPDATA.dbo.DEPARTEMENT') AS RemoteDepts;</span></pre>
<p>Jusque là, rien de révolutionnaire.</p>
<h2>Juste pour rire, on réinvente le CSV engine:</h2>
<p>Il existe un certain nombre de providers non documentés sur lesquels s&#8217;appuient en général  les <em>DMV/DMF </em>et quelques fonctions table-valuées comme<em> fn_dblog()</em>,<em> fn_helpcollations()</em>, etc&#8230; qui feront l&#8217;objet de l&#8217;épisode 2. Il en existe un toutefois, <strong>BULK</strong>,  qui est parfaitement documenté et très pratique, et qui permet de faire du chargement massif notamment (l&#8217;ancêtre du BULK INSERT T-SQL) mais aussi de renvoyer à peu près n&#8217;importe quoi sous une forme table-valuée.</p>
<p>Par exemple, on peut reprendre à notre compte le concept du <a href="http://dev.mysql.com/doc/refman/5.0/en/csv-storage-engine.html">moteur CSV</a> de MySQL, et créer des vues qui mappent des fichiers CSV sur disque, pour visualiser le contenu sans les stocker physiquement dans la base. Un exemple de fichier CSV et de fichier de format (pour plus d&#8217;infos voir <a href="http://msdn.microsoft.com/en-us/library/ms178129.aspx">la page MSDN</a> concernant les fichiers de format):</p>
<p><strong>bulktest.csv:</strong></p>
<pre><span style="color: #008000;">LOUISH,21941
DAVID,21940
LOUISP,21943
LAURENT,21942
JSEB,21944</span></pre>
<p><strong>bulktest.fmt:</strong></p>
<pre><span style="color: #008000;">9.0
2
1 SQLCHAR 0 10 "," 1 CurName ""
2 SQLCHAR 0 5 "\r\n" 2 ID ""</span></pre>
<p><strong>La vue T-SQL:</strong></p>
<pre><span style="color: #0000ff;">create view CSVENgine
as
    select * from openrowset(bulk 'V:\DBA2\MSSQL.1\MSSQL\Backup\bulktest.csv',
    FORMATFILE = 'V:\DBA2\MSSQL.1\MSSQL\Backup\bulktest.fmt') as CSV1
GO
</span></pre>
<pre><span style="color: #0000ff;">select * from CSVENgine order by ID desc

</span><span style="color: #0000ff;"><em>CurName    ID
---------- -----
JSEB       21944
LOUISP     21943
LAURENT    21942
LOUISH     21941
DAVID      21940

(5 ligne(s) affectée(s)</em></span>)</pre>
<p>Seule petite ombre au tableau, le provider BULK ne permet pas la mise à jour, on ne peut accéder aux données qu&#8217;en lecture seule. Mais ça reste quand même bien pratique pour attaquer une trace perfmon directement en SQL.</p>
<h2>Charger des fichiers binaires:</h2>
<p>Les options SINGLE_BLOB / SINGLE_CLOB/ SINGLE_NCLOB du provider BULK permettent en outre de charger des  documents binaires (vidéos, images, PDF, documents word, etc&#8230;)  dans  des tables, respectivement soit en varbinary(max) / varchar(max) / nvarchar(max) si les  fichiers sont inférieurs à 2Gb comme c&#8217;est la cas dans l&#8217;exemple, soit  dans du filestream pour les fichiers supérieurs.</p>
<pre><span style="color: #0000ff;">create table Documents (ID numeric identity, title varchar(255), Filedata varbinary(max))

insert into Documents (title, Filedata)
SELECT 'SQL 2008 Licensing',
doc.* from OPENROWSET(BULK 'E:\CAPDATA\DOCUMENTATION\SGBD\SQL Server\WINWORDS\2008
SQL Licensing Overview final.docx', SINGLE_BLOB) as doc

insert into Documents (title, Filedata)
SELECT 'DBM Sharepoint Labs',
doc.* from OPENROWSET(BULK 'E:\CAPDATA\DOCUMENTATION\SGBD\SQL Server\WINWORDS\
DBM_Sharepoint_Labs.docx', SINGLE_BLOB) as doc

insert into Documents (title, Filedata)
SELECT 'DBM and Log Shipping',
doc.* from OPENROWSET(BULK 'E:\CAPDATA\DOCUMENTATION\SGBD\SQL Server\WINWORDS\
DBMandLogShipping.docx', SINGLE_BLOB) as doc

insert into Documents (title, Filedata)
SELECT 'Database Snapshot Performance',
doc.* from OPENROWSET(BULK 'E:\CAPDATA\DOCUMENTATION\SGBD\SQL Server\WINWORDS\
DBSnapshotPerf.docx', SINGLE_BLOB) as doc</span></pre>
<h2>Récupérer la sortie d&#8217;une procédure stockée sous la forme d&#8217;une table:</h2>
<p>Une astuce monstrueuse révélée par<a href="http://blogs.technet.com/b/wardpond/"> Ward Pond</a> en 2005, qui détourne l&#8217;utilisation d&#8217;OPENROWSET pour appeler une procédure stockée sur le serveur local et retransformer le résultat en format table-valué, un peu à la façon dont les <a href="http://www.sypron.nl/mda.html">tables MDA</a> sont appelées sous Sybase ASE, et qui nous a sauvé la vie bien des fois sur SQL Server 2000 avant que n&#8217;apparaissent les DMV:</p>
<pre><span style="color: #0000ff;">sp_configure 'Ad Hoc Distributed Queries',1
GO
reconfigure
GO</span>
<span style="color: #0000ff;">SELECT  w.SPID, w.Status, w.loginame, w.blk, w.dbname,w.cmd,
Lck.Objid, lck.Type, lck.Mode
FROM    OPENROWSET ('SQLNCLI','Server=(local);TRUSTED_CONNECTION=YES;',
                   'set fmtonly off exec master.dbo.sp_who') as w
INNER JOIN OPENROWSET ('SQLNCLI','Server=(local);TRUSTED_CONNECTION=YES;',
                       'set fmtonly off exec master.dbo.sp_lock') as lck
on lck.spid = w.spid
where w.blk &lt;&gt; 0
GO
<em>
SPID   Status       loginame            blk   dbname          cmd     Objid       Type Mode
------ ------------ ------------------- ----- --------------- ------- ----------- ---- --------
55     suspended    DBA2\dbaffaleuf     54    whitepaperz     SELECT  2073058421  TAB  IS
55     suspended    DBA2\dbaffaleuf     54    whitepaperz     SELECT  0           DB   S
55     suspended    DBA2\dbaffaleuf     54    whitepaperz     SELECT  2073058421  RID  S
55     suspended    DBA2\dbaffaleuf     54    whitepaperz     SELECT  2073058421  PAG  IS</em></span></pre>
<h2>Voir plus loin ?</h2>
<p>MS fournit des providers OLEDB pour s&#8217;interfacer avec OPENROWSET, certains sont publics (providers relatifs aux serveurs liés, BULK), et certains sont non documentés et utilisés par des vues ou fonctions dynamiques dans SQL Server. C&#8217;est ceux-ci que nous essaierons de référencer et de comprendre dans l&#8217;épisode 2. Enfin dans l&#8217;épisode 3,  nous verrons comment créer son propre provider OLEDB avec Advanced Template Library et Visual C++, et nous essaierons de le faire communiquer avec SQL Server. Du pain sur la planche !</p>
<p>A+ David B.<strong>Continuez votre lecture sur le blog :</strong>
<ul class="similar-posts">
<li><a href="http://blog.capdata.fr/index.php/regenerer-le-ddl-des-indexes-full-text/" rel="bookmark" title="12 octobre 2011">Regénérer le DDL des indexes FULL TEXT</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/how-to-reduire-lenveloppe-de-tempdb/" rel="bookmark" title="7 juillet 2011">How-To : réduire l&#8217;enveloppe de tempdb</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/point-in-time-recovery-et-fn_dump_dblog/" rel="bookmark" title="13 juillet 2011">Point-in-time recovery et fn_dump_dblog()</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/pourquoi-il-faut-sauvegarder-les-bases-systemes/" rel="bookmark" title="3 juillet 2011">Pourquoi il faut sauvegarder les bases systèmes</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/error-8976-8978-problemes-de-chainage-comment-recuperer-les-donnees/" rel="bookmark" title="30 mai 2011">Error 8976 / 8978, problèmes de chaînage, comment récupérer les données</a> (David BAFFALEUF) [SQL Server]</li>
</ul>
<p><!-- Similar Posts took 4.218 ms -->
<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fopenrowset-episode-1%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fopenrowset-episode-1%2F&amp;style=normal&amp;b=2" height="61" width="50" /><br />
			</a>
		</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.capdata.fr/index.php/openrowset-episode-1/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How-To: réduire la taille du journal de transactions sur disque</title>
		<link>http://blog.capdata.fr/index.php/how-to-reduire-la-taille-du-journal-de-transactions-sur-disque/</link>
		<comments>http://blog.capdata.fr/index.php/how-to-reduire-la-taille-du-journal-de-transactions-sur-disque/#comments</comments>
		<pubDate>Mon, 11 Jul 2011 21:40:33 +0000</pubDate>
		<dc:creator>David BAFFALEUF</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[journal de transaction]]></category>

		<guid isPermaLink="false">http://blog.capdata.fr/?p=2796</guid>
		<description><![CDATA[Deuxième how-to vidéo sur SQL Server, sur le sujet N°1 remonté dans les forums d&#8217;entraide SQL Server: comment réduire la taille d&#8217;un journal de transactions sur disque.
Démo:
Pourquoi il explose:
Avant de vouloir le réduire, il faut comprendre pourquoi on en est arrivé là. A l&#8217;origine de ce problème, une option par défaut de la base de [...]]]></description>
			<content:encoded><![CDATA[<p>Deuxième how-to vidéo sur SQL Server, sur le sujet N°1 remonté dans les forums d&#8217;entraide SQL Server: comment réduire la taille d&#8217;un journal de transactions sur disque.</p>
<h2>Démo:</h2>

<span id="video0" class="HDFLV">
<a href="http://www.macromedia.com/go/getflashplayer">Get the Flash Player</a> to see this player.</span>
<script type="text/javascript">
var s0 = new SWFObject("http://blog.capdata.fr/wp-content/plugins/contus-hd-flv-player/hdflvplayer/hdplayer.swf","n0","800height=600","400","7");
s0.addParam("allowfullscreen","true");
s0.addParam("allowscriptaccess","always");
s0.addParam("wmode","opaque");
s0.addVariable("baserefW","http://blog.capdata.fr");s0.addVariable("pid","1");
s0.addVariable("vid","4");
s0.write("video0");
</script>
<div id="htmlplayer1"><iframe  type="text/html" width="800height=600" height="400"  src="http://www.youtube.com/embed/TKRhE9iQzWM" frameborder="0">
</iframe> </div><script>var txt =  navigator.platform ;if(txt =="iPod"|| txt =="iPad"|| txt =="iPhone" || txt =="Linux armv7I")
            {   document.getElementById("htmlplayer1").style.display = "block";
                document.getElementById("video1").style.display = "none";
            }else{
 document.getElementById("htmlplayer1").style.display = "none";
            }
			 function failed(e) {
			  if(txt =="iPod"|| txt =="iPad"|| txt =="iPhone" || txt =="Linux armv7I")
            {
   alert("Player doesnot support this video.");
   }
}
        </script>
<h2>Pourquoi il explose:</h2>
<p>Avant de vouloir le réduire, il faut comprendre pourquoi on en est arrivé là. A l&#8217;origine de ce problème, une option par défaut de la base de données qui s&#8217;appelle <strong>RECOVERY</strong>. Elle permet d&#8217;indiquer deux choses:</p>
<ul>
<li>Quels types de sauvegarde il sera possible d&#8217;effectuer sur la base.</li>
<li>De quelle manière on recycle les transactions validées dans le journal de transactions.</li>
</ul>
<p><strong>Cette option a trois valeurs possibles:</strong></p>
<ul>
<li> <strong>FULL</strong>:toutes les transactions attendent d&#8217;être sauvegardées pour être purgées du fichier LDF. Il n&#8217;y a qu&#8217;une sauvegarde de transactions qui puisse les purger. Full est le mode par défaut pour toute nouvelle base utilisateur.</li>
<li><span style="text-decoration: underline;"> </span><strong>SIMPLE</strong>: les transactions validées sont purgées du journal à intervalles réguliers par un processus d&#8217;arrière plan, le CHECKPOINT. Cette option correspond à l&#8217;ancien mode &#8216;trunc log on checkpoint&#8217; qui existait en 7.0.</li>
<li> <strong>BULK_LOGGED</strong>: permet de minimiser la journalisation de certains ordres de modification massive de données, tels que les reconstructions d&#8217;indexes par exemple.</li>
</ul>
<p>Pour plus de détails sur ces différents modes, voir les épisodes <a href="http://blog.capdata.fr/index.php/modes-de-recuperation-et-journal-de-transactions-episode-1/">1</a> et <a href="http://blog.capdata.fr/index.php/modes-de-recuperation-et-journal-de-transactions-episode-2">2</a> sur les modes de récupération.</p>
<p>On peut lire cette option en utilisant la commande suivante:</p>
<pre><span style="color: #0000ff;">select DATABASEPROPERTYEX('CAPDATA','RECOVERY')
GO

<em>------------
FULL</em></span></pre>
<p>FULL est la valeur utilisée par défaut lors de la création d&#8217;une base. <strong>Ce qui signifie que si on ne fait jamais de sauvegardes de transactions sur une base dans ce mode, les transactions ne seront jamais purgées du journal</strong> et au bout de 6 mois ou d&#8217;un an on arrive à saturer l&#8217;espace disque, d&#8217;où le besoin de réduire la taille de ce fichier. Il existe une technique pour le ramener à sa taille initiale en moins de 2 minutes, comme expliqué dans le paragraphe suivant et dans la démo vidéo ci-dessus. Pour savoir si un backup transactionnel a déjà été exécuté sur une base, on peut interroger la table backupset de la base msdb:</p>
<pre><span style="color: #0000ff;">select backup_start_date, backup_finish_date, type, recovery_model
from msdb.dbo.backupset where database_name = 'CAPDATA' and type = 'L'

<em>backup_start_date              backup_finish_date         type          recovery_model
<span style="color: #0000ff;">--------------------------     -------------------------- ------------- -------------------</span></em><span style="color: #0000ff;">
</span></span><em><span style="color: #0000ff;">2010-09-07 18:54:42.000           2010-09-07 18:54:52.000            L           FULL</span></em></pre>
<h2>Comment le réduire:</h2>
<p><strong>En deux étapes:</strong></p>
<ul>
<li><strong><span style="text-decoration: underline;">Etape 1</span></strong>: on vide le journal de son contenu, ce qui revient à recycler simplement les transactions à l&#8217;intérieur de l&#8217;enveloppe, sans la réduire.</li>
<li><strong><span style="text-decoration: underline;">Etape 2</span></strong>: on réduit l&#8217;enveloppe sur disque: une fois le journal vidé, il peut être réduit avec la commande DBCC SHRINKFILE ().</li>
</ul>
<p>Si vous n&#8217;avez jamais fait de sauvegarde de journal, il y a des chances pour que la taille du fichier LDF soit assez conséquente. S&#8217;il s&#8217;agit de plusieurs dizaines ou centaines de Gb, et que vous faites des sauvegardes complètes tous les jours par exemple, ça ne sert à rien de sauvegarder toutes ces transactions qui sont entérinées depuis belle lurette. Si on regarde le contenu du journal de transactions avant de faire la manoeuvre;</p>
<pre><span style="color: #0000ff;">dbcc sqlperf(logspace)

<em>Database Name       Log Size (Mb)       Log Space Used (%)      Status
------------------  ------------------  ---------------------   ---------
</em></span><em><span style="color: #0000ff;">CAPDATA               25889,367                <span style="color: #ff0000;">91,7766</span>              0</span></em></pre>
<p>La technique consiste à passer la base en mode SIMPLE:</p>
<pre><span style="color: #0000ff;">ALTER DATABASE [CAPDATA] SET RECOVERY SIMPLE
GO</span></pre>
<p>Un checkpoint est exécutée automatiquement dès la fin de la commande. Toutes les transactions validées sont purgées du journal . Techniquement elles ne sont pas effacées mais leur conteneur physique  est réinitialisé et peut être réutilisé:</p>
<pre><span style="color: #0000ff;">dbcc sqlperf(logspace)
</span></pre>
<pre><span style="color: #0000ff;"><em>Database Name       Log Size (Mb)       Log Space Used (%)      Status
------------------  ------------------  ---------------------   ---------
</em><em>CAPDATA               25889,367                </em></span><span style="color: #ff0000;"><em>1,192919</em></span><span style="color: #0000ff;"><em>              0</em></span></pre>
<p>Il ne reste plus qu&#8217;à réduire le fichier journal de transactions avec un DBCC SHRINKFILE. A cette étape il faut décider quelle est la nouvelle taille à donner au journal de transactions. Si on le réduit à son maximum, il risque de grandir à nouveau pour atteindre sa taille de croisière, et si on souhaite de réagrandir ensuite, la portion allouée sera zéro-initialisée [1]. Idéalement il faudrait un historique d&#8217;utilisation de l&#8217;espace dans le journal pour savoir combien d&#8217;espace consomme la plus grosse transaction. Par exemple dans notre cas, 50Mb:</p>
<pre><span style="color: #0000ff;">select name from CAPDATA.sys.database_files where Type_DESC='LOG'</span></pre>
<pre><span style="color: #0000ff;"><em>name
---------------
GESTIMMO_log01</em></span></pre>
<pre><span style="color: #0000ff;">
dbcc shrinkfile('GESTIMMO_log01',50)</span></pre>
<pre><span style="color: #0000ff;">
<em>DbId   FileId      CurrentSize MinimumSize UsedPages   EstimatedPages
------ ----------- ----------- ----------- ----------- --------------
5      2           6400        6400        6400        6400</em></span></pre>
<p>6400 pages de 8192 octets = 50Mb, on a réussi à réduire l&#8217;enveloppe sur disque de 25Gb à 50Mb en quelques secondes.</p>
<h2>Quoi faire ensuite:</h2>
<p>Il faut décider de la stratégie de backup à adopter pour cette base: quelle quantité de données est-il acceptable de perdre: 1 heure, 4 heures, 1 journée, 1 semaine ?</p>
<ul>
<li>Si la réponse est inférieure à la fréquence des backups complets, alors il faut repasser la base en mode FULL, relancer un backup complet pour réinitialiser la chaîne de sauvegarde, puis planifier des backups de journaux via l&#8217;agent ou un plan de maintenance par exemple. Les backups de journaux permettent de garantir une restauration à tout point dans le temps en mode de récupération FULL.</li>
<li>Si la réponse est au moins égale à la fréquence des backups complets, alors laisser la base en mode SIMPLE, et l&#8217;espace sera automatiquement recyclé.</li>
</ul>
<p>A suivre un autre How-To sur les problèmes d&#8217;administration de base de SQL Server.</p>
<p>A+ David B.</p>
<h2>Notes:</h2>
<p>[1]: l&#8217;initialisation instantanée (IFI) ne s&#8217;applique qu&#8217;aux fichiers de données MDF et NDF, pas aux journaux de transactions.<strong>Continuez votre lecture sur le blog :</strong>
<ul class="similar-posts">
<li><a href="http://blog.capdata.fr/index.php/how-to-reduire-lenveloppe-de-tempdb/" rel="bookmark" title="7 juillet 2011">How-To : réduire l&#8217;enveloppe de tempdb</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/point-in-time-recovery-et-fn_dump_dblog/" rel="bookmark" title="13 juillet 2011">Point-in-time recovery et fn_dump_dblog()</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/pourquoi-il-faut-sauvegarder-les-bases-systemes/" rel="bookmark" title="3 juillet 2011">Pourquoi il faut sauvegarder les bases systèmes</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/openrowset-episode-1/" rel="bookmark" title="13 juillet 2011">OPENROWSET, épisode 1</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/suppression-accidentelle-de-ligne-comment-retrouver-le-coupable/" rel="bookmark" title="6 octobre 2011">Suppression accidentelle de ligne : comment retrouver le coupable ?</a> (David BAFFALEUF) [SQL Server]</li>
</ul>
<p><!-- Similar Posts took 4.263 ms -->
<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fhow-to-reduire-la-taille-du-journal-de-transactions-sur-disque%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fhow-to-reduire-la-taille-du-journal-de-transactions-sur-disque%2F&amp;style=normal&amp;b=2" height="61" width="50" /><br />
			</a>
		</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.capdata.fr/index.php/how-to-reduire-la-taille-du-journal-de-transactions-sur-disque/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>How-To : réduire l&#8217;enveloppe de tempdb</title>
		<link>http://blog.capdata.fr/index.php/how-to-reduire-lenveloppe-de-tempdb/</link>
		<comments>http://blog.capdata.fr/index.php/how-to-reduire-lenveloppe-de-tempdb/#comments</comments>
		<pubDate>Thu, 07 Jul 2011 08:25:28 +0000</pubDate>
		<dc:creator>David BAFFALEUF</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[tempdb]]></category>

		<guid isPermaLink="false">http://blog.capdata.fr/?p=2673</guid>
		<description><![CDATA[Pour rééquilibrer un peu le discours global des articles, dans lesquels on essaie d&#8217;aborder des sujets très peu étayés sur le net, et qui du coup peuvent paraître parfois obscurs à celui qui cherche juste comment  réduire son journal de transactions où changer son compte de service, nous allons partir sur une autre série de [...]]]></description>
			<content:encoded><![CDATA[<p>Pour rééquilibrer un peu le discours global des articles, dans lesquels on essaie d&#8217;aborder des sujets très peu étayés sur le net, et qui du coup peuvent paraître parfois obscurs à celui qui cherche juste comment  réduire son journal de transactions où changer son compte de service, nous allons partir sur une autre série de How-To sur des actions basiques, avec des démos sous la forme de petites vidéos. Et ce pour deux raisons principales:</p>
<p>- C&#8217;est plus pratique pour nous <img src='http://blog.capdata.fr/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' /> . Il m&#8217;est arrivé de passer plusieurs semaines sur un seul article donc ça ne va pas faire de mal de parler de choses communes, et en plus s&#8217;il y a une démo à l&#8217;appui&#8230;<br />
- Ça parle à plus de gens. Je suis conscient qu&#8217;on ne doit pas être très nombreux à s&#8217;intéresser aux schedulers non-préemptifs et aux IO completion ports (re- <img src='http://blog.capdata.fr/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' />  ).</p>
<p>Ces articles seront taggés &#8216;<em><strong>howto</strong></em>&#8216; dans le nuage de tags, vous pourrez donc les retrouver tous facilement. Pour ce premier, on va parler d&#8217;un sujet qui revient de temps en temps dans les forums, à savoir comment faire pour réduire la taille de tempdb sur SQL 2005 et SQL 2008.</p>
<h2>Démo sur SQL 2005:</h2>

<span id="video1" class="HDFLV">
<a href="http://www.macromedia.com/go/getflashplayer">Get the Flash Player</a> to see this player.</span>
<script type="text/javascript">
var s1 = new SWFObject("http://blog.capdata.fr/wp-content/plugins/contus-hd-flv-player/hdflvplayer/hdplayer.swf","n1","800height=600","400","7");
s1.addParam("allowfullscreen","true");
s1.addParam("allowscriptaccess","always");
s1.addParam("wmode","opaque");
s1.addVariable("baserefW","http://blog.capdata.fr");s1.addVariable("pid","1");
s1.addVariable("vid","3");
s1.write("video1");
</script>
<div id="htmlplayer2"><iframe  type="text/html" width="800height=600" height="400"  src="http://www.youtube.com/embed/Q7p07GnIxPw" frameborder="0">
</iframe> </div><script>var txt =  navigator.platform ;if(txt =="iPod"|| txt =="iPad"|| txt =="iPhone" || txt =="Linux armv7I")
            {   document.getElementById("htmlplayer2").style.display = "block";
                document.getElementById("video2").style.display = "none";
            }else{
 document.getElementById("htmlplayer2").style.display = "none";
            }
			 function failed(e) {
			  if(txt =="iPod"|| txt =="iPad"|| txt =="iPhone" || txt =="Linux armv7I")
            {
   alert("Player doesnot support this video.");
   }
}
        </script>
<h2>Sur SQL Server 2005:</h2>
<p>Parce que ça a l&#8217;air trivial comme ça, en théorie. Vous vous dites, trop facile je fais un dbcc shrinkfile du ou des fichiers de données de tempdb. Ou alors je fais un ALTER DATABASE tempdb MODIFY FILE (NAME=&#8230;, SIZE=&#8230;) et je redémarre.</p>
<p>Voyons voir:</p>
<pre><span style="color: #0000ff;">select serverproperty('productversion')
</span><span style="color: #0000ff;"><span style="color: #008000;">-- Taille initiale</span></span>
<span style="color: #0000ff;">SELECT size/128 FROM master.sys.master_files WHERE file_id=1 and database_id=2<span style="color: #008000;"> </span>
</span><span style="color: #0000ff;"><span style="color: #008000;"> -- Taille courante</span></span>
<span style="color: #0000ff;">select size/128 from tempdb.sys.database_files WHERE file_id=1
</span></pre>
<pre><em><span style="color: #0000ff;">---------------------------
9.00.4035.00
</span><span style="color: #0000ff;">
---------------------------
</span><span style="color: #0000ff;">1699
</span><span style="color: #0000ff;">
---------------------------
1699</span></em><span style="color: #0000ff;">
</span></pre>
<pre><span style="color: #0000ff;">alter database tempdb modify file(name='tempdev',size=500MB)</span>

<em><span style="color: #ff0000;">Msg 5039, Niveau 16, État 1, Ligne 2
Échec de MODIFY FILE. La taille spécifiée est inférieure à la taille en cours.</span></em></pre>
<p>Dans cette version de SQL Server, la base tempdb est considérée comme les autres bases: on ne peut pas réduire l&#8217;enveloppe à une valeur inférieure à la taille en cours. Quant au DBCC SHRINKFILE / SHRINKDATABASE, il ne va fonctionner que sur une base sans activité, or sur une instance en production, il y a toujours de l&#8217;activité dans tempdb: tris, agrégats, tables temporaires, version store, etc&#8230; Toute cette activité va le plus souvent empêcher DBCC SHRINKFILE de réduire l&#8217;enveloppe à la taille désirée:</p>
<pre><span style="color: #0000ff;">select name,size/128 from master.sys.master_files where database_id=2 and file_id=1
GO
</span>
<span style="color: #0000ff;"><em></em><em>-----------------
1699

</em></span><span style="color: #0000ff;">dbcc shrinkfile('tempdev',500)
GO
<em>
DbId	FileId	CurrentSize	MinimumSize	UsedPages	EstimatedPages
------- ------- --------------  --------------- --------------  --------------
2	1	200200	         1024	         200192	         200192</em></span></pre>
<p>Les tailles sont exprimées en nombre de pages. Taille en Mb = 200200/128 = 1564 Mb, donc on n&#8217;a pas récupéré grand chose. Vous pouvez tenter le shrinkfile avec moins d&#8217;activité si vous ne souhaitez pas une interruption de service, mais si ça ne fonctionne pas, il n&#8217;y qu&#8217;une seule façon de régler le problème: il faut redémarrer l&#8217;instance avec la configuration minimale:</p>
<p>Démarrer SQL Server manuellement dans un prompt DOS en utilisant les options -f -c:</p>
<pre><span style="color: #0000ff;"><strong>C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Binn&gt;sqlservr -c -f</strong>
2011-07-06 07:35:12.17 Server      Microsoft SQL Server 2005 - <span style="color: #ff0000;">9.00.4035.00</span> (Intel X86)
        Nov 24 2008 13:01:59
        Copyright (c) 1988-2005 Microsoft Corporation
        Developer Edition on Windows NT 5.1 (Build 2600: Service Pack 3)

2011-07-06 07:35:12.18 Server      (c) 2005 Microsoft Corporation.
2011-07-06 07:35:12.18 Server      All rights reserved.
2011-07-06 07:35:12.18 Server      Server process ID is 4076.
2011-07-06 07:35:12.18 Server      Authentication mode is MIXED.
2011-07-06 07:35:12.19 Server      Logging SQL Server messages in file
                                    'V:\DBA2\MSSQL.1\MSSQL\LOG\ERRORLOG'.
2011-07-06 07:35:12.19 Server      This instance of SQL Server last reported using a process
                                  ID of 3496 at 06/07/2011 07:34:18 (local) 06/07/2011 05:34:18
                                  (UTC). This is an informational message only;
 no user action is required.
2011-07-06 07:35:12.19 Server      Registry startup parameters:
2011-07-06 07:35:12.19 Server            -d V:\DBA2\MSSQL.1\MSSQL\DATA\master.mdf
2011-07-06 07:35:12.20 Server            -e V:\DBA2\MSSQL.1\MSSQL\LOG\ERRORLOG
2011-07-06 07:35:12.20 Server            -l V:\DBA2\MSSQL.1\MSSQL\DATA\mastlog.ldf
2011-07-06 07:35:12.20 Server      Command Line Startup Parameters:
2011-07-06 07:35:12.20 Server            -c
2011-07-06 07:35:12.20 Server            -f
<span style="color: #ff0000;">2011-07-06 07:35:12.26 Server      Warning: The server instance was started using minimal
                                   configuration startup option (-f). Starting an instance
                                   of SQL Server with minimal configuration places the server
                                   in single-user mode automatically.  After the server has been
                                   started with minimal configuration, you should change the appropriate
                                   server option value or values, stop, and then restart the server.</span>
2011-07-06 07:35:12.27 Serveur     SQL Server is starting at normal priority base (=7)...
2011-07-06 07:35:12.27 Serveur     Detected 2 CPUs. This is an informational message; no user action is required.
2011-07-06 07:35:12.94 Serveur     Using dynamic lock allocation.  Initial allocation of 2500 Lock blocks and 5000 Lock Owner blocks per node...
<span style="color: #ff0000;">2011-07-06 07:35:12.96 Serveur     Support for distributed transactions was not enabled for this instance of the Database Engine because it was started using the minimal
                                  configuration option. </span><span style="color: #ff0000;"> </span>
2011-07-06 07:35:12.97 spid5s      Starting up database 'master'.
2011-07-06 07:35:13.11 spid5s      Recovery is writing a checkpoint in database 'master' (1). This is an informational message only. No user action is required.
<span style="color: #ff0000;">2011-07-06 07:35:13.16 spid5s      Server started with '-f' option. Auditing will not be started. This is an informational message only; no user action is required.</span>
2011-07-06 07:35:13.18 spid5s      Starting up database 'mssqlsystemresource'.
2011-07-06 07:35:13.20 spid5s      The resource database build version is 9.00.4035. This is an informational message only. No user action is required.
2011-07-06 07:35:13.40 spid6s      Starting up database 'model'.
2011-07-06 07:35:13.40 spid5s      Server name is 'DBA2'. This is an informational message only. No user action is required.
(...)</span></pre>
<p>-f indique à SQL Server de démarrer avec les options minimales et en mode mono utilisateur (pas besoin d&#8217;utiliser le mode -m). Dans ce mode, tempdb reprend la taille initiale de la base model (2Mb), donc on va pouvoir faire passer un ALTER DATABASE MODIFY FILE.<br />
-c indique que SQL Server ne démarre pas en tant que service</p>
<p>A partir de là on se connecte dans une seconde fenêtre DOS:</p>
<pre><span style="color: #0000ff;">DOS&gt;sqlcmd -E
1&gt; select size/128 from tempdb.sys.database_files WHERE file_id=1
2&gt; go

<em>-----------
          2</em>

1&gt; alter database tempdb modify file(name='tempdev',size=500MB)
2&gt; go</span>
<span style="color: #0000ff;">1&gt; </span><span style="color: #0000ff;">exit
</span></pre>
<p>En on redémarre via SQL Configuration Manager.</p>
<h2>Sur SQL Server 2008:</h2>
<p>La bonne nouvelle, c&#8217;est qu&#8217;à partir de SQL Server 2008, on n&#8217;a plus besoin de redémarrer en mode minimal, un ALTER DATABASE MODIFY FILE fonctionne avec des tailles inférieures à la taille en cours (uniquement sur tempdb):</p>
<pre><span style="color: #0000ff;">select serverproperty('productversion')
<span style="color: #008000;">-- Taille initiale</span>
SELECT size/128 FROM master.sys.master_files WHERE file_id=1 and database_id=2
<span style="color: #008000;"> -- Taille courante</span>
select size/128 from tempdb.sys.database_files WHERE file_id=1
<em>
-------------------
10.0.2757.0

-------------------
1564

-------------------
1564</em>

alter database tempdb modify file(name='tempdev',size=500MB)

<em>Command(s) completed successfully.</em></span></pre>
<p>Suivi d&#8217;un arrêt / redémarrage de l&#8217;instance avec SQL Configuration Manager.</p>
<p>A suivre un autre how-to SQL Server. A+</p>
<p>David B.</p>
<h2>Liens:</h2>
<p><a href="http://support.microsoft.com/kb/307487/en-us">http://support.microsoft.com/kb/307487/en-us</a></p>
<div id="_mcePaste" style="overflow: hidden; position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px;">9.00.4035.00</div>
<p><strong>Continuez votre lecture sur le blog :</strong>
<ul class="similar-posts">
<li><a href="http://blog.capdata.fr/index.php/pourquoi-il-faut-sauvegarder-les-bases-systemes/" rel="bookmark" title="3 juillet 2011">Pourquoi il faut sauvegarder les bases systèmes</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/how-to-reduire-la-taille-du-journal-de-transactions-sur-disque/" rel="bookmark" title="11 juillet 2011">How-To: réduire la taille du journal de transactions sur disque</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/bench-avec-netapp-datacore-esx/" rel="bookmark" title="26 avril 2011">Bench avec NetApp / Datacore / ESX</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/openrowset-episode-1/" rel="bookmark" title="13 juillet 2011">OPENROWSET, épisode 1</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/point-in-time-recovery-et-fn_dump_dblog/" rel="bookmark" title="13 juillet 2011">Point-in-time recovery et fn_dump_dblog()</a> (David BAFFALEUF) [SQL Server]</li>
</ul>
<p><!-- Similar Posts took 4.436 ms -->
<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fhow-to-reduire-lenveloppe-de-tempdb%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fhow-to-reduire-lenveloppe-de-tempdb%2F&amp;style=normal&amp;b=2" height="61" width="50" /><br />
			</a>
		</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.capdata.fr/index.php/how-to-reduire-lenveloppe-de-tempdb/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>I/O asynchrones (épisode 1)</title>
		<link>http://blog.capdata.fr/index.php/io-asynchrones-episode-1/</link>
		<comments>http://blog.capdata.fr/index.php/io-asynchrones-episode-1/#comments</comments>
		<pubDate>Tue, 05 Jul 2011 16:02:20 +0000</pubDate>
		<dc:creator>David BAFFALEUF</dc:creator>
				<category><![CDATA[Operating System]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[I/O]]></category>

		<guid isPermaLink="false">http://blog.capdata.fr/?p=1868</guid>
		<description><![CDATA[Ce post est le premier d&#8217;une série sur les API systèmes impliquées au niveau de SQL Server:

Gestion des entrées/sorties: alignement, I/Os asynchrones, alternate streams, scatter gather, I/O completion ports, etc&#8230;
Le multithreading:  synchronisation, events, mutexes, spinlocks, sections critiques&#8230;
La mémoire: allocation, fichiers mappés, AWE, large pages&#8230;

Cette série plutôt barbare va dévier un peu des sujets SGBD traditionnels. [...]]]></description>
			<content:encoded><![CDATA[<p>Ce post est le premier d&#8217;une série sur les API systèmes impliquées au niveau de SQL Server:</p>
<ol>
<li><strong>Gestion des entrées/sorties</strong>: alignement, I/Os asynchrones, alternate streams, scatter gather, I/O completion ports, etc&#8230;</li>
<li><strong>Le multithreading</strong>:  synchronisation, events, mutexes, spinlocks, sections critiques&#8230;</li>
<li><strong>La mémoire:</strong> allocation, fichiers mappés, AWE, large pages&#8230;</li>
</ol>
<p>Cette série plutôt barbare va dévier un peu des sujets SGBD traditionnels. Elle s&#8217;adresse à toute personne souhaitant approfondir les mécanismes intrinsèques de SQL Server, pour en comprendre la logique et les contraintes. Ces articles seront classés sous les sujets <a href="http://blog.capdata.fr/index.php/category/operating-system/">Operating System</a> et <a href="http://blog.capdata.fr/index.php/category/sqlserver/">SQL Server</a>. Leur lecture nécessite que vous ayez des connaissances minimum sur C/C++ et l&#8217;API win32.</p>
<p>Dans un post <a href="http://blog.capdata.fr/index.php/consistence-des-ecritures-avec-sata">précédent</a>, nous avions déjà effleuré la question de l&#8217;écriture à travers le cache, nous reprendrons le même exemple pour évoquer le sujet du jour: les I/Os asynchrones dans des fichiers. C&#8217;est une question large donc je propose d&#8217;éclater le sujet en trois épisodes:</p>
<ol>
<li> Ce premier pour expliquer le principe et montrer à travers un petit exemple simple  les avantages, mais aussi les contraintes de faire des I/Os asynchrones.</li>
<li>Un second pour montrer comment gérer plus efficacement  la synchronisation des I/Os asynchrones entre plusieurs threads, notamment avec l&#8217;utilisation d&#8217;I/O completion ports.</li>
<li> Un dernier pour montrer l&#8217;utilisation qui en est faite par SQL Server, ainsi que certaines problématiques particulières.</li>
</ol>
<h2>SYNCHRONE vs ASYNCHRONE:</h2>
<p>Quand on écrit un programme sous windows, on doit tôt ou tard accéder à un device en lecture / écriture. La plupart des applications que l&#8217;on utilise n&#8217;ont pas besoin d&#8217;écrire très souvent des  données durables. Souvent elles misent sur la bufferisation des entrées / sorties, c&#8217;est à dire l&#8217;écriture ou la lecture de données en mémoire. Parfois cependant, elles devront lire des données sur disque, par exemple lorsqu&#8217;elles démarrent, ou bien écrire des informations dans un fichier de configuration lorsqu&#8217;elles s&#8217;arrêtent. Le moyen de plus simple de lire ou d&#8217;écrire dans un fichier est alors de lancer une entrée/sortie <span style="color: #000000;">synchrone </span>en utilisant ReadFile() ou WriteFile().</p>
<p>On utilise le terme synchrone parce que le thread qui a initié cette écriture ou cette lecture doit attendre l’acquittement ou le retour de l&#8217;entrée/sortie avant de pouvoir continuer son exécution. Dès que la demande de lecture ou d&#8217;écriture est lancée, il entre immédiatement dans un état d&#8217;attente, il ne peut rien faire d&#8217;autre. De la sorte, s&#8217;il doit lancer de nombreuses opérations d&#8217;entrées sorties, il va devoir attendre que chacune soit terminée avant de pouvoir exécuter la suivante, ce qui va limiter en quelque sorte sa capacité à traiter un grand nombre d&#8217;opérations par seconde.</p>
<p>L&#8217;alternative est d&#8217;indiquer au thread de ne pas attendre le retour de l&#8217;I/O pour continuer son exécution, en postant des I/Os <span style="color: #000000;">asynchrones </span>(ou overlapped IO). Il existe plusieurs contraintes associées à l&#8217;utilisation des IO asynchrones, qui rendent la chose nettement plus compliquée à gérer:</p>
<ul>
<li>Lors d&#8217;une IO synchrone, le device object maintient un pointeur d&#8217;offset de sorte que si on lance deux lectures de 8K à la suite par exemple, le kernel sait toujours là où il s&#8217;est arrêté et là où il doit reprendre. D&#8217;ailleurs, si on regarde le prototype de <a href="http://msdn.microsoft.com/en-us/library/aa365467%28v=vs.85%29.aspx">ReadFile()</a> ou <a href="http://msdn.microsoft.com/en-us/library/aa365747%28v=vs.85%29.aspx">WriteFile()</a>, il n&#8217;y a pas d&#8217;information d&#8217;offset passées lors de l&#8217;appel. Avec une IO asynchrone, le pointeur d&#8217;offset est ignoré par le driver donc il faut le gérer dans le code. C&#8217;est une des différences les plus caractéristiques entre ces deux modes, et un vrai casse tête comme on va le voir dans un instant.</li>
<li>Il y a de surcroît forcément un point dans le code où on ne  peut plus avancer sans avoir le retour de ces IO. Il faut donc trouver un moyen de se synchroniser avec la fin des opérations.</li>
</ul>
<p><strong>Dans la pratique, chaque thread  peut se synchroniser de 4 manières différentes:</strong></p>
<ol>
<li>Sur le fichier lui-même, car le fichier est un handle comme un autre. Ce serait le plus simple, il n&#8217;y a pas d&#8217;autre objet à créer. Le problème est que s&#8217;il y a plusieurs I/Os en cours sur le même fichier, on ne pourra pas déterminer laquelle vient de se terminer car le handle est signalé dès que la première I/O est retournée.</li>
<li>Sur un event associé à l&#8217;I/O. Comme nous allons le voir dans la section suivante, on utilise une structure comme traceur pour suivre chaque entrée/sortie asynchrone et cette structure contient par défaut un event associé, sur lequel le thread pourra se synchroniser. C&#8217;est la méthode que l&#8217;on va aborder dans cet épisode.</li>
<li>En utilisant une routine APC. A chaque thread créé avec beginthreadex() est associé une APC queue qui lui permet d&#8217;exécuter une routine de complétion lorsque sa tache est terminée. On peut utiliser ces routines avec les primitives WriteFileEx() et ReadFileEx(). L&#8217;avantage principal de cette méthode est que l&#8217;exécution de l&#8217;APC se fait en user mode, donc le programme conserve le contrôle pendant toute l&#8217;opération. Malgré ces quelques atouts, elle a une réputation calamiteuse [1] et reste compliquée à gérer. Le simple fait de gérer la completion dans une fonction séparée rend les choses beaucoup moins maintenables et débogables.</li>
<li>Sur un outil de synchronisation plus perfectionné, comme un I/O completion port. SQL Server utilise un I/O completion port par CPU-node pour gérer les entrées / sorties réseau. Nous verrons l&#8217;utilisation d&#8217;un tel outil dans l&#8217;épisode 2 pour gérer des I/O dans des fichiers.</li>
</ol>
<h2>Principes d&#8217;utilisation dans le code:</h2>
<h3>La structure OVERLAPPED</h3>
<p>Que l&#8217;on utilise des entrées/sorties synchrones ou asynchrones, les primitives utilisées sont les mêmes [2], on leur passera simplement des paramètres différents. Pour pouvoir pister nos entrées / sorties asynchrones, on va utiliser un genre de traceur sous la forme d&#8217;une structure C appelée  <a href="http://msdn.microsoft.com/en-us/library/ms684342%28v=vs.85%29.aspx"><strong>Overlapped</strong></a>. Si on regarde sa composition de plus près:</p>
<pre><span style="color: #0000ff;">typedef struct _OVERLAPPED {
  ULONG_PTR Internal;
  ULONG_PTR InternalHigh;
  union {
    struct {
      DWORD Offset;
      DWORD OffsetHigh;
    };
    PVOID  Pointer;
  };
  HANDLE    hEvent;
} OVERLAPPED, *LPOVERLAPPED;</span></pre>
<p>Parmi les propriétés importantes pour le code utilisateur, <span style="color: #0000ff;">Offset </span>et <span style="color: #0000ff;">OffsetHigh </span>délimitent une portion de 64 bits de données et indiquent à quel endroit on va commencer d&#8217;écrire dans le fichier. <span style="color: #0000ff;">Offset </span>gère les 32 premiers bits pour les fichiers &lt; 4Gb (souvenez-vous FAT 32&#8230;), et <span style="color: #0000ff;">OffsetHigh </span>les 32 suivants pour les fichiers de plus de 4Gb.</p>
<p>Si on exécutait deux IO synchrones, elles s&#8217;exécuteraient l&#8217;une derrière l&#8217;autre et le kernel s&#8217;occupperait d&#8217;indiquer à la seconde écriture où elle doit commencer, parce que la première s&#8217;est terminée et donc on sait où on en est. Mais dans le cas d&#8217;écritures asynchrones, on n&#8217;a pas cette information, donc si on exécute une seconde entrée/sortie avant que la première ne soit terminée, il faut pouvoir gérer les offsets soi-même. C&#8217;est le rôle de ces deux propriétés, mais c&#8217;est à nous de les maintenir. Lors d&#8217;une première exécution, ces deux valeurs doivent être initalisées (souvent à 0 pour marquer le premier offset en début de fichier):</p>
<pre><span style="color: #0000ff;">OVERLAPPED ov;
ov.Offset = 0;
ov.OffsetHigh = 0;</span></pre>
<p>De son côté, <span style="color: #0000ff;">hEvent </span>est l&#8217;event  que l&#8217;on va pouvoir utiliser pour synchroniser le thread sur l&#8217;I/O asynchrone. Si on utilise cette technique, alors il va falloir l&#8217;initialiser avec CreateEvent() en lui passant un event à reset manuel et dans l&#8217;état non &#8211; signalé (second et troisième paramètres, cf MSDN <a href="http://msdn.microsoft.com/en-us/library/ms682396%28v=vs.85%29.aspx">CreateEvent()</a>)</p>
<p>Une structure OVERLAPPED est associée à chaque I/O asynchrone que l&#8217;on va effectuer. Si on en lance 8 en même temps, il faudra créer et initialiser 8 structures. On peut utiliser un OVERLAPPED [] pour stocker toutes nos instances de structure par exemple. La chose la plus importante au sujet d&#8217;OVERLAPPED, est qu&#8217;elle doit toujours être accessible pendant toute la durée de vie de l&#8217;I/O, donc pas question d&#8217;allouer le tableau d&#8217;OVERLAPPED[] sur la stack. Le cas d&#8217;erreur le plus courant est d&#8217;exécuter un appel à ReadFile() ou WriteFile() dans une fonction et gérer la complétion dans le main ou une autre fonction, par exemple:</p>
<pre><span style="color: #0000ff;">VOID ReadMyFile(HANDLE h)
{
    OVERLAPPED ov;
    BYTE b[255];
    ov.Offset=0;
    ov.OffsetHigh=0;
<span style="color: #ff0000;">    ReadFile(h,b,255,NULL,&amp;ov);</span>
}</span></pre>
<p>Lorsque la fonction sort la stack est vidée alors que le driver pointe encore vers <span style="color: #0000ff;">&amp;ov</span> qui n&#8217;est déjà plus valide.</p>
<p>Pour indiquer à Windows que l&#8217;on souhaite faire des I/O asynchrones sur un fichier, on utilise un flag spécifique<strong> </strong><span style="color: #0000ff;"><strong>FILE_FLAG_OVERLAPPED</strong></span>, que l&#8217;on passe à CreateFile(), tel que:</p>
<pre><span style="color: #008000;">// Ouverture du fichier Writethrough + No Buffering + Overlapped ----------------------------
</span><span style="color: #0000ff;">HANDLE hOutputFile=CreateFile(argv[1]
       ,GENERIC_READ
       | GENERIC_WRITE
       ,0
       ,NULL
       ,CREATE_ALWAYS
       ,FILE_ATTRIBUTE_NORMAL
       | FILE_FLAG_WRITE_THROUGH
       | FILE_FLAG_NO_BUFFERING
      <span style="color: #ff6600;"> | FILE_FLAG_OVERLAPPED</span>
       ,NULL);

 if (INVALID_HANDLE_VALUE==hOutputFile)
 {
       printf("Unable to open file %s.  Last error=%d\n",argv[1],GetLastError());
       return 1;
 }</span></pre>
<p>A noter que cet exemple utilise aussi des I/Os non bufferisées comme nous l&#8217;avons déjà vu dans <a href="http://blog.capdata.fr/index.php/consistence-des-ecritures-avec-sata/">l&#8217;article sur SATA</a>. SQL Server utilise précisément tous ces flags pour ouvrir les fichiers MDF, NDF et LDF. Il ne reste plus qu&#8217;à appeler ReadFile() ou WriteFile() en passant la structure Overlapped initialisée:</p>
<pre><span style="color: #0000ff;">WriteFile(hOutput,csBuffer,dg.BytesPerSector,&amp;dwBytesWritten,&amp;ov)</span></pre>
<h2>Gestion de Offset et OffsetHigh</h2>
<p>Si on exécute au moins deux appels à la suite à ReadFile() ou WriteFile() , Windows ne sait pas où le premier en est et où le second peut commencer à lire ou à écrire. C&#8217;est donc au programme utilisateur de comptabiliser les offsets exacts où chaque IO va devoir s&#8217;appliquer. On utilise pour cela les deux propriétés Offset et OffsetHigh de la structure Overlapped.</p>
<p>Historiquement, le compilateur C qui supporte l&#8217;API win32 ne comporte pas de type natif sur 64 bits (ce n&#8217;est pas le cas du compilateur VC++). Donc on ne peut pas tracer des offsets d&#8217;une valeur supérieure à 4Gb. Et encore aujourd&#8217;hui, il faut jongler avec une construction de type <a href="http://msdn.microsoft.com/en-us/library/aa383713%28v=vs.85%29.aspx">LARGE_INTEGER</a> pour arriver à jouer avec des offsets au delà de 2e32:</p>
<pre><span style="color: #0000ff;"> typedef union LARGE_INTEGER {
    struct {
        DWORD LowPart;
        LONG HighPart;
       };
    LONGLONG QuadPart;
};</span></pre>
<p>En fait le type LARGE_INTEGER est une union d&#8217;un DWORD et d&#8217;un LONG pour former un LONGLONG de 64 bits. Si on considère le schéma ci-dessous:</p>
<p><a href="http://blog.capdata.fr/wp-content/uploads/2011/07/large_integer.jpg"><img class="size-full wp-image-2678 alignleft" title="large_integer" src="http://blog.capdata.fr/wp-content/uploads/2011/07/large_integer.jpg" alt="" width="952" height="157" /></a></p>
<p>On utilisera donc QuadPart pour allouer de nouvelles valeurs d&#8217;offsets sur 64 bits puis LowPart et HighPart pour les passer respectivement à Offset et OffsetHigh, quelque chose comme :</p>
<pre><span style="color: #0000ff;">OffsethOutput.QuadPart=0;
while(!EOF)
{
    ov.Offset        = OffsethOutput.LowPart;
    ov.OffsetHigh    = OffsethOutput.HighPart;
    BOOL b = Readfile (..., &amp;ov);
</span><span style="color: #0000ff;"><span style="color: #0000ff;"> </span>   OffsethOutput.QuadPart += (LONGLONG) dg.BytesPerSector;
}
</span></pre>
<h3>Gérer le retour de ReadFile() / WriteFile():</h3>
<p>Voici un exemple d&#8217;appel à WriteFile() avec une structure overlapped tel que codé dans l&#8217;exemple:</p>
<pre><span style="color: #0000ff;">if (!WriteFile(hOutput,csBuffer,dg.BytesPerSector,&amp;dwBytesWritten,&amp;ov))
{
     DWORD dwLastErr=GetLastError();            
     if (ERROR_IO_PENDING == dwLastErr)
     {    
         isASYNCWRITE=TRUE;    <span style="color: #008000;">// On a une IO asynchrone</span>
         OffsethOutput.QuadPart += (LONGLONG) dg.BytesPerSector;    <span style="color: #008000;">// On décale Offset et OffsetHigh de la valeur de l'IO</span>
     }            
     else
     {
          if (ERROR_SUCCESS == dwLastErr)        <span style="color: #008000;">// C'est une IO synchrone</span>
          isASYNCWRITE=FALSE;
          else
          {
              printf("<span style="color: #008000;"><span style="color: #ff0000;">Erro</span><span style="color: #ff0000;"><span style="color: #ff0000;">r</span> when Writing to file %S. Last error=%d\n</span></span>",argv[1],GetLastError());
              return -1;
          }
      }
}</span></pre>
<p>Déjà quelque chose doit vous interpeller à ce niveau-là du code. Dans les faits, on n&#8217;a jamais la garantie à 100% d&#8217;obtenir du système qu&#8217;il poursuive pour nous une IO asynchrone, car l&#8217;IO Manager a toujours le dernier mot. Il peut décider pour une raison ou pour une autre de transformer cette demande en IO synchrone. Donc il faut toujours tester le code retour de WriteFile() quand on lui passe une structure OVERLAPPED:</p>
<ul>
<li><strong>ERROR_SUCCESS </strong>=&gt;Le kernel décide de transformer l&#8217;appel en IO synchrone. Il peut y avoir plusieurs raisons, notamment lorsqu&#8217;on écrit dans un fichier encrypté NTFS, ou lorsque l&#8217;écriture déclenche une augmentation de taille du fichier, cela doit vous rappeler certaines choses côté sikouel&#8230;  Une autre condition dans le cas d&#8217;un ReadFile() est que le kernel est capable d’accommoder la lecture immédiatement parce que la page demandée se trouve déjà dans le cache NTFS, ce qui ne sera jamais notre cas puisqu&#8217;on utilise FILE_FLAG_NO_BUFFERING.</li>
<li><strong>ERROR_IO_PENDING </strong> =&gt; dans ce cas l&#8217;IO est asynchrone. Ça ne sert donc à rien  d&#8217;attendre de WriteFile() qu&#8217;il nous renvoie une valeur d&#8217;octets écrits puisque lorsqu&#8217;il retourne au caller, il ne le sait pas lui-même. L&#8217;IO va se terminer quelque part dans le futur, donc pour l&#8217;instant on n&#8217;aura rien dans &amp;dwBytesWritten. Dans ce cas on indique la taille de cluster comme incrément au QuadPart pour la prochaine valeur d&#8217;Offset à lire.</li>
<li><strong>ERROR_HANDLE_EOF  =&gt;</strong> peut être retournée lorsqu&#8217;on a atteint la fin du fichier par exemple dans le cas d&#8217;une lecture, mais comme on écrit on ne le teste pas dans notre cas.</li>
<li><strong>AUTRE </strong>=&gt; auquel cas c&#8217;est une erreur et il faut la traiter.</li>
</ul>
<h3>Récupérer le résultat de l&#8217;IO une fois terminée avec GetOverlappedResult()</h3>
<p>Utiliser des IO asynchrones, c&#8217;est un peu comme jongler avec plusieurs balles à la fois: au bout d&#8217;un moment il va falloir les rattraper. Il faut donc choisir un point dans le code où on va devoir se synchroniser pour attendre le retour de ces IO avant de pouvoir continuer. On peut utiliser les primitives standard de synchronisation telles que <a href="http://msdn.microsoft.com/en-us/library/ms687032%28v=vs.85%29.aspx">WaitForSingleObject()</a>, et dans notre cas puisqu&#8217;on attend le retour de plusieurs IO, <a href="http://msdn.microsoft.com/en-us/library/ms687025%28v=vs.85%29.aspx">WaitForMultipleObjects()</a>.On bloque en passant à la primitive le ou les events qui attendent d&#8217;être signalés:</p>
<pre><span style="color: #0000ff;">dwEventSignPos = WaitForMultipleObjects(MAXOUTSTANDINGIO, hEvents, FALSE, INFINITE) - WAIT_OBJECT_0;</span><span style="color: #0000ff;">
</span></pre>
<p>Lorsqu&#8217;un premier event est signalé parce que son IO vient de se terminer, WaitForMultipleObjects() retourne WAIT_OBJECT_0, et le code peut se poursuivre. WAIT_OBJECT_0 représente le premier event qui vient d&#8217;être signalé, on va s&#8217;en servir pour déterminer l&#8217;indice de cet event dans un tableau, comme dans notre exemple commenté ci-dessous. On va ensuite devoir récupérer le nombre d&#8217;octets écrits, et si l&#8217;on souhaite réutiliser la structure Overlapped, réinitialiser son event, puis rebloquer en attendant le retour d&#8217;une autre IO:</p>
<pre><span style="color: #0000ff;">ResetEvent (hEvents[dwEventSignPos]);</span></pre>
<p>Il existe plusieurs primitives win32 pour gérer la fin d&#8217;une IO asynchrone:</p>
<ol>
<li>Utiliser <a href="http://msdn.microsoft.com/en-us/library/ms683209%28v=vs.85%29.aspx">GetOverlappedResults()</a>: permet de récupérer la quantité d&#8217;octets écrits ou lus, ainsi que l&#8217;event qui a été signalé.</li>
<li>Utiliser <a href="http://msdn.microsoft.com/en-us/library/ms683244%28v=vs.85%29.aspx">HasOverlappedIOCompleted()</a>: renvoie vrai ou faux en fonction de la completion de l&#8217;IO. Les informations d&#8217;octets lus ou écrits sont disponibles dans la structure Overlapped (Internal contient le code retour et InternalHigh le nombre d&#8217;octets lus ou écrits). Dans ce cas on n&#8217;aurait pas besoin de bloquer avec WaitForMultipleObjects(), mais il faut prévoir plusieurs points dans le code où faire régulièrement cette vérification. C&#8217;est de cette manière que SQL Server procède, comme on le verra dans l&#8217;épisode 3. Dans l&#8217;exemple:</li>
</ol>
<pre><span style="color: #0000ff;">if (isASYNCWRITE)
{
    if (!GetOverlappedResult(hOutput,&amp;olwrite[dwEventSignPos],&amp;dwBytesWritten[dwEventSignPos],TRUE))
    {
        printf("<span style="color: #ff0000;">Error getting pending write IO.  Last error=%d\n</span>",GetLastError());
        break;
    }
    else
    {
   <span style="color: #008000;"> // Action principale: on totalise chaque fin d'écriture async</span>
    llTotalBytesWritten+=dwBytesWritten[dwEventSignPos];
    }
}</span></pre>
<p>Il est important de n&#8217;exécuter GetOverlappedResults() que si l&#8217;IO est bien asynchrone, c&#8217;est pourquoi on teste avec un booléen d&#8217;abord.</p>
<p>Voilà, on a fait le tour, place à l&#8217;exemple commenté !</p>
<h2>Exemple commenté d&#8217;utilisation des I/Os asynchrones:</h2>
<p>Cet exemple reprend le principe de ce qui avait été utilisé dans l&#8217;<a href="http://blog.capdata.fr/index.php/consistence-des-ecritures-avec-sata/">article sur SATA</a>. Il utilise simplement des écritures asynchrones en plus. Évidemment ça n&#8217;a pas grand intérêt de faire des écritures asynchrones dans ce contexte là (mono-thread, on lance un nombre d&#8217;IO puis on bloque sur la completion de l&#8217;ensemble), mais il a l&#8217;avantage d&#8217;être simple et de ne pas nous embarquer dans des notions hors cadre de synchronisation de threads et compagnie&#8230; Encore une fois, il s&#8217;agit juste de comprendre le principe de mise en place.</p>
<p>On définit une quantité de données à écrire dans un fichier (FileSizeInBytes, par défaut 10Mb) ainsi qu&#8217;un certain nombre d&#8217;outstanding IOs (MAXOUTSTANDINGIO), c&#8217;est à dire d&#8217;IO simultanées. On ne peut envoyer plusieurs IO simultanées que si on effectue des IO asynchrones, c&#8217;est pourquoi on dit que des compteurs tels que &#8216;Current Disk Queue Length&#8217; par exemple peuvent être normalement élevés avec SQL Server, car on envoie plusieurs IOs en même temps.</p>
<pre><span style="color: #0000ff;"><span style="color: #008000;">// writethrougasync.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include &lt;windows.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;winioctl.h&gt;
#include &lt;direct.h&gt;</span>

<span style="color: #008000;">#define MAXOUTSTANDINGIO 4            // Definit le nb d'IO max en cours
#define FileSizeInBytes 10485760    // Definit la taille max du fichier</span>

int _tmain(int argc, _TCHAR* argv[])
{
    if (argc!=2)
    {
        printf("<span style="color: #ff0000;">Usage is: writethrougasync &lt;destfile&gt; \n</span>");
        printf("<span style="color: #ff0000;">Example:  writethrougasync destfile.dat \n</span>");
        return -1;
    }

   <span style="color: #008000;"> // Initialisation des différents objets nécessaires: OLs, events, HANDLES, etc... ------------------------------</span>
    OVERLAPPED olwrite[MAXOUTSTANDINGIO];    <span style="color: #008000;">// Ol IO pour l'écriture</span>
    HANDLE hEvents[MAXOUTSTANDINGIO];        <span style="color: #008000;">// tableau pour stocker les events écritures</span>

    HANDLE hOutput;                             <span style="color: #008000;">// HANDLE fichier dest</span>
    LARGE_INTEGER OffsethOutput;                <span style="color: #008000;">// (Offsets,OffsetHigh) olwrite </span>

    DWORD dwBytesWritten[MAXOUTSTANDINGIO];     <span style="color: #008000;">// Nb octets lus par olwrite</span>
    LONGLONG llTotalBytesWritten=0;             <span style="color: #008000;">// Quantité totale d'octets écrits</span>
    BOOL isASYNCWRITE=FALSE;                    <span style="color: #008000;">// Flag de contrôle IO async / sync pour les écritures</span>
    int NBIOCOMPLETED=0;                       <span style="color: #008000;"> // Compteur d'IOs terminées</span>
    DWORD dwEventSignPos=0;                     <span style="color: #008000;">// Index de l'event signalé retourné par WaitForMultipleObjects()</span>
<span style="color: #008000;">
    // Récupération de la taille du secteur disque avec un IOCTL ---------------------------------------------------</span>
    DISK_GEOMETRY dg;
    HANDLE hDrive;
    DWORD ioctlJnk; 

    hDrive=CreateFile(TEXT("<span style="color: #ff0000;">\\\\.\\PhysicalDrive0</span>")
                           ,0
                           ,FILE_SHARE_READ
                          | FILE_SHARE_WRITE
                          ,NULL
                          ,OPEN_EXISTING
                          ,0
                          ,NULL);

    if (INVALID_HANDLE_VALUE==hDrive)
    {
        printf("<span style="color: #ff0000;">Unable to open drive PhysicalDrive0. Last error=%d\n</span>",GetLastError());
        return 1;
    }

    if (! DeviceIoControl(hDrive
                          ,IOCTL_DISK_GET_DRIVE_GEOMETRY
                          ,NULL
                          ,0
                          ,&amp;dg
                          ,sizeof(dg)
                          ,&amp;ioctlJnk
                          ,NULL) )
    {
        printf("<span style="color: #ff0000;">Error on IOCTL (IOCTL_DISK_GET_DRIVE_GEOMETRY) to %s.  Last error=%d\n</span>",argv[1],GetLastError());
        return 1;
    }

    CloseHandle(hDrive);

    <span style="color: #008000;">// Ouverture du fichier cible en write-though / no buffering / Overlapped -------------------------------------</span>
    hOutput = CreateFile(argv[1]
                         ,GENERIC_READ
                         | GENERIC_WRITE
                          ,0
                          ,NULL
                          ,CREATE_ALWAYS
                          ,FILE_ATTRIBUTE_NORMAL
                          | FILE_FLAG_NO_BUFFERING
                          | FILE_FLAG_WRITE_THROUGH
                          | FILE_FLAG_OVERLAPPED
                          ,NULL);

    if (INVALID_HANDLE_VALUE==hOutput)
    {
        printf("<span style="color: #ff0000;">Unable to open output file %S.  Last error=%d\n</span>",argv[1],GetLastError());
        return -1;
    }

  <span style="color: #008000;">  // Initialisation du tampon --------------------------------------------------------------------------------</span>
    wchar_t *csBuffer = (wchar_t *)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,dg.BytesPerSector * sizeof(wchar_t));
    int len = swprintf_s(csBuffer,dg.BytesPerSector,L"<span style="color: #ff0000;">abcdefghijklmnopqrstuvwxyz</span>");    

    <span style="color: #008000;">// On initialise les events</span>
    for(int i=0;i&lt;MAXOUTSTANDINGIO;i++)
    {        
        hEvents[i] = olwrite[i].hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
        if (hEvents[i] == NULL)
        {
             printf("<span style="color: #ff0000;">Unable to create event for olwrite Last error=%d\n</span>",GetLastError());
             return -1;
        }
    }

   <span style="color: #008000;"> // On envoie les écritures ----------------------------------------------------------------------------------</span>
    OffsethOutput.QuadPart=0;

    while (OffsethOutput.QuadPart &lt; FileSizeInBytes)
    {
         <span style="color: #008000;">// On envoie MAXOUTSTANDINGIO écritures ASYNC si possible</span>
         for(int i=0;i&lt;MAXOUTSTANDINGIO;i++)
         {        
              <span style="color: #008000;">// Mise à jour Offset + OffsetHigh pour olwrite</span>
              olwrite[i].Offset        = OffsethOutput.LowPart;
              olwrite[i].OffsetHigh    = OffsethOutput.HighPart;

              if (!WriteFile(hOutput,csBuffer,dg.BytesPerSector,&amp;dwBytesWritten[i],&amp;olwrite[i]))
              {
                   DWORD dwLastErr=GetLastError();            
                   if (ERROR_IO_PENDING == dwLastErr)
                   {    
                       isASYNCWRITE=TRUE;    <span style="color: #008000;">// On a une IO asynchrone</span>
                   }            
                   else
                   {
                       if (ERROR_SUCCESS == dwLastErr)       <span style="color: #008000;"> // C'est une IO synchrone</span>
                            isASYNCWRITE=FALSE;
                       else
                       {
                           printf("<span style="color: #ff0000;">Error when Writing to file %S. Last error=%d\n</span>",argv[1],GetLastError());
                           return -1;
                       }
                   }</span>
                   <span style="color: #0000ff;">OffsethOutput.QuadPart += (LONGLONG) dg.BytesPerSector;  </span>  <span style="color: #008000;">// On décale Offset et OffsetHigh de la valeur de l'IO</span>
<span style="color: #0000ff;">             }
         }

         NBIOCOMPLETED =0;
         while(NBIOCOMPLETED &lt; MAXOUTSTANDINGIO)
         {
             <span style="color: #008000;">// On commence à se synchroniser sur les MAXOUTSTANDINGIO écritures envoyées</span>
             dwEventSignPos = WaitForMultipleObjects(MAXOUTSTANDINGIO, hEvents, FALSE, INFINITE) - WAIT_OBJECT_0;

            <span style="color: #008000;">// Une IO est terminée</span>
            NBIOCOMPLETED++;
            ResetEvent (hEvents[dwEventSignPos]);   <span style="color: #008000;"> // on reset l'event</span>

            if (isASYNCWRITE)
            {
                if (!GetOverlappedResult(hOutput,&amp;olwrite[dwEventSignPos],&amp;dwBytesWritten[dwEventSignPos],TRUE))
                {
                    printf("<span style="color: #ff0000;">Error getting pending write IO.  Last error=%d\n</span>",GetLastError());
                    break;
                }
                else
                {
                     /<span style="color: #008000;">/ Action principale: on totalise chaque fin d'écriture async</span>
                     llTotalBytesWritten+=dwBytesWritten[dwEventSignPos];
                }
            }
       }
    }

    <span style="color: #008000;">// A la fin, affichage du nombre d'octets écrits</span>
    printf("<span style="color: #ff0000;">%d octets écrits\n</span>", llTotalBytesWritten);        

   <span style="color: #008000;"> //Cleanup -------------------------------------------------------------------------------------------------------</span>
    for (int i = 0; i &lt; MAXOUTSTANDINGIO; i++) {
         CloseHandle (hEvents [i]);
    }
    CloseHandle (hOutput);

    return 0;
}
</span></pre>
<ol>
<li>Comme on veut envoyer 4 IO en même temps, on est obligés d&#8217;avoir 4 structures Overlapped et 4 events. Donc on utilise un tableau de dimension [MAXOUTSTANDINGIO] de chaque pour stocker nos objets. Les autres variables locales sont plutôt évidentes: un HANDLE pour le fichier, un LARGE_INTEGER pour gérer nos offsets, deux variables pour comptabiliser les octets écrits, etc&#8230;</li>
<li>Comme dans l&#8217;article précédent, l&#8217;utilisation conjointe de FILE_FLAG_WRITE_THROUGH et FILE_FLAG_NO_BUFFERING impose de faire des écritures alignées sur la taille du secteur disque, donc on va le récupérer avec un IOCTL. Sur mon laptop, il est de 512 octets.</li>
<li>On ouvre le fichier en lui passant les deux flags pré-cités plus FILE_FLAG_OVERLAPPED qui indique que l&#8217;on souhaite effectuer des IO asynchrones.</li>
<li>On initialise le buffer à écrire avec swprintf_s()</li>
<li>On initialise les 4 Events avec CreateEvent() en passant bien bManualReset à TRUE (on resette manuellement l&#8217;event, prerequis pour se synchroniser dessus) et bInitialState à FALSE (non signalé, il sera signalé lors de la fin de l&#8217;IO correspondant à la structure overlapped).</li>
<li>On initialise à 0 les Offsets en utilisant le QuadPart, on commence à écrire en tout début de fichier.</li>
<li>On boucle tant qu&#8217;on n&#8217;a pas écrit la quantité de données attendue (10mb):</li>
<li>On met à jour Offset et OffsetHigh puis on envoie les 4 premières IO simultanées: si GetLastError() renvoie 997 (ERROR_IO_PENDING), ASYNCWRITE passe à true et on a bien une IO asynchrone. Sinon si le code retour de WriteFile() est ERROR_IO_SUCCESS, alors on a une IO synchrone, et sinon on a une erreur.</li>
<li>On incrémente les valeurs d&#8217;offsets en ajoutant la quantité d&#8217;octets envoyés avec le QuadPart.</li>
<li>Lorsqu&#8217;on a envoyé notre premier paquet d&#8217;IOs, on bloque avec WaitForMultipleObjects sur le tableau d&#8217;events. Lorsqu&#8217;un premier event est signalé, on exécute un GetOverlappedResult () sur la structure Overlapped correspondante.</li>
<li>On réinitialise l&#8217;event pour cette IO (ResetEvent() repasse bInitialState à FALSE).</li>
<li>Et on incrémente la quantité totale d&#8217;octets écrits jusqu&#8217;à écrire les 10Mb, etc&#8230;</li>
</ol>
<h2>Résumé:</h2>
<ul>
<li>Par défaut, une opération d&#8217;entrée/sortie est synchrone, et le thread qui l&#8217;a initiée doit bloquer et attendre que chaque opération soit terminée avant de poursuivre. Pour ne pas bloquer, on utilise des IO asynchrones.</li>
<li>On indique le flag FILE_FLAG_OVERLAPPED à la primitive d&#8217;ouverture pour indiquer que l&#8217;on souhaite faire des IO asynchrones.</li>
<li>La structure Overlapped contient des informations sur chaque IO asynchrone. Elle doit être initialisée et maintenue dans le code utilisateur.</li>
<li>Il existe plusieurs façons de se synchroniser sur le retour des IOs, la plus simple étant d&#8217;utiliser un event associé à chaque structure overlapped. Cet event doit être à reset manuel et initialisé dans l&#8217;état non-signalé.</li>
<li>Il faut maintenir les pointeurs d&#8217;offsets manuellement dans le cas d&#8217;une IO asynchrone. On utilise les propriétés Offset et OffsetHigh pour cela.</li>
<li>Rien ne garantit que l&#8217;IO sera bien asynchrone, il existe des cas où il ne sera pas possible de le faire, donc il faut pouvoir tester le code retour de ReadFile() ou WriteFile() et agir en conséquence.</li>
<li>On peut récupérer la fin d&#8217;une IO asynchrone en utilisant GetOverlappedResult() ou HasOverlappedIOCompleted().</li>
</ul>
<p>La prochaine fois, on verra comment faire pour utiliser les IO completion ports pour gérer les IO asynchrones.</p>
<p>A+. David B.</p>
<h2>Biblio:</h2>
<p><a href="http://www.amazon.com/Windows-via-Pro-Jeffrey-Richter/dp/0735624240">Windows via C++</a> (Jeffrey Richter / Christope Navarre)<br />
<a href="http://www.flounder.com/">Floundercraft</a>: a Joseph M. Newcomer resource<br />
<a href="http://download.microsoft.com/download/6/E/8/6E882A06-B71B-4642-9EB4-D1EA0D6223C8/SQL%20Server%20IO%20Reliability%20Program%20Requirements%20Document.docx">http://download.microsoft.com/download/6/E/8/6E882A06-B71B-4642-9EB4-D1EA0D6223C8/SQL%20Server%20IO%20Reliability%20Program%20Requirements%20Document.docx</a> : SQL Server I/O Reliability Program Review.</p>
<h2>Notes:</h2>
<p>[1] Dave Cutler, le papa de VMS et du premier kernel windows NT avait été tellement déprimé par l&#8217;implémentation des APC dans VMS qu&#8217;il aurait déclaré plus tard lorsqu&#8217;il travaillait sur le kernel NT : &laquo;&nbsp;<span style="font-family: arial,sans-serif;"><span style="font-family: arial,sans-serif;"><span style="font-family: arial,sans-serif;"><span style="font-family: arial,sans-serif;">Asynchronous callback I/O will go into Windows over my dead body!</span></span></span></span>&nbsp;&raquo;<br />
[2] en dehors de ReadFileEx et WriteFileEx qui sont réservées aux I/Os asynchrones.<strong>Continuez votre lecture sur le blog :</strong>
<ul class="similar-posts">
<li><a href="http://blog.capdata.fr/index.php/consistence-des-ecritures-avec-sata/" rel="bookmark" title="13 mars 2011">Consistence des écritures avec SATA</a> (David BAFFALEUF) [Operating SystemSQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/bench-avec-netapp-datacore-esx/" rel="bookmark" title="26 avril 2011">Bench avec NetApp / Datacore / ESX</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/comment-limiter-les-alertes-liees-a-la-journalisation-checkpoint-not-completed-et-compagnie/" rel="bookmark" title="8 juillet 2011">Checkpoint not complete: Comment limiter les alertes liées à la journalisation</a> (Louis PROU) [Oracle]</li>
<li><a href="http://blog.capdata.fr/index.php/pourquoi-il-faut-sauvegarder-les-bases-systemes/" rel="bookmark" title="3 juillet 2011">Pourquoi il faut sauvegarder les bases systèmes</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/openrowset-episode-1/" rel="bookmark" title="13 juillet 2011">OPENROWSET, épisode 1</a> (David BAFFALEUF) [SQL Server]</li>
</ul>
<p><!-- Similar Posts took 5.808 ms -->
<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fio-asynchrones-episode-1%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fio-asynchrones-episode-1%2F&amp;style=normal&amp;b=2" height="61" width="50" /><br />
			</a>
		</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.capdata.fr/index.php/io-asynchrones-episode-1/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Pourquoi il faut sauvegarder les bases systèmes</title>
		<link>http://blog.capdata.fr/index.php/pourquoi-il-faut-sauvegarder-les-bases-systemes/</link>
		<comments>http://blog.capdata.fr/index.php/pourquoi-il-faut-sauvegarder-les-bases-systemes/#comments</comments>
		<pubDate>Sun, 03 Jul 2011 14:20:26 +0000</pubDate>
		<dc:creator>David BAFFALEUF</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[corruption]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[traceflag]]></category>

		<guid isPermaLink="false">http://blog.capdata.fr/?p=2474</guid>
		<description><![CDATA[On peut presque dire que sur 90% des instances que l&#8217;on audite, les bases systèmes ne sont JAMAIS sauvegardées.
Souvent c&#8217;est un aspect de l&#8217;administration qui est négligé: on effectue des sauvegardes des bases utilisateurs, mais les bases systèmes ne contiennent pas de données métier donc elles ne font pas partie  du plan de maintenance. C&#8217;est [...]]]></description>
			<content:encoded><![CDATA[<p>On peut presque dire que sur 90% des instances que l&#8217;on audite, les bases systèmes ne sont JAMAIS sauvegardées.</p>
<p>Souvent c&#8217;est un aspect de l&#8217;administration qui est négligé: on effectue des sauvegardes des bases utilisateurs, mais les bases systèmes ne contiennent pas de données métier donc elles ne font pas partie  du plan de maintenance. C&#8217;est une erreur qui peut se payer très cher lorsque la base système devient inutilisable.</p>
<p>Dans cet article, nous allons voir comment se sortir de deux cas de corruption de la base master, qui est la clé de voûte de SQL Server:</p>
<ul>
<li>Lors du démarrage, le fichier master.mdf n&#8217;est pas lisible.</li>
<li>Lors du démarrage, le fichier master.mdf est lisible mais le recovery sur la base master échoue.</li>
</ul>
<p>Dans les deux cas, la conséquence est la même: SQL Server ne peut pas démarrer.</p>
<p>Et en prime, une petite vidéo qui illustre le second cas:</p>

<span id="video2" class="HDFLV">
<a href="http://www.macromedia.com/go/getflashplayer">Get the Flash Player</a> to see this player.</span>
<script type="text/javascript">
var s2 = new SWFObject("http://blog.capdata.fr/wp-content/plugins/contus-hd-flv-player/hdflvplayer/hdplayer.swf","n2","800height=600","400","7");
s2.addParam("allowfullscreen","true");
s2.addParam("allowscriptaccess","always");
s2.addParam("wmode","opaque");
s2.addVariable("baserefW","http://blog.capdata.fr");s2.addVariable("pid","1");
s2.addVariable("vid","2");
s2.write("video2");
</script>
<div id="htmlplayer3"><iframe  type="text/html" width="800height=600" height="400"  src="http://www.youtube.com/embed/IlFzMnYj3LQ" frameborder="0">
</iframe> </div><script>var txt =  navigator.platform ;if(txt =="iPod"|| txt =="iPad"|| txt =="iPhone" || txt =="Linux armv7I")
            {   document.getElementById("htmlplayer3").style.display = "block";
                document.getElementById("video3").style.display = "none";
            }else{
 document.getElementById("htmlplayer3").style.display = "none";
            }
			 function failed(e) {
			  if(txt =="iPod"|| txt =="iPad"|| txt =="iPhone" || txt =="Linux armv7I")
            {
   alert("Player doesnot support this video.");
   }
}
        </script>
<h2>Bases système et rôles:</h2>
<p>Les bases systèmes sont au nombre de 4 (on ne parle ni de distribution qui est liée à la réplication ni de resource qui n&#8217;est pas visible par défaut).</p>
<ul>
<li><strong>master:</strong> elle contient tous les éléments de configuration et de fonctionnement de l&#8217;instance: logins, liste des bases, options de configuration, liste des fichiers, des serveurs liés, endpoints, etc&#8230; Sans base master, pas de SQL Server. Donc il faut la sauvegarder.</li>
<li><strong>msdb: </strong>elle contient toutes les informations nécessaires pour que l&#8217;agent puisse fonctionner: jobs, alertes, opérateurs, schedules, avec en plus de nombreuses tables d&#8217;historique (backupset, backupfile, restorefile, suspect_pages, etc&#8230;). Sans base msdb, pas d&#8217;agent SQL, pas d&#8217;informations statistiques. Donc il faut la sauvegarder.</li>
<li><strong>model</strong>: sert de définition pour toute nouvelle création de base. Nécessaire lors de la séquence de boot de l&#8217;instance pour permettre la création de tempdb. Beaucoup diront qu&#8217;il n&#8217;est pas  nécessaire de sauvegarder la base model, et l&#8217;argument est discutable: sans base model, pas de base tempdb au démarrage et donc pas de SQL Server. Sans sauvegarde, il faudrait retrouver une base depuis une instance en même version avec la même collation, alors que SQL Server met moins d&#8217;une seconde à sauvegarder cette base de 2Mb. Est-ce que ça ne vaut vraiment pas le coup de l&#8217;inclure dans le plan de maintenance ?</li>
<li><strong>tempdb</strong>: base servant à stocker temporairement le résultat d&#8217;agrégats (tris, regroupements, etc&#8230;), le version store, les facts de DBCC CHECKDB, parmi d&#8217;autres choses&#8230; Cette base est recréée de zéro à chaque redémarrage. Bien que l&#8217;issue de sa création soit critique pour la suite de la séquence de démarrage, les données qui s&#8217;y trouvent ne le sont pas. De la sorte, il n&#8217;est pas nécessaire de la sauvegarder.</li>
</ul>
<h2>Séquence de démarrage de SQL Server:</h2>
<p>Pour bien comprendre quoi faire lorsque la base master est endommagée, il faut d&#8217;abord détailler la séquence de démarrage de SQL Server. En rouge les phases importantes pour ce qui nous intéresse dans cet article:</p>
<p><strong>Etape 1</strong>:</p>
<pre><span style="color: #0000ff;"><span style="color: #999999;">Server    Microsoft SQL Server 2005 - 9.00.4035.00 (Intel X86)    Nov 24 2008 13:01:59 ...
Server    (c) 2005 Microsoft Corporation.
Server    All rights reserved.
Server    Server process ID is 168048.
Server    Authentication mode is MIXED.
Server    Logging SQL Server messages in file 'C:\UTRECHT\MSSQL.1\MSSQL\LOG\ERRORLOG'.
Server    This instance of SQL Server last reported using a process ID of 3332 at 21/06/2011 ...
Server    Registry startup parameters:</span>
<span style="color: #ff0000;">S</span><span style="color: #ff0000;">erver      -d C:\UTRECHT\MSSQL.1\MSSQL\DATA\master.mdf
Server      -e C:\UTRECHT\MSSQL.1\MSSQL\LOG\ERRORLOG
Server      -l C:\UTRECHT\MSSQL.1\MSSQL\DATA\mastlog.ldf</span>
</span><span style="color: #0000ff;"> </span></pre>
<p>SQL Server va lire ses informations de démarrage dans la base de registre (sous HKLM\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL.1\MSSQLServer\Parameters):<br />
- Le fichier de données de la base master (-d)<br />
- Le journal de transactions de la base master (-l)<br />
- Le fichier ERRORLOG (-e)<br />
- Éventuellement une liste de traceflags (-T, non indiqué dans cet exemple)</p>
<p><strong>Etape 2:</strong></p>
<pre><span style="color: #0000ff;"><span style="color: #999999;">Serveur    SQL Server is starting at normal priority base (=7)...
Serveur    Detected 2 CPUs. This is an informational message; no user action is required.
Serveur    Using dynamic lock allocation.  Initial allocation of 2500 Lock blocks and 5000 ...
Serveur    Attempting to initialize Microsoft Distributed Transaction Coordinator (MS DTC) ...
Serveur    The Microsoft Distributed Transaction Coordinator (MS DTC) service could not be ...
Serveur    Database mirroring has been enabled on this instance of SQL Server.</span>
<span style="color: #ff0000;">spid4s    Starting up database 'master'.
spid4s    6 transactions rolled forward in database 'master' (1) ...
spid4s    0 transactions rolled back in database 'master' (1)...
spid4s    Recovery is writing a checkpoint in database 'master' (1) ...</span>
</span></pre>
<p><strong> en 3 sous étapes</strong>:<br />
- 2.1 SQL Server ouvre le fichier master.mdf et le journal de transactions.<br />
- 2.2: il exécute un recovery sur la base master.<br />
- 2.3: master est mise en ligne à la fin du recovery.</p>
<p>On note qu&#8217;à partir de ce point le thread principal forke un premier fils (spid4s). Le suffixe &#8216;<em>s</em>&#8216; apposé au SPID indique qu&#8217;il s&#8217;agit d&#8217;une session système. Une fois la base master en ligne, cette session va demeurer pendant toute la durée d&#8217;exécution de SQL Server sous la dénomination &#8216;SIGNAL HANDLER&#8217;. C&#8217;est ce thread qui sera chargé d&#8217;intercepter ensuite une demande STOP envoyée par le Gestionnaire de Services.</p>
<p><strong>Etape 3:</strong></p>
<pre><span style="color: #0000ff;"><span style="color: #999999;">spid4s    SQL Trace ID 1 was started by login "sa".</span>
<span style="color: #888888;">spid4s    Starting up database 'mssqlsystemresource'.
spid4s    The resource database build version is 9.00.4035 ...</span>
<span style="color: #999999;">spid4s    Server name is 'UTRECHT'...</span>
<span style="color: #ff0000;">spid9s    Starting up database 'model'.
spid9s    Clearing tempdb database.
spid9s    Starting up database 'tempdb'.
</span></span></pre>
<p><strong> </strong>SQL Server ouvre la base model, effectue un recovery et créé la base tempdb. Ces actions sont effectuées par une session système différente (spid9s).</p>
<p><strong>Etape 4:</strong></p>
<pre><span style="color: #0000ff;"><span style="color: #999999;">Serveur    A self-generated certificate was successfully loaded for encryption.
Serveur    Server is listening on [ 'any' &lt;ipv6&gt; 1433].
Serveur    Server is listening on [ 'any' &lt;ipv4&gt; 1433].
Serveur    Server local connection provider is ready to accept connection on ...
Serveur    Server named pipe provider is ready to accept connection on ...
Serveur    Server is listening on [ ::1 &lt;ipv6&gt; 1434].
Serveur    Server is listening on [ 127.0.0.1 &lt;ipv4&gt; 1434].
Serveur    Dedicated admin connection support was established for listening locally on port ...
Serveur    SQL Server is now ready for client connections...
spid12s    The Service Broker protocol transport is disabled or not configured.
spid12s    The Database Mirroring protocol transport is disabled or not configured.
spid12s    Service Broker manager has started.</span>
<span style="color: #ff0000;">spid16s    Starting up database 'msdb'.
spid17s    Starting up database 'CAPDATA'.
spid18s    Starting up database 'FoodMart2005'.
spid19s    Starting up database 'demorecovery'.
spid20s    Starting up database 'distribution'.
</span></span></pre>
<p>Enfin dernière étape, les bases utilisateurs sont ouvertes en parallèle et un recovery est exécuté sur chacune d&#8217;entre elles. Noter les différents threads système utilisés pour accélérer la mise en ligne.</p>
<p>Il faut savoir que ces étapes de démarrage sont débrayables. On peut indiquer à SQL Server de s&#8217;arrêter :<br />
- Soit juste après l&#8217;ouverture du fichier master.mdf mais AVANT le début du recovery de master entre les étapes 2.1 et 2.2 (trace flag -T3607)<br />
- Soit juste après le recovery de master, entre les étape 2.3 et 3  (trace flag -T 3608).</p>
<p><strong> </strong></p>
<h2>Corruption de la base master, quoi faire:</h2>
<h3>Cas n°1: le fichier master.mdf est illisible:</h3>
<p>Lorsqu&#8217;un problème survient entre les étapes 2.1 et 2.2, le fichier master.mdf est illisible et ne peut pas être ouvert. Le recovery ne peut pas débuter et l&#8217;instance s&#8217;arrête:</p>
<pre><span style="color: #0000ff;">2011-07-03 15:25:50.27 spid7s      Starting up database 'master'.
<span style="color: #ff0000;">2011-07-03 15:25:50.30 spid7s      Error: 824, Severity: 24, State: 6.
2011-07-03 15:25:50.30 spid7s      SQL Server detected a logical consistency-based I/O error: restore pending. It occurred during a read of page (1:0) in database ID 1 at
                                    offset 0000000000000000 in file 'E:\MSSQL10.SQL1\MSSQL\DATA\master_corrupted.mdf'.</span>  Additional messages in the SQL Server error log or
                                    system event log may provide more detail. This is a severe error condition that threatens database integrity and must be corrected immediately.
                                    Complete a full database consistency check (DBCC CHECKDB). This error can be caused by many factors; for more information, see SQL Server Books Online.
2011-07-03 15:25:50.33 spid7s      Error: 5173, Severity: 16, State: 1.
2011-07-03 15:25:50.33 spid7s      One or more files do not match the primary file of the database. If you are attempting to attach a database, retry the operation with the correct
                                   files. <span style="color: #ff0000;"> If this is an existing database, the file may be corrupted and should be restored from a backup.</span></span></pre>
<p>Dans ce cas, c&#8217;est l&#8217;enveloppe du fichier master.mdf qui est en cause, et on ne peut pas mettre de point d&#8217;arrêt avant l&#8217;étape 2.1, donc on est obligé de recréer une enveloppe saine pour la base master, que l&#8217;on ait un backup ou non.</p>
<p>En version 2005, on était obligé de repartir du DVD d&#8217;installation et relancer un <strong><span style="color: #0000ff;">setup /wait REINSTALL=SQL_Engine REBUILDDATABASE<span style="color: #0000ff;">=</span></span><span style="color: #0000ff;">1 </span></strong>pour reconstruire toutes les bases système. Donc au passage, si on n&#8217;avait pas non plus de sauvegarde de msdb, adieu jobs, opérateurs, alertes&#8230;</p>
<p>Depuis la version 2008, fort heureusement, un mini-setup et des templates des bases systèmes se trouvent dans le répertoire de base de SQL Server, et on n&#8217;a plus besoin du DVD d&#8217;installation:</p>
<ul>
<li>Le setup embarqué: c:\Program Files\Microsoft SQL Server\100\Setup Bootstrap\Release\Setup.exe</li>
<li>Les templates des bases systèmes: sous le répertoire des binaires de l&#8217;instance, sous Binn\Templates.</li>
</ul>
<p>On relance donc une réinstallation des bases systèmes à partir de là:</p>
<pre><strong><span style="color: #0000ff;">DOS&gt;setup.exe /QUIET /ACTION=REBUILDDATABASE /INSTANCENAME=SQL1 /SQLSYSADMINACCOUNTS=user1 </span><span style="color: #0000ff;">/SAPWD=m0np@$$w0rdS@ /SQLCOLLATION=French_CI_AS</span></strong><span style="color: #0000ff;">
Microsoft (R) SQL Server 2008 Setup 10.00.2735.00
Copyright (c) Microsoft Corporation.  All rights reserved.
DOS&gt;</span><span style="color: #0000ff;"><em>
</em></span><span style="color: #0000ff;"> 
</span></pre>
<ul>
<li>/ACTION=REBUILDDATABASE : indique que l&#8217;on souhaite reconstruire les bases systèmes.</li>
<li>/INSTANCE=: est le nom de l&#8217;instance à reconstruire, on aurait utilisé MSSQLSERVER s&#8217;il avait s&#8217;agit d&#8217;une instance par défaut.</li>
<li>/SQLSYSADMINACCOUNTS= : donne le ou les comptes à ajouter au rôle sysadmin lors de la réinstallation, c&#8217;est un minimum pour pouvoir ensuite se connecter.</li>
<li>/SAPWD= : le mot de passe pour le compte SA lorsque l&#8217;authentification est MIXED.</li>
<li>/SQLCOLLATION= : la collation de l&#8217;instance.</li>
</ul>
<p>On peut vérifier la bonne exécution du setup dans le fichier Summary.txt sous ~SetupBootstrap\Log:</p>
<pre><span style="color: #0000ff;">Overall summary:
<span style="color: #008000;"> Final result:                  Passed</span>
<span style="color: #008000;"> Exit code (Decimal):           0</span>
<span style="color: #008000;"> Exit message:                  Passed</span>
 Start time:                    2011-07-03 15:50:37
 End time:                      2011-07-03 15:52:36
 Requested action:              RebuildDatabase</span></pre>
<p>Puis il faut redémarrer l&#8217;instance en mode mono-utilisateur dans une fenêtre DOS:</p>
<pre><span style="color: #0000ff;"><strong>E:\MSSQL10.SQL1\MSSQL\Binn&gt;sqlservr -m -s SQL1</strong>
2011-07-03 15:53:25.63 Server      Microsoft SQL Server 2008 (SP1) - 10.0.2757.0 (Intel X86)
 Jan  8 2010 20:19:57
 Copyright (c) 1988-2008 Microsoft Corporation
 Enterprise Edition on Windows NT 6.0 &lt;X86&gt; (Build 6001: Service Pack 1) (VM)

2011-07-03 15:53:25.63 Server      (c) 2005 Microsoft Corporation.
2011-07-03 15:53:25.63 Server      All rights reserved.
2011-07-03 15:53:25.64 Server      Server process ID is 952.
2011-07-03 15:53:25.64 Server      System Manufacturer: 'VMware, Inc.', System Model: 'VMware Virtual Platform'.
2011-07-03 15:53:25.64 Server      Authentication mode is MIXED.
2011-07-03 15:53:25.64 Server      Logging SQL Server messages in file 'E:\MSSQL10.SQL1\MSSQL\Log\ERRORLOG'.
2011-07-03 15:53:25.64 Server      This instance of SQL Server last reported using a process ID of 816 at 7/3/2011 3:52:34 PM (local) 7/3/2011 1:52:34 PM (UTC). ...
2011-07-03 15:53:25.64 Server      Registry startup parameters:
 -d E:\MSSQL10.SQL1\MSSQL\DATA\master.mdf
 -e E:\MSSQL10.SQL1\MSSQL\Log\ERRORLOG
 -l E:\MSSQL10.SQL1\MSSQL\DATA\mastlog.ldf
2011-07-03 15:53:25.64 Server      Command Line Startup Parameters:
<span style="color: #ff0000;"> -m</span>
 -s SQL1
 SQL1
2011-07-03 15:53:25.67 Server      SQL Server is starting at normal priority base (=7). This is an informational message only...
2011-07-03 15:53:25.67 Server      Detected 1 CPUs. This is an informational message; no user action is required.
2011-07-03 15:53:25.70 Server      Perfmon counters for resource governor pools and groups failed to initialize and are disabled.
2011-07-03 15:53:25.70 Server      Using dynamic lock allocation.  Initial allocation of 2500 Lock blocks and 5000 Lock Owner blocks per node...
2011-07-03 15:53:25.82 Server      Node configuration: node 0: CPU mask: 0x00000001 Active CPU mask: 0x00000001. This message provides a description of the NUMA configuration for this computer...
2011-07-03 15:53:25.85 Server      Database Mirroring Transport is disabled in the endpoint configuration.
2011-07-03 15:53:25.86 spid7s      Warning ******************
2011-07-03 15:53:25.87 spid7s      <span style="color: #ff0000;">SQL Server started in single-user mode.</span> This an informational message only. No user action is required.
2011-07-03 15:53:25.87 spid7s      Starting up database 'master'.
2011-07-03 15:53:25.98 spid7s      Recovery is writing a checkpoint in database 'master' (1)...
2011-07-03 15:53:26.05 spid7s      SQL Server Audit is starting the audits. This is an informational message. No user action is required.
2011-07-03 15:53:26.05 spid7s      SQL Server Audit has started the audits. This is an informational message. No user action is required.
2011-07-03 15:53:26.07 spid7s      FILESTREAM: effective level = 0, configured level = 0, file system access share name = 'SQL1'.
2011-07-03 15:53:26.17 spid7s      SQL Trace ID 1 was started by login "sa".
2011-07-03 15:53:26.18 spid7s      Starting up database 'mssqlsystemresource'.
2011-07-03 15:53:26.22 spid7s      The resource database build version is 10.00.2757...
2011-07-03 15:53:26.63 spid9s      Starting up database 'model'.
2011-07-03 15:53:26.98 Server      A self-generated certificate was successfully loaded for encryption.
2011-07-03 15:53:26.99 spid7s      Server name is 'VMSUSER1\SQL1'. This is an informational message only. No user action is required.
2011-07-03 15:53:27.17 Server      Server is listening on [ 'any' &lt;ipv6&gt; 49161].
2011-07-03 15:53:27.21 Server      Server is listening on [ 'any' &lt;ipv4&gt; 49161].
2011-07-03 15:53:27.24 Server      Server local connection provider is ready to accept connection on [ \\.\pipe\SQLLocal\SQL1 ].
2011-07-03 15:53:27.24 Server      Server local connection provider is ready to accept connection on [ \\.\pipe\MSSQL$SQL1\sql\query ].
2011-07-03 15:53:27.28 Server      Server is listening on [ ::1 &lt;ipv6&gt; 49162].
2011-07-03 15:53:27.28 Server      Server is listening on [ 127.0.0.1 &lt;ipv4&gt; 49162].
2011-07-03 15:53:27.28 Server      Dedicated admin connection support was established for listening locally on port 49162.
2011-07-03 15:53:27.29 Server      The SQL Server Network Interface library could not register the Service Principal Name (SPN) for the SQLServer service...
2011-07-03 15:53:27.30 Server      SQL Server is now ready for client connections. This is an informational message; no user action is required.
2011-07-03 15:53:27.36 spid9s      Clearing tempdb database.
2011-07-03 15:53:27.38 spid11s     A new instance of the full-text filter daemon host process has been successfully started.
2011-07-03 15:53:27.44 spid7s      Starting up database 'msdb'.
2011-07-03 15:53:28.04 spid9s      Starting up database 'tempdb'.
<span style="color: #0000ff;">2011-07-03 15:53:28.23 spid7s      Recovery is complete. This is an informational message only. No user action is required.</span></span></pre>
<p>Dans une seconde fenêtre de commande, se connecter et restaurer le backup :</p>
<pre><span style="color: #0000ff;"><strong>C:\Users\user1&gt;sqlcmd -E -S .\SQL1
</strong><strong>1&gt; restore database master from disk='E:\MSSQL10.SQL1\MSSQL\Backup\master.bak'
2&gt; go</strong>
Processed 376 pages for database 'master', file 'master' on file 1.
Processed 4 pages for database 'master', file 'mastlog' on file 1.
The master database has been successfully restored. Shutting down SQL Server.
SQL Server is terminating this process.</span></pre>
<p>Quand on est bien rôdé, la restauration ne prend pas plus de cinq minutes. Une fois la base restaurée, SQL Server s&#8217;arrête, et il ne reste plus qu&#8217;à le redémarrer via net start ou la console Configuration Manager. Et le service est de nouveau en ligne.</p>
<h3>Cas n°2: le fichier master.mdf est lisible mais le recovery échoue:</h3>
<p>C&#8217;est le cas utilisé dans la démo. Lorsqu&#8217;un problème survient entre les étapes 2.2 et 2.3, le fichier master.mdf est ouvert et le recovery commence, mais échoue avant d&#8217;avoir pu aller au bout. Même conséquence, la base master ne peut être ouverte et l&#8217;instance doit s&#8217;arrêter:</p>
<pre><span style="color: #0000ff;">2011-07-03 16:11:32.98 spid7s      Starting up database 'master'.
<span style="color: #ff0000;">2011-07-03 16:11:34.89 spid7s      Error: 824, Severity: 24, State: 2.</span>
<span style="color: #ff0000;">2</span><span style="color: #ff0000;"><span style="color: #ff0000;">01</span>1-07-03 16:11:34.89 spid7s      SQL Server detected a logical consistency-based I/O error: incorrect pageid (expected 1:84; actual 12336:808464432).
                                   It occurred during a read of page (1:84) in database ID 1 at offset 0x000000000a8000 in file 'E:\MSSQL10.SQL1\MSSQL\DATA\master_corrupted.mdf'.  </span>
                                   Additional messages in the SQL Server error log or system event log may provide more detail. This is a severe error condition that threatens
                                   database integrity and must be corrected immediately. Complete a full database consistency check (DBCC CHECKDB). This error can be caused by many
                                   factors; for more information, see SQL Server Books Online.
<span style="color: #ff0000;">2011-07-03 16:11:34.94 spid7s      Error: 3314, Severity: 21, State: 1.
2011-07-03 16:11:34.94 spid7s      During undoing of a logged operation in database 'master', an error occurred at log record ID (196:64:2).</span> Typically, the specific failure is logged
                                   previously as an error in the Windows Event Log service. Restore the database or file from a backup, or repair the database.
<span style="color: #ff0000;">2</span><span style="color: #ff0000;"><span style="color: #ff0000;">0</span>11-07-03 16:11:34.94 spid7s      Cannot recover the master database. SQL Server is unable to run. Restore master from a full backup, repair it, or rebuild it.</span> For more information
                                   about how to rebuild the master database, see SQL Server Books Online.</span></pre>
<p>Contrairement au cas précédent, l&#8217;étape d&#8217;ouverture de master.mdf est validée donc si on a un backup on peut intercaler un point d&#8217;arrêt et dire à SQL Server de stopper la séquence de démarrage juste avant le recovery, en utilisant le traceflag -T3607:</p>
<pre><span style="color: #0000ff;"><strong>E:\MSSQL10.SQL1\MSSQL\Binn&gt;sqlservr -m -T 3607 -s SQL1</strong>
2011-07-03 15:19:21.42 Server      Microsoft SQL Server 2008 (SP1) - 10.0.2757.0 (Intel X86)
 Jan  8 2010 20:19:57
 Copyright (c) 1988-2008 Microsoft Corporation
 Enterprise Edition on Windows NT 6.0 &lt;X86&gt; (Build 6001: Service Pack 1) (VM)

2011-07-03 15:19:21.47 Server      (c) 2005 Microsoft Corporation.
2011-07-03 15:19:21.47 Server      All rights reserved.
2011-07-03 15:19:21.48 Server      Server process ID is 560.
2011-07-03 15:19:21.48 Server      System Manufacturer: 'VMware, Inc.', System Model: 'VMware Virtual Platform'.
2011-07-03 15:19:21.48 Server      Authentication mode is MIXED.
2011-07-03 15:19:21.48 Server      Logging SQL Server messages in file 'E:\MSSQL10.SQL1\MSSQL\Log\ERRORLOG'.
2011-07-03 15:19:21.53 Server      This instance of SQL Server last reported using a process ID of 1996 at 7/2/2011 11:52:16 PM (local) 7/2/
2011 9:52:16 PM (UTC). This is an informational message only; no user action is required.
2011-07-03 15:19:21.53 Server      Registry startup parameters:
 -d E:\MSSQL10.SQL1\MSSQL\DATA\master_corrupted.mdf
 -e E:\MSSQL10.SQL1\MSSQL\Log\ERRORLOG
 -l E:\MSSQL10.SQL1\MSSQL\DATA\mastlog_corrupted.ldf
2011-07-03 15:19:21.54 Server      Command Line Startup Parameters:
<span style="color: #ff0000;"> -m
 -T 3607</span>
 3607
 -s SQL1
 SQL1
2011-07-03 15:19:21.62 Server      SQL Server is starting at normal priority base (=7). This is an informational message only. No user action is required.
2011-07-03 15:19:21.62 Server      Detected 1 CPUs. This is an informational message; no user action is required.
2011-07-03 15:19:22.18 Server      Perfmon counters for resource governor pools and groups failed to initialize and are disabled.
2011-07-03 15:19:22.22 Server      Using dynamic lock allocation.  Initial allocation of 2500 Lock blocks and 5000 Lock Owner blocks per node.  ...
2011-07-03 15:19:23.13 Server      Node configuration: node 0: CPU mask: 0x00000001 Active CPU mask: 0x00000001. This message provides a description of the NUMA configuration for this computer. ...
2011-07-03 15:19:23.34 Server      Database Mirroring Transport is disabled in the endpoint configuration.
2011-07-03 15:19:23.40 spid7s      Warning ******************
<span style="color: #ff0000;">2</span><span style="color: #ff0000;"><span style="color: #ff0000;">01</span>1-07-03 15:19:23.41 spid7s      SQL Server started in single-user mode. This an informational message only. No user action is required.
2011-07-03 15:19:23.41 spid7s      Starting without recovery. This is an informational message only. No user action is required.</span>
2011-07-03 15:19:23.56 spid7s      Starting up database 'master'.
2011-07-03 15:19:24.99 Server      A self-generated certificate was successfully loaded for encryption.
2011-07-03 15:19:25.02 Server      Server is listening on [ 'any' &lt;ipv6&gt; 49161].
2011-07-03 15:19:25.03 Server      Server is listening on [ 'any' &lt;ipv4&gt; 49161].
2011-07-03 15:19:25.05 Server      Server local connection provider is ready to accept connection on [ \\.\pipe\SQLLocal\SQL1 ].
2011-07-03 15:19:25.05 Server      Server local connection provider is ready to accept connection on [ \\.\pipe\MSSQL$SQL1\sql\query ].
2011-07-03 15:19:25.07 Server      Server is listening on [ ::1 &lt;ipv6&gt; 49162].
2011-07-03 15:19:25.08 Server      Server is listening on [ 127.0.0.1 &lt;ipv4&gt; 49162].
2011-07-03 15:19:25.08 Server      Dedicated admin connection support was established for listening locally on port 49162.
2011-07-03 15:19:25.12 Server      The SQL Server Network Interface library could not register the Service Principal Name (SPN) for the SQL
Server service. Error: 0x54b, state: 3. Failure to register an SPN may cause integrated authentication to fall back to NTLM instead of Kerberos...
2011-07-03 15:19:25.13 Server      SQL Server is now ready for client connections. This is an informational message; no user action is required.</span></pre>
<p>Ensuite on ouvre un second prompt DOS et on restaure la sauvegarde de master :</p>
<pre><strong><span style="color: #0000ff;">C:\Users\user1&gt;sqlcmd -E -S .\SQL1</span>
</strong><span style="color: #0000ff;"><strong>1&gt; restore database master from disk='E:\MSSQL10.SQL1\MSSQL\Backup\master.bak'
2&gt; go</strong>
Processed 376 pages for database 'master', file 'master' on file 1.
Processed 4 pages for database 'master', file 'mastlog' on file 1.
The master database has been successfully restored. Shutting down SQL Server.
SQL Server is terminating this process.</span></pre>
<p>Là encore, SQL Server détecte qu&#8217;une restauration de la base master vient d&#8217;avoir lieu, et stoppe l&#8217;instance immédiatement ensuite. Il suffit de redémarrer le service via SQL Configuration Manager et notre application est de nouveau disponible, tout ça en moins de 2 minutes.</p>
<p>Sans backup dans ce cas, il aurait fallu reconstruire une enveloppe de la base master comme dans le cas précédent, puis remettre à jour tous les logins, serveurs liés, options de configuration, patches, endpoints, etc&#8230; et enfin rattacher les bases utilisateur.</p>
<h2>Conclusion:</h2>
<p>La conclusion est simple: <span style="text-decoration: underline;"><strong>sauvegardez vos bases système</strong></span>. Si master se retrouve endommagée, sans backup le temps passé à remonter toutes les informations sera beaucoup plus important, et parfois il ne sera pas possible de tout remonter (les mots de passe des logins par exemple, certaines options de configuration modifiées). Le préjudice peut être très élevé: une interruption intempestive coûte cher à l&#8217;entreprise: pénalités financières par rapport aux clients, crise d&#8217;image, etc&#8230; alors qu&#8217;ajouter trois petites bases système dans un plan de maintenance ne vous coûte quasiment rien.</p>
<p>A+. David B.<strong>Continuez votre lecture sur le blog :</strong>
<ul class="similar-posts">
<li><a href="http://blog.capdata.fr/index.php/how-to-reduire-lenveloppe-de-tempdb/" rel="bookmark" title="7 juillet 2011">How-To : réduire l&#8217;enveloppe de tempdb</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/openrowset-episode-1/" rel="bookmark" title="13 juillet 2011">OPENROWSET, épisode 1</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/point-in-time-recovery-et-fn_dump_dblog/" rel="bookmark" title="13 juillet 2011">Point-in-time recovery et fn_dump_dblog()</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/sql-server-principes-dune-sauvegarde-a-chaud/" rel="bookmark" title="12 décembre 2008">Principes d&#8217;une sauvegarde à chaud</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/how-to-reduire-la-taille-du-journal-de-transactions-sur-disque/" rel="bookmark" title="11 juillet 2011">How-To: réduire la taille du journal de transactions sur disque</a> (David BAFFALEUF) [SQL Server]</li>
</ul>
<p><!-- Similar Posts took 5.352 ms -->
<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fpourquoi-il-faut-sauvegarder-les-bases-systemes%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fpourquoi-il-faut-sauvegarder-les-bases-systemes%2F&amp;style=normal&amp;b=2" height="61" width="50" /><br />
			</a>
		</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.capdata.fr/index.php/pourquoi-il-faut-sauvegarder-les-bases-systemes/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Repérer un package SSIS lors de son exécution</title>
		<link>http://blog.capdata.fr/index.php/reperer-un-package-ssis-lors-de-son-execution/</link>
		<comments>http://blog.capdata.fr/index.php/reperer-un-package-ssis-lors-de-son-execution/#comments</comments>
		<pubDate>Thu, 30 Jun 2011 15:07:39 +0000</pubDate>
		<dc:creator>Louis HOCHBERG</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[application name;]]></category>
		<category><![CDATA[package]]></category>
		<category><![CDATA[ssis]]></category>

		<guid isPermaLink="false">http://blog.capdata.fr/?p=2504</guid>
		<description><![CDATA[Voici quelques infos pour identifier et tracer l&#8217;exécution d&#8217;un package SSIS avec SQL Server Profiler .
Il n&#8217;est pas forcément évident de repérer une session d&#8217;exécution d&#8217;un package SSIS parmi toutes les sessions connectées au même moment sur un server SQL.
Pour repérer le package SSIS , il est possible de donner un nom parlant et repérable à [...]]]></description>
			<content:encoded><![CDATA[<p>Voici quelques infos pour identifier et tracer l&#8217;exécution d&#8217;un package SSIS avec SQL Server Profiler .</p>
<p>Il n&#8217;est pas forcément évident de repérer une session d&#8217;exécution d&#8217;un package SSIS parmi toutes les sessions connectées au même moment sur un server SQL.</p>
<p>Pour repérer le package SSIS , il est possible de donner un nom parlant et repérable à votre package, en ajoutant la propriété &laquo;&nbsp;Application Name&nbsp;&raquo;  dans la chaîne de connexion<br />
de la source de données SSIS comme dans l&#8217;exemple suivant:</p>
<pre><span style="font-weight: normal">Data Source=<em>MonServer</em>;User ID=<em>Monuser</em>;Initial Catalog=<em>Mabase</em>;Provider=SQLNCLI10;
</span>Persist Security Info=True;Auto Translate=False;<span style="color: #ff0000">Application Name=Monapplication</span>;</pre>
<p><strong> </strong></p>
<p>Une fois recompilé, le package SSIS  sera repérable de la manière suivante dans la liste de vos sessions :</p>
<pre>select program_name, S.* from sys.dm_exec_sessions S where program_name  like '<span style="color: #ff0000">Monapplication%</span>'</pre>
<div>Dans le profiler, vous pourrez filtrer la valeur de la colonne suivante :</div>
<pre>ApplicationName='<span style="color: #ff0000">Monapplicatio</span><span style="color: #ff0000">n</span>'</pre>
<p><span style="font-size: small"><strong><em><br />
</em></strong></span><strong>Continuez votre lecture sur le blog :</strong>
<ul class="similar-posts">
<li><a href="http://blog.capdata.fr/index.php/openrowset-episode-1/" rel="bookmark" title="13 juillet 2011">OPENROWSET, épisode 1</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/io-asynchrones-episode-1/" rel="bookmark" title="5 juillet 2011">I/O asynchrones (épisode 1)</a> (David BAFFALEUF) [Operating SystemSQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/regenerer-le-ddl-des-indexes-full-text/" rel="bookmark" title="12 octobre 2011">Regénérer le DDL des indexes FULL TEXT</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/journees-sql-server-1213-decembre-suite/" rel="bookmark" title="27 décembre 2011">Journées SQL Server 12/13 décembre (suite)</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/error-8976-8978-problemes-de-chainage-comment-recuperer-les-donnees/" rel="bookmark" title="30 mai 2011">Error 8976 / 8978, problèmes de chaînage, comment récupérer les données</a> (David BAFFALEUF) [SQL Server]</li>
</ul>
<p><!-- Similar Posts took 4.054 ms -->
<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Freperer-un-package-ssis-lors-de-son-execution%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Freperer-un-package-ssis-lors-de-son-execution%2F&amp;style=normal&amp;b=2" height="61" width="50" /><br />
			</a>
		</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.capdata.fr/index.php/reperer-un-package-ssis-lors-de-son-execution/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Error 8976 / 8978, problèmes de chaînage, comment récupérer les données</title>
		<link>http://blog.capdata.fr/index.php/error-8976-8978-problemes-de-chainage-comment-recuperer-les-donnees/</link>
		<comments>http://blog.capdata.fr/index.php/error-8976-8978-problemes-de-chainage-comment-recuperer-les-donnees/#comments</comments>
		<pubDate>Mon, 30 May 2011 22:17:34 +0000</pubDate>
		<dc:creator>David BAFFALEUF</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[corruption]]></category>
		<category><![CDATA[dbcc ind]]></category>
		<category><![CDATA[internals]]></category>

		<guid isPermaLink="false">http://blog.capdata.fr/?p=2400</guid>
		<description><![CDATA[Cet article fait suite à une question postée sur developpez.net la semaine dernière. La personne indique qu&#8217;une erreur 823 est remontée sur une base en version SQL Server 2000. On lui demande d&#8217;inspecter les logs systèmes à la recherche d&#8217;une panne matérielle qui aurait pu être à l&#8217;origine du problème, puis de retourner le résultat [...]]]></description>
			<content:encoded><![CDATA[<p>Cet article fait suite à une <a href="http://www.developpez.net/forums/d1087285/bases-donnees/ms-sql-server/administration/error-i-o-msg-823-a/">question </a>postée sur <em>developpez.net</em> la semaine dernière. La personne indique qu&#8217;une erreur 823 est remontée sur une base en version SQL Server 2000. On lui demande d&#8217;inspecter les logs systèmes à la recherche d&#8217;une panne matérielle qui aurait pu être à l&#8217;origine du problème, puis de retourner le résultat d&#8217;un DBCC CHECKDB sur la base:</p>
<pre><span style="color: #008000;">Msg*8909, Niveau*16, État*1, Ligne*1
Erreur de TABLE : Objet ID = 0, INDEX ID*=*0, page ID = (1:22988). ID de page de l'en-tête
de page = (0:0).
CHECKDB a trouvé 0 erreurs d'allocation et 1 erreurs de cohérence non associées à un
quelconque objet UNIQUE.
Msg*8928, Niveau*16, État*1, Ligne*1
Objet ID = 574885365, INDEX ID = 0 : <span style="color: #ff0000;">La page (1:22988) ne peut pas être traitée</span>. Pour
plus d'informations, consultez les autres erreurs.
<span style="color: #ff0000;">Msg*8976, Niveau*16, État*1, Ligne*1</span>
Erreur de table : Objet ID = 574885365, index ID = 1. <span style="color: #ff0000;">Page (1:22988) n'a pas été trouvé
dans l'analyse bien que ses parents (1:22930) et (1:22987) précédents y font référence</span>.
Contrôlez toutes les erreurs précédentes.
<span style="color: #ff0000;">Msg*8978</span>, Niveau*16, État*1, Ligne*1
Erreur de table : Objet ID = 574885365, index ID = 1. <span style="color: #ff0000;">La page (1:22989) n'a pas de référence
dans la page précédente (1:22988). Possibilité d'un problème de liaison de chaîne.</span>
CHECKDB a trouvé 0 erreurs d'allocation et 3 erreurs de cohérence dans la TABLE
'XXXXXXXX' (objet ID = 574885365).
CHECKDB a trouvé 0 erreurs d'allocation et 4 erreurs de cohérence dans la base de
données 'xxxxxxx'.
<span style="color: #ff0000;">repair_allow_data_loss </span>est le minimum de niveau de réparation pour les erreurs trouvés
par DBCC CHECKDB (chantier ).</span></pre>
<p>Outre le fait qu&#8217;un problème matériel empêche tout bonnement la lecture d&#8217;une page depuis le disque, le CHECKDB indique deux autres erreurs 8978 et 8976, parlant d&#8217;un problème de liaison de page. On rappelle que chaque page de données ou d&#8217;index hors pages spéciales (IAM, DBINFO, SGAM, GAM, PFS, ML, DM,&#8230;) est liée à la page qui la précède et à la page qui la suit dans le plan d&#8217;allocation, mais aussi à une page parente dans un index.</p>
<p>Lorsqu&#8217;une page parente référence une page à un niveau inférieur du B-tree et que cette page n&#8217;est pas trouvée, alors DBCC CHECKDB renvoie une erreur 8976. Lorsqu&#8217;une page référence une page Next et que cette page n&#8217;est pas trouvée, alors DBCC CHECKDB renvoie une erreur 8978. Mais ces messages n&#8217;indiquent pas s&#8217;il ne s&#8217;agit que d&#8217;une corruption de l&#8217;entête de la page, ou aussi des données.</p>
<p>Ce qui veut dire que les données peuvent être bonnes alors que DBCC CHECKDB nous oriente vers un repair_allow_data_loss comme méthode de résolution, avec les conséquences que l&#8217;on sait (désallocation de la page qui pose problème et perte des données).</p>
<p>Dans le cas évoqué sur <em>developpez.net</em>, la personne ne dispose pas de backup, la question est de savoir quelles données il risque de perdre, et si les données ne sont pas endommagées, comment peut-il les récupérer.</p>
<h2>Repro pour ce problème</h2>
<p>Nous allons créer une base linkcorrupt et modifier la valeur du pointeur prevPageID dans une page de niveau feuille d&#8217;un index cluster pour forcer des erreurs 8976 et 8978.</p>
<pre><span style="color: #0000ff;">create database linkcorrupt
GO
use linkcorrupt
GO
</span><span style="color: #0000ff;">create table T1(
     a numeric identity,
     b char(4000) default replicate('b',4000),
     c bigint default round(rand()*100,0))
GO
insert into T1 default values
GO 1000
create unique clustered index IDX_T1C on T1(a)</span><span style="color: #0000ff;">
GO
</span></pre>
<p>On va choisir une page du niveau feuille de l&#8217;index cluster (le dbcc ind a été purgé de certaines colonnes pour une meilleure lisibilité)</p>
<pre><span style="color: #0000ff;">dbcc ind('linkcorrupt','T1',-1)</span></pre>
<pre><span style="color: #0000ff;"> PageFID PagePID     PageType  IndexLevel NextPageFID NextPagePID PrevPageFID PrevPagePID
 ------- ----------- --------  ---------- ----------- ----------- ----------- -----------
<span style="color: #808080;"> 1       90          10        NULL       0           0           0           0</span>
<span style="color: #ff6600;"> 1       110         2         1          1           115         0           0
 1       115         2         1          0           0           1           110
 1       121         2         2          0           0           0           0</span>
 1       672         1         0          1           673         0           0
 1       673         1         0          1           674         1           672
<span style="color: #ff0000;"> 1       674         1         0          1           675         1           673</span>
 1       675         1         0          1           676         1           674
 1       676         1         0          1           677         1           675
 1       677         1         0          1           678         1           676
 1       678         1         0          1           679         1           677
 1       679         1         0          1           680         1           678
 1       680         1         0          1           681         1           679
 ...
</span></pre>
<p>De ce résultat on déduit que:</p>
<ul>
<li>La page 1:90 est la page IAM pour T1.</li>
<li>La page 1:121 est la page racine de l&#8217;index cluster</li>
<li>Les pages 1:110 et 1:115 sont des pages de niveau intermédiaire pour l&#8217;index cluster (on aura volontairement casé deux lignes par page pour augmenter le feuillage de l&#8217;index).</li>
<li>Les autres pages sont les pages du niveau feuille de l&#8217;index cluster. On choisira de corrompre le pointeur prevPagePID dans la page 1:674. Comme l&#8217;index vient juste d&#8217;être construit, il est parfaitement clusterisé et les pages avant et après sont respectivement 1:673 et 1:675. On passe la base hors-ligne et on ouvre le fichier MDF dans <a href="http://www.chmaas.handshake.de/delphi/freeware/xvi32/xvi32.htm">xvi32</a>.</li>
</ul>
<pre><span style="color: #0000ff;">alter database linkcorrupt set offline</span></pre>
<p>La page 1:674 se trouve à l&#8217;offset 0&#215;544000 dans le fichier mdf  (674*8192). Il est alors aisé de repérer les offset où sont stockés les pointeurs prev et next:</p>
<p><a href="http://blog.capdata.fr/wp-content/uploads/2011/05/linkcorrupt.jpg"><img class="alignnone size-large wp-image-2421" title="linkcorrupt" src="http://blog.capdata.fr/wp-content/uploads/2011/05/linkcorrupt-1024x290.jpg" alt="" width="1024" height="290" /></a></p>
<p>Le trait rouge représente l&#8217;offset 0 de la page 1:674. Les 96 premiers octets constituent l&#8217;entête de la page. x86 étant en little-endian, l&#8217;octet de poids faible est en premier donc il faut lire à l&#8217;envers. 02A1 représente notre page prev 1:673, 02A3 notre page next 1:675 et 02A2 notre PageID. On décide de zéro-initialiser l&#8217;octet de poids faible de 673 :</p>
<p style="text-align: center;"><a href="http://blog.capdata.fr/wp-content/uploads/2011/05/linkcorrupt2.jpg"><img class="size-full wp-image-2426 aligncenter" title="linkcorrupt2" src="http://blog.capdata.fr/wp-content/uploads/2011/05/linkcorrupt2.jpg" alt="" width="251" height="78" /></a></p>
<p>On repasse alors la base en ligne et on contrôle notre base pour vérifier qu&#8217;elle se trouve bien dans l&#8217;état attendu:</p>
<pre><span style="color: #0000ff;">alter database linkcorrupt set online
GO
dbcc checkdb ('linkcorrupt') with no_infomsgs, all_errormsgs
GO

<span style="color: #008000;">Msg 8928, Niveau 16, État 1, Ligne 1
ID d'objet 2073058421, ID d'index 1, ID de partition 72057594038386688,  ID d'unité d'allocation 72057594043367424 (type In-row data) :
<span style="color: #ff0000;">impossible de traiter la page (1:674)</span>. Pour plus d'informations,  consultez les autres erreurs.
Msg 8939, Niveau 16, État 98, Ligne 1
Erreur de table : ID d'objet 2073058421, ID d'index 1, ID de partition  72057594038386688, ID d'unité d'allocation 72057594043367424
(type  In-row data), page (1:674). Échec du test (IS_OFF (BUF_IOERR,  pBUF-&gt;bstat)). Valeurs 12716041 et -4.
<span style="color: #ff0000;">Msg 8976</span><span style="color: #ff0000;">, Niveau 16, État 1, Ligne 1</span>
Erreur de table : ID d'objet 2073058421, ID d'index 1, ID de partition  72057594038386688, ID d'unité d'allocation 72057594043367424
(type  In-row data). <span style="color: #ff0000;">La page (1:674) n'a pas été détectée lors de l'analyse  alors que sa page parent (1:110) et les (1:673) pages précédentes
la  référencent</span>. Vérifiez les erreurs précédentes.
<span style="color: #ff0000;">Msg 8978, Niveau 16, État 1, Ligne 1</span>
Erreur de table : ID d'objet 2073058421, ID d'index 1, ID de partition  72057594038386688, ID d'unité d'allocation 72057594043367424
(type  In-row data). <span style="color: #ff0000;">Une référence de la page précédente (1:674) est manquante à  la page (1:675)</span>. Un problème de liaison de chaîne s'est
peut-être  produit.
CHECKDB a trouvé 0 erreurs d'allocation et 4 erreurs de cohérence dans la table 'T1' (ID d'objet 2073058421).
CHECKDB a trouvé 0 erreurs d'allocation et 4 erreurs de cohérence dans la base de données 'linkcorrupt'.
repair_allow_data_loss est le niveau minimum de réparation pour les erreurs trouvées par DBCC CHECKDB (linkcorrupt).</span></span></pre>
<h2>Identifier les plages de valeurs concernées par la corruption:</h2>
<p>Une des questions qu&#8217;on peut se poser, comment savoir ce qu&#8217;il y a à récupérer (si les données ne sont pas touchées) ou ce qu&#8217;on va potentiellement perdre (si les données sont touchées).</p>
<p>Avant cela, il faut déjà récupérer tout le reste de la table en injectant les données jusqu&#8217;à la page 1:673 comprise et à partir de la page 1:675 comprise, puis d&#8217;inspecter la page 1:674 pour voir si les données qui y sont contenues sont lisibles. On sait que les pages sont classées physiquement dans l&#8217;ordre de notre clé clusterisée, donc il suffit pour exclure l&#8217;intervalle compris dans la page 1:674, de connaître la dernière valeur de &#8216;a&#8217; dans la page 1:673 et la première valeur de &#8216;a&#8217; dans la page 1:675.</p>
<p>On créé deux tables temporaires pour stocker le résultat des dbcc page sur les deux pages prev et next:</p>
<pre><span style="color: #0000ff;">CREATE TABLE #recover673 (parentobject varchar(50), Object varchar(100), Field varchar(50), Value varchar(max))
GO
INSERT INTO #recover673  exec ('dbcc page(10,1,673,3) with tableresults')
GO</span></pre>
<pre><span style="color: #0000ff;">CREATE TABLE #recover675 (parentobject varchar(50), Object varchar(100), Field varchar(50), Value varchar(max))
GO
INSERT INTO #recover675  exec ('dbcc page(10,1,675,3) with tableresults')
GO</span></pre>
<p>C&#8217;est la table d&#8217;offset de ligne qui permet de retourner les lignes dans l&#8217;ordre, dans les faits les lignes ne sont pas nécessairement classées physiquement dans la page. On se sert des slots pour connaître la dernière valeur de &#8216;a&#8217; dans la page 1:673 et la première dans la page 1:675 (on rappelle qu&#8217;on n&#8217;a que deux lignes par page).</p>
<pre><span style="color: #0000ff;">select Object, Value FROM #recover673 where Field='a'
Object                                    Value
--------------------------------------  ---------
Slot 0 Column 0 Offset 0x4 Length 9        3
<span style="color: #ff0000;">Slot 1 Column 0 Offset 0x4 Length 9        4</span>
</span></pre>
<pre><span style="color: #0000ff;">select Object, Value FROM #recover675 where Field='a'

Object                                    Value
--------------------------------------  ---------
<span style="color: #ff0000;">Slot 0 Column 0 Offset 0x4 Length 9        7</span>
Slot 1 Column 0 Offset 0x4 Length 9        8</span></pre>
<p>Il s&#8217;agit donc des valeurs de clé 5 et 6 qui sont dans la page qui pose problème. On créé une table à l&#8217;identique et on injecte les deux plages de valeurs [0;4] et [7;1000]:</p>
<pre><span style="color: #0000ff;">select * into T1_backup from T1 where 1=2
GO
insert into T1_backup select a,b from T1 <span style="color: #008000;">with (index = IDX_T1C)</span> where a in (1,2,3,4)
GO
insert into T1_backup select a,b from T1 <span style="color: #008000;">with (index = IDX_T1C)</span> where a &gt;=7
GO</span></pre>
<p>En vert, un hint qui permet de forcer un seek sur l&#8217;index clusterisé. En effet, un Clustered Index Scan aurait tenté de lire les pages 1:673 -&gt; 1:674 et aurait remonté une erreur de chaînage et une erreur 824.</p>
<p>Il ne reste plus qu&#8217;à essayer de lire le contenu de la page endommagée pour voir ce qu&#8217;il peut y avoir de récupérable:</p>
<pre><span style="color: #0000ff;">CREATE TABLE #recover674 (parentobject varchar(50), Object varchar(100), Field varchar(50), Value varchar(max))
GO
insert into #recover674  exec ('dbcc page(17,1,674,3) with tableresults')
GO

</span></pre>
<p>Si la page est lisible alors une requête simple permet de ramener les valeurs au format de T1:</p>
<pre><span style="color: #0000ff;">with TA as (select cast(Object as char(6)) SlotID, Value from #recover674 where Field='a'),
TB as (select cast(Object as char(6)) SlotID, Value from #recover674 where Field='b')
</span><span style="color: #0000ff;">insert into T1_backup</span> <span style="color: #0000ff;">select TA.Value as 'a', TB.value as 'b' from TA inner join TB on TA.SLotID = TB.SlotID
<span style="color: #008000;">
a      b
-------------------------------
5      bbbbbbbbbbbbbbbbbbb(...)
6      bbbbbbbbbbbbbbbbbbb(...)</span></span></pre>
<p>Dans le cas où les données ne sont pas lisibles, on a au moins l&#8217;information de la plage qui est perdue, information que ne donne pas le repair_allow_data_loss.</p>
<p>A+. David B.<strong>Continuez votre lecture sur le blog :</strong>
<ul class="similar-posts">
<li><a href="http://blog.capdata.fr/index.php/msg-2508-level-16-state-1-the-in-row-data-for-object-is-incorrect/" rel="bookmark" title="10 mai 2011">Msg 2508, Level 16, State 1: the In-Row data %% for object %% is incorrect</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/regenerer-le-ddl-des-indexes-full-text/" rel="bookmark" title="12 octobre 2011">Regénérer le DDL des indexes FULL TEXT</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/suppression-accidentelle-de-ligne-comment-retrouver-le-coupable/" rel="bookmark" title="6 octobre 2011">Suppression accidentelle de ligne : comment retrouver le coupable ?</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/alter-table-rebuild/" rel="bookmark" title="2 mars 2011">Alter table rebuild</a> (Louis HOCHBERG) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/point-in-time-recovery-et-fn_dump_dblog/" rel="bookmark" title="13 juillet 2011">Point-in-time recovery et fn_dump_dblog()</a> (David BAFFALEUF) [SQL Server]</li>
</ul>
<p><!-- Similar Posts took 4.468 ms -->
<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Ferror-8976-8978-problemes-de-chainage-comment-recuperer-les-donnees%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Ferror-8976-8978-problemes-de-chainage-comment-recuperer-les-donnees%2F&amp;style=normal&amp;b=2" height="61" width="50" /><br />
			</a>
		</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.capdata.fr/index.php/error-8976-8978-problemes-de-chainage-comment-recuperer-les-donnees/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Msg 2508, Level 16, State 1: the In-Row data %% for object %% is incorrect</title>
		<link>http://blog.capdata.fr/index.php/msg-2508-level-16-state-1-the-in-row-data-for-object-is-incorrect/</link>
		<comments>http://blog.capdata.fr/index.php/msg-2508-level-16-state-1-the-in-row-data-for-object-is-incorrect/#comments</comments>
		<pubDate>Tue, 10 May 2011 10:20:37 +0000</pubDate>
		<dc:creator>David BAFFALEUF</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[corruption]]></category>
		<category><![CDATA[DBCC]]></category>

		<guid isPermaLink="false">http://blog.capdata.fr/?p=2203</guid>
		<description><![CDATA[Un petit article pour parler de cette erreur assez courante qui se rencontre soit avec des versions antérieures à SQL Server 2005, soit à la suite d&#8217;une migration. Elle n&#8217;est pas très grave et plutôt bien décrite puisque la solution est indiquée dans le message (&#171;&#160;Run DBCC UPDATEUSAGE&#160;&#187;).
dbcc checkdb('MvxRef') with no_infomsgs, all_errormsgs

Msg 2508, Niveau 16, État 1, Ligne 1
Le [...]]]></description>
			<content:encoded><![CDATA[<p>Un petit article pour parler de cette erreur assez courante qui se rencontre soit avec des versions antérieures à SQL Server 2005, soit à la suite d&#8217;une migration. Elle n&#8217;est pas très grave et plutôt bien décrite puisque la solution est indiquée dans le message (&laquo;&nbsp;<em>Run DBCC UPDATEUSAGE&nbsp;&raquo;</em>).</p>
<pre><span style="color: #0000ff;">dbcc checkdb('MvxRef') with no_infomsgs, all_errormsgs</span>

<span style="color: #008000;">Msg 2508, Niveau 16, État 1, Ligne 1
Le nombre In-row data USED page pour l'objet 'Client_NULL', ID d'index 0,
ID de partition 29725599596544,ID d'unité d'allocation 29725599596544
(type In-row data) est incorrect. Exécutez DBCC UPDATEUSAGE.
CHECKDB a trouvé 0 erreurs d'allocation et 1 erreurs de cohérence
dans la table 'Client_NULL' (ID d'objet 453576654).
CHECKDB a trouvé 0 erreurs d'allocation et 1 erreurs de cohérence
dans la base de données 'MvxRef'.</span></pre>
<p>Lorsque DBCC CHECKDB arrive sur un objet en particulier, il se constitue une synthèse des informations qui décrivent la table, et notamment le décompte de pages totales allouées, et de pages utilisées pour l&#8217;objet. Si une des valeurs est négative, le message 2508 est renvoyé avec en paramètre une indication sur quelle valeur est négative (RSVD pour le nombre total de pages, USED pour le nombre de pages utilisées). La valeur négative en question est remontée par la DMV sys.allocation_units:</p>
<pre><span style="color: #0000ff;">select allocation_unit_id, type_desc, container_id,total_pages, used_pages
from sys.allocation_units AU inner join sys.partitions P on AU.container_id = P.hobt_id
and P.object_id = object_id('Client_NULL')</span></pre>
<pre><span style="color: #008000;">allocation_unit_id   type_desc        container_id         total_pages    used_pages  
-------------------- ---------------- -------------------- -------------- ------------
29725599596544       IN_ROW_DATA      29725599596544       0              <strong><span style="color: #ff0000;">-104 </span>        </strong></span></pre>
<p>Il se trouve que la table a été vidée de son contenu entre deux DBCC CHECKDB, et l&#8217;erreur n&#8217;avait pas été détectée immédiatement après la migration. Un petit coup de DBCC UPDATEUSAGE sur cette table:</p>
<pre><span style="color: #0000ff;">dbcc updateusage('MvxRef','Client_NULL')</span></pre>
<pre><span style="color: #008000;">DBCC UPDATEUSAGE : nombre mis à jour pour la table 'Client_NULL'
(index 'Client_NULL', partition 1) :
 Pages USED (In-row Data) : changement de (-104) pages en (0) pages.
Exécution de DBCC terminée. Si DBCC vous a adressé des messages d'erreur,
contactez l'administrateur système.</span></pre>
<p>Et tout est rentré dans l&#8217;ordre:</p>
<pre><span style="color: #0000ff;">select allocation_unit_id, type_desc, container_id,total_pages, used_pages
from sys.allocation_units AU inner join sys.partitions P on AU.container_id = P.hobt_id
and P.object_id = object_id('Client_NULL')</span></pre>
<pre><span style="color: #008000;">allocation_unit_id   type_desc     container_id         total_pages   used_pages
-------------------- ------------- -------------------- ------------- ------------
29725599596544       IN_ROW_DATA   29725599596544       0             <strong><span style="color: #ff0000;">0           </span></strong>

</span></pre>
<p><span style="color: #000000;">A+. David B.</span><strong>Continuez votre lecture sur le blog :</strong>
<ul class="similar-posts">
<li><a href="http://blog.capdata.fr/index.php/error-8976-8978-problemes-de-chainage-comment-recuperer-les-donnees/" rel="bookmark" title="30 mai 2011">Error 8976 / 8978, problèmes de chaînage, comment récupérer les données</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/suppression-accidentelle-de-ligne-comment-retrouver-le-coupable/" rel="bookmark" title="6 octobre 2011">Suppression accidentelle de ligne : comment retrouver le coupable ?</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/regenerer-le-ddl-des-indexes-full-text/" rel="bookmark" title="12 octobre 2011">Regénérer le DDL des indexes FULL TEXT</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/pourquoi-il-faut-sauvegarder-les-bases-systemes/" rel="bookmark" title="3 juillet 2011">Pourquoi il faut sauvegarder les bases systèmes</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/how-to-reduire-lenveloppe-de-tempdb/" rel="bookmark" title="7 juillet 2011">How-To : réduire l&#8217;enveloppe de tempdb</a> (David BAFFALEUF) [SQL Server]</li>
</ul>
<p><!-- Similar Posts took 4.108 ms -->
<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fmsg-2508-level-16-state-1-the-in-row-data-for-object-is-incorrect%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fmsg-2508-level-16-state-1-the-in-row-data-for-object-is-incorrect%2F&amp;style=normal&amp;b=2" height="61" width="50" /><br />
			</a>
		</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.capdata.fr/index.php/msg-2508-level-16-state-1-the-in-row-data-for-object-is-incorrect/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Bench avec NetApp / Datacore / ESX</title>
		<link>http://blog.capdata.fr/index.php/bench-avec-netapp-datacore-esx/</link>
		<comments>http://blog.capdata.fr/index.php/bench-avec-netapp-datacore-esx/#comments</comments>
		<pubDate>Tue, 26 Apr 2011 17:23:47 +0000</pubDate>
		<dc:creator>David BAFFALEUF</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[datacore]]></category>
		<category><![CDATA[sqlio]]></category>
		<category><![CDATA[virtualisation]]></category>

		<guid isPermaLink="false">http://blog.capdata.fr/?p=2101</guid>
		<description><![CDATA[J&#8217;ai récemment eu l&#8217;occasion de pouvoir effectuer un bench IO sur une plateforme SQL Server sous ESX avec un couple Datacore / NetApp au niveau du stockage.
Contexte technique:
DataCore SANMelody / NetApp:
Le but de la manœuvre est de déterminer le débit maximum que sera capable de tirer le  sous système IO cible. L&#8217;architecture est la suivante:
2 [...]]]></description>
			<content:encoded><![CDATA[<p>J&#8217;ai récemment eu l&#8217;occasion de pouvoir effectuer un bench IO sur une plateforme SQL Server sous ESX avec un couple Datacore / NetApp au niveau du stockage.</p>
<h2>Contexte technique:</h2>
<h3>DataCore SANMelody / NetApp:</h3>
<p>Le but de la manœuvre est de déterminer le débit maximum que sera capable de tirer le  sous système IO cible. L&#8217;architecture est la suivante:</p>
<p>2 salles techniques composées chacune de (<br />
-<em> 1 Contrôleur NetApp FAS 3210<br />
- 1 tiroir T1 de 14 x 450 Gb SAS 15KRPM en RAIDDP. (11 disques data, 2 disques DP, 1 disque de spare).<br />
- 1 tiroir T2 de 14 x 600 Gb SAS 15KRPM en RAIDDP. (11 disques data, 2 disques DP, 1 disque de spare).<br />
- 1 tiroir T3 de 14 x 600 Gb SAS 15KRPM en RAIDDP. (11 disques data, 2 disques DP, 1 disque de spare).<br />
- 1 contrôleur DataCore SANMelody 3.0.3.5, 16Gb cache.<br />
- 1 volume Tiers1 pris sur le tiroir T1 raccordé directement à DataCore.</em><br />
)</p>
<p>Soit:</p>
<p><a href="http://blog.capdata.fr/wp-content/uploads/2011/04/Datacore1.jpg"><img class="alignnone size-full wp-image-2147" title="Datacore" src="http://blog.capdata.fr/wp-content/uploads/2011/04/Datacore1.jpg" alt="" width="688" height="510" /></a></p>
<p><a href="http://www.datacore.com/Software/Products/Product-Archive/SANmelody-Software.aspx">SANMelody </a>est un logiciel tournant sur Windows Server et qui émule un SAN en quelque sorte.  Il s&#8217;intercale entre le SAN et le host, et présente aux machines clientes (en l&#8217;occurence ici les 2 ESX) des disques comme s&#8217;il s&#8217;agissait directement du SAN. Il est à la fois initiateur côté baie, et target côté host. Deux avantages principaux:<br />
- Il réserve une grande partie de la mémoire de la machine Windows pour bufferiser les IOs en provenance des hosts. Il ajoute un niveau de cache supplémentaire ici de 16Gb par contrôleur ce qui n&#8217;est pas rien.<br />
- Ensuite, il permet de répliquer les blocs vers un second contrôleur de manière synchrone pour assurer une redondance des données.</p>
<p>Il est clair que le cache utilisé par SANMelody n&#8217;est pas <a href="http://en.wikipedia.org/wiki/Algorithms_for_Recovery_and_Isolation_Exploiting_Semantics">WAL compliant</a> dans la mesure où celui-ci réserve ses buffers à partir de la mémoire présentée par windows. Par contre, dans la mesure où le contrôleur est répliqué en synchrone sur une autre salle, chacune étant alimentée de manière autonome, on limite les risques. Il faudrait que les deux salles tombent en même temps pour exposer des données à la corruption. D&#8217;ailleurs une option &#8216;<em>Force Cache Write Through</em>&#8216; existe au niveau de SANMelody, qui permet comme son nom l&#8217;indique de forcer les écritures sur le média (donc le contrôleur NetApp). Donc bon, dans l&#8217;absolu, avec une bonne stratégie de backup derrière, pourquoi pas&#8230;</p>
<p>Dans tous les cas, nous allons tester avec et sans cette option.</p>
<h3>Les ESX:</h3>
<p>Du côté de la VM, on ne peut pas vraiment faire mieux: les procs sont des <a href="http://ark.intel.com/Product.aspx?id=46499">core i7 Xeon 7560</a>, 64 bits, VT compatibles. D&#8217;après la matrice de VMM [1], un guest en 64 bits sur une telle plateforme profitera des avantages suivants:</p>
<p>- Il s&#8217;exécute en ring 0, donc on est en CPU-direct (pas de translation binaire).<br />
- L&#8217;hyperviseur utilisera la technologie <a href="http://www.intel.com/technology/itj/2006/v10i3/1-hardware/8-virtualization-future.htm">EPT </a>du core i7 pour décharger la gestion des page tables et de la mémoire par guest.</p>
<p>On n&#8217;utilisera pas VMFS mais directement du soft RDM, donc pas d&#8217;histoire d&#8217;alignement [2]. Et de toutes façons, dans la mesure où ESX est amené à disparaître au profit d&#8217;ESXi, on pourra difficilement vérifier cela à l&#8217;avenir puisqu&#8217;on n&#8217;aura plus de service console.</p>
<p>Le guest OS quant à lui sera un Windows 2008R2 64 bits avec 4vCPU, 4Gb de mémoire dans un premier temps, et SQL Server 2005 x64 Enterprise Edition.</p>
<p>Les deux ESX forment un cluster HA DRS. Nous avons testé de lancer une alimentation (1 million de singleton inserts) pendant une bascule vMotion pour valider la partie secours et on n&#8217;a observé qu&#8217;un overhead de 7% seulement.</p>
<h3>SQLIO:</h3>
<p>Pour tester le sous système IO complet, nous nous appuierons sur <a href="http://www.google.com/url?sa=t&amp;source=web&amp;cd=1&amp;ved=0CBwQFjAA&amp;url=http%3A%2F%2Fwww.microsoft.com%2Fdownloads%2Fdetails.aspx%3Ffamilyid%3D9a8b005b-84e4-4f24-8d65-cb53442d9e19&amp;rct=j&amp;q=SQLIO&amp;ei=MXWGTa_THMXx4gbW463xCA&amp;usg=AFQjCNG457croR4yz8ueC0pGOkn5lZTK5Q&amp;sig2=8pRGh0eocw2nVSfnwzmJqw&amp;cad=rja">SQLIO. </a>Quelques rappels sur les paramètres de base:</p>
<pre><span style="color: #008000;">Usage: sqlio [options] [&lt;filename&gt;...]
 [options] may include any of the following:
<span style="color: #ff0000;"> -k&lt;R|W&gt;                 kind of IO (R=reads, W=writes)</span>
 -t&lt;threads&gt;             number of threads
<span style="color: #ff0000;"> -s&lt;secs&gt;                number of seconds to run</span>
 -d&lt;drv_A&gt;&lt;drv_B&gt;..      use same filename on each drive letter given
 -R&lt;drv_A/0&gt;,&lt;drv_B/1&gt;.. raw drive letters/number for I/O
<span style="color: #ff0000;"> -f&lt;stripe factor&gt;       stripe size in blocks, random, or sequential</span>
 -p[I]&lt;cpu affinity&gt;     cpu number for affinity (0 based)(I=ideal)
 -a[R[I]]&lt;cpu mask&gt;      cpu mask for (R=roundrobin (I=ideal)) affinity
<span style="color: #ff0000;"> -o&lt;#outstanding&gt;        depth to use for completion routines</span>
<span style="color: #ff0000;"> -b&lt;io size(KB)&gt;         IO block size in KB</span>
 -i&lt;#IOs/run&gt;            number of IOs per IO run
 -m&lt;[C|S]&gt;&lt;#sub-blks&gt;    do multi blk IO (C=copy, S=scatter/gather)
<span style="color: #ff0000;"> -L&lt;[S|P][i|]&gt;           latencies from (S=system, P=processor) timer</span>
<span style="color: #ff0000;"> -B&lt;[N|Y|H|S]&gt;           set buffering (N=none, Y=all, H=hdwr, S=sfwr)</span>
 -S&lt;#blocks&gt;             start I/Os #blocks into file
 -v1.1.1                 I/Os runs use same blocks, as in version 1.1.1
<span style="color: #ff0000;"> -F&lt;paramfile&gt;           read parameters from &lt;paramfile&gt;</span>
Defaults:
 -kR -t1 -s30 -f64 -b2 -i64 -BN testfile.dat
Maximums:
 -t (threads):                   256
 no. of files, includes -d &amp; -R: 256
 filename length:                256
</span></pre>
<p>Nous nous contenterons d&#8217;utiliser les paramètres suivants:</p>
<p><strong>-k: </strong>type d&#8217;accès (lectures / écritures)<br />
<strong>- s: </strong>durée du test en secondes.<br />
<strong>- f: </strong>sequential / random.<br />
<strong>- o: </strong>nombre d&#8217;outstandings IOs, le nombre d&#8217;I/Os en attente par thread. Dans la mesure où chaque worker et le Lazy Writer postent la grande majorité des IOs en asynchrone, il y a des chances qu&#8217;il y ait plus d&#8217;une IO asynchrone par thread à un instant donné. Nous choisirons de tester 64, 128 et 256 outstanding IOs par thread.<br />
<strong>- b:</strong> taille de l&#8217;IO. Nous utiliserons toutes les tailles multiple de la page de 8K jusqu&#8217;à 256K, en accès séquentiel, et seulement 8k en aléatoire.<br />
<strong>- L:</strong> indique que nous souhaitons récupérer les valeurs de latence remontées par le système.<br />
<strong>- B:</strong> indique que nous ne souhaitons pas utiliser le cache NTFS (les fichiers de données et journaux sont ouverts en FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING)<br />
<strong>- F: </strong>nous allons passer à sqlio un fichier de paramètres dans lequel nous indiquerons le nombre de threads et la taille et la localisation du fichier de test.</p>
<h2>Différentes batteries de tests:</h2>
<p>Nous allons tester trois configuration possibles:</p>
<p>BANC1: NetApp -&gt; Datacore sans Force Cache Write Through -&gt; ESX -&gt; Guest OS<br />
BANC2:- NetApp -&gt; Datacore avec Force Cache Write Through -&gt; ESX -&gt; Guest OS<br />
BANC3:  NetApp -&gt; ESX -&gt; Guest OS</p>
<p>Le BANC1 sera l&#8217;utilisation nominale de la plateforme. Le BANC2 devra montrer le prix à payer pour être 100% WAL-compliant. Dans la dernière configuration, on souhaite mesurer ce qu&#8217;on perd potentiellement si on retire Datacore  de l&#8217;équation.</p>
<h2>Préparation des scripts:</h2>
<p>Il a fallu se constituer un petit package de recueil  et d&#8217;agrégation d&#8217;informations pour le bench. On a choisi de le préparer en fonction des IOs types envoyées par SQL Server:</p>
<ul>
<li>Lectures séquentielles de 8,32,64,128 et 256 K (read-ahead, ramp up prefetch, DBCC CHECKDB&#8230;)</li>
<li>Écritures séquentielles de 8,32,64,128 et 256 K (Backup / restore, Alter index Rebuild, Bulk insert&#8230;)</li>
<li>Lectures aléatoires de 8K: lectures types OLTP.</li>
<li>Écritures aléatoires de 8K: écritures types OLTP.</li>
</ul>
<p>Voici le contenu du fichier .bat utilisé:</p>
<pre><span style="color: #008000;">sqlio -kR -s120 -o64 -fsequential -b8 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kR -s120 -o64 -fsequential -b32 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kR -s120 -o64 -fsequential -b64 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kR -s120 -o64 -fsequential -b128 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kR -s120 -o64 -fsequential -b256 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kR -s120 -o64 -frandom -b8 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kW -s120 -o64 -fsequential -b8 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kW -s120 -o64 -fsequential -b32 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kW -s120 -o64 -fsequential -b64 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kW -s120 -o64 -fsequential -b128 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kW -s120 -o64 -fsequential -b256 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kW -s120 -o64 -frandom -b8 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
<span style="color: #ff6600;">sqlio -kR -s120 -o128 -fsequential -b8 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kR -s120 -o128 -fsequential -b32 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kR -s120 -o128 -fsequential -b64 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kR -s120 -o128 -fsequential -b128 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kR -s120 -o128 -fsequential -b256 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kR -s120 -o128 -frandom -b8 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kW -s120 -o128 -fsequential -b8 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kW -s120 -o128 -fsequential -b32 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kW -s120 -o128 -fsequential -b64 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kW -s120 -o128 -fsequential -b128 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kW -s120 -o128 -fsequential -b256 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kW -s120 -o128 -frandom -b8 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"</span>
<span style="color: #3366ff;">sqlio -kR -s120 -o256 -fsequential -b8 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kR -s120 -o256 -fsequential -b32 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kR -s120 -o256 -fsequential -b64 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kR -s120 -o256 -fsequential -b128 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kR -s120 -o256 -fsequential -b256 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kR -s120 -o256 -frandom -b8 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kW -s120 -o256 -fsequential -b8 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kW -s120 -o256 -fsequential -b32 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kW -s120 -o256 -fsequential -b64 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kW -s120 -o256 -fsequential -b128 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kW -s120 -o256 -fsequential -b256 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"
sqlio -kW -s120 -o256 -frandom -b8 -BH -LS -F"C:\Program Files\SQLIO\my_param_file.txt"</span></span>

<span style="color: #3366ff;"> </span></pre>
<p>En tout 72 minutes de bench, trois pavés avec 64, 128 et 256 outstanding IOs par thread. Le contenu du fichier de configuration my_param_file.txt :</p>
<pre><span style="color: #0000ff;">e:\testfile.dat 4 0x0 51200</span></pre>
<p>On utilisera donc 4 threads (1 par vCPU) sur un fichier de 50Gb. Le choix dans la taille du fichier de test est important car on souhaite tester le sous-système disque et principalement le back-end de la baie. Il faut donc créer un fichier qui ne tienne ni dans le cache du DATACORE ni dans le cache du contrôleur I/O de la baie, pour obliger celle-ci à déborder sur les disques. Le plus gros datamart de l&#8217;application faisant 50Gb, on n&#8217;a donc pas choisi cette taille par hasard. On lance donc le script et on s&#8217;en va boire un café&#8230;</p>
<pre><span style="color: #0000ff;">C:\Program Files\SQLIO&gt;benchSQL.bat &gt; results.txt</span></pre>
<h2>Intégration des résultats en base:</h2>
<p>Une fois le test terminé, il va falloir intégrer les résultats en base. Le script ci-dessous doit nous permettre de stocker le contenu du fichier results.txt dans une table et de l&#8217;afficher de manière simple et conviviale. Ce script est un dérivé de celui que propose Brent Ozar (<a href="http://www.brentozar.com/">blog</a>|<a href="http://www.google.com/url?sa=t&amp;source=web&amp;cd=1&amp;ved=0CBMQFjAA&amp;url=http%3A%2F%2Ftwitter.com%2Fbrento&amp;rct=j&amp;q=brento%20twitter&amp;ei=b3SGTaznAdKH5Aaay_jmCA&amp;usg=AFQjCNF7nMLkdSLEMVUlQMmawMZbgZJ-4w&amp;sig2=mlfMI6Sin4eYAQLZtzHmUg&amp;cad=rja">twitter</a>) sur <a href="http://www.google.com/url?sa=t&amp;source=web&amp;cd=1&amp;ved=0CBMQFjAA&amp;url=http%3A%2F%2Fsqlserverpedia.com%2Fwiki%2FSAN_Performance_Tuning_with_SQLIO&amp;rct=j&amp;q=sqlio%20sqlserverpedia&amp;ei=1n6GTbqHBoWYhQfZvb2mAw&amp;usg=AFQjCNHbAxDQmYhug-uXPsFjE-qkevNUcQ&amp;sig2=k-L6_o_pogXCETR91ax-JQ&amp;cad=rja">SQLServerPedia</a> et utilise une vue sur le fichier resultat brut plutôt qu&#8217;une table intermédiaire.</p>
<pre><span style="color: #0000ff;">create database <span style="color: #ff0000;">SQLIO</span>
GO
use <span style="color: #ff0000;">SQLIO</span>
GO

CREATE TABLE [<span style="color: #0000ff;">dbo</span>].[<span style="color: #ff0000;">SQLIO_Import</span>](
 <span style="color: #0000ff;">[RowID] [int] IDENTITY(1,1) NOT NULL,
 [ParameterRowID] [int] NULL,
 [ResultText] [varchar](max) NULL,
CONSTRAINT [PK_SQLIO_Import] PRIMARY KEY CLUSTERED
(
 [RowID] ASC
))
GO</span>

create view <span style="color: #ff0000;">v_sqlimport</span>
as
select
<span style="color: #008000;">-- Test set #</span>
row_number() over (order by ParameterRowID) '<span style="color: #ff0000;">Test set #</span>',
<span style="color: #008000;">-- SQLIO Version</span>
(select substring(ResultText, 7, len(ResultText))
 from sqlio_import inner0 where ResultText like 'sqlio v%'
 and inner0.ParameterRowID = imp.ParameterRowID) '<span style="color: #ff0000;">SQLIO Version</span>'
<span style="color: #008000;">-- # of threads</span>
,(select substring(ResultText, charindex('with',ResultText) + 5,
 (charindex('threads',ResultText)-(charindex('with',ResultText)+5)))
 from sqlio_import inner1 where ResultText like '%using mask%'
 and inner1.ParameterRowID = imp.ParameterRowID) '<span style="color: #ff0000;"># of worker threads</span>'
<span style="color: #008000;">-- Read or Write</span>
,(select substring(ResultText, charindex('-k',ResultText) + 2, 1)
 from sqlio_import inner2 where ResultText like '%&gt;sqlio%'
 and inner2.ParameterRowID = imp.ParameterRowID) '<span style="color: #ff0000;">Reads (R) | Writes (W)</span>'
<span style="color: #008000;">-- Duration (secs)</span>
,(select substring(ResultText, charindex('-s',ResultText) + 2,
 ((charindex('-',ResultText,charindex('-s',ResultText)+2))-(charindex('-s',ResultText) + 2)))
 from sqlio_import inner3 where ResultText like '%&gt;sqlio%'
 and inner3.ParameterRowID = imp.ParameterRowID) '<span style="color: #ff0000;">Duration (sec)</span>'
<span style="color: #008000;">-- I/O Size (Kb)</span>
,(select substring(ResultText, charindex('-b',ResultText) + 2,
 ((charindex('-',ResultText,charindex('-b',ResultText)+2))-(charindex('-b',ResultText) + 2)))
 from sqlio_import inner4 where ResultText like '%&gt;sqlio%'
 and inner4.ParameterRowID = imp.ParameterRowID) '<span style="color: #ff0000;">I/O Size (Kb)</span>'
<span style="color: #008000;">-- I/O Patterns</span>
,(select substring(ResultText, charindex('-f',ResultText) + 2,
 ((charindex('-',ResultText,charindex('-f',ResultText)+2))-(charindex('-f',ResultText) + 2)))
 from sqlio_import inner4 where ResultText like '%&gt;sqlio%'
 and inner4.ParameterRowID = imp.ParameterRowID) '<span style="color: #ff0000;">I/O Pattern (Random | sequential)</span>'
<span style="color: #008000;">-- # outstanding I/Os</span>
,(select substring(ResultText, charindex('-o',ResultText) + 2,
 ((charindex('-',ResultText,charindex('-o',ResultText)+2))-(charindex('-o',ResultText) + 2)))
 from sqlio_import inner4 where ResultText like '%&gt;sqlio%'
 and inner4.ParameterRowID = imp.ParameterRowID) '<span style="color: #ff0000;">Outstanding I/Os per thread</span>'
<span style="color: #008000;">-- File Size (Mb)</span>
,(select substring(ResultText, charindex('size:',ResultText) + 6,
 ((charindex('MB',ResultText,charindex('size:',ResultText)+6))-(charindex('size:',ResultText) + 6)))
 from sqlio_import inner4 where ResultText like 'using specified size:%'
 and inner4.ParameterRowID = imp.ParameterRowID) '<span style="color: #ff0000;">Target File Size (Mb)</span>'
<span style="color: #008000;">-- IOPS</span>
,(select substring(ResultText, charindex('IOs/sec:',ResultText) + 8,
 ((len(ResultText)-(charindex('IOs/sec:',ResultText) + 7))))
 from sqlio_import inner4 where ResultText like 'IOs/sec:%'
 and inner4.ParameterRowID = imp.ParameterRowID) '<span style="color: #ff0000;">IOPS</span>'
<span style="color: #008000;">-- MBPS</span>
,(select substring(ResultText, charindex('MBs/sec:',ResultText) + 8,
 ((len(ResultText)-(charindex('MBs/sec:',ResultText) + 7))))
 from sqlio_import inner4 where ResultText like 'MBs/sec:%'
 and inner4.ParameterRowID = imp.ParameterRowID) '<span style="color: #ff0000;">MBPS</span>'
<span style="color: #008000;">-- Min Latency</span>
,(select substring(ResultText, charindex('Min_Latency(ms):',ResultText) + 17,
 ((len(ResultText)-(charindex('Min_Latency(ms):',ResultText) + 16))))
 from sqlio_import inner4 where ResultText like 'Min_Latency(ms):%'
 and inner4.ParameterRowID = imp.ParameterRowID) '<span style="color: #ff0000;">Min Latency (ms)</span>'
<span style="color: #008000;">-- Avg Latency</span>
,(select substring(ResultText, charindex('Avg_Latency(ms):',ResultText) + 17,
 ((len(ResultText)-(charindex('Avg_Latency(ms):',ResultText) + 16))))
 from sqlio_import inner4 where ResultText like 'Avg_Latency(ms):%'
 and inner4.ParameterRowID = imp.ParameterRowID) '<span style="color: #ff0000;">Avg Latency (ms)</span>'
<span style="color: #008000;">-- Max Latency</span>
,(select substring(ResultText, charindex('Max_Latency(ms):',ResultText) + 17,
 ((len(ResultText)-(charindex('Max_Latency(ms):',ResultText) + 16))))
 from sqlio_import inner4 where ResultText like 'Max_Latency(ms):%'
 and inner4.ParameterRowID = imp.ParameterRowID) '<span style="color: #ff0000;">Max Latency (ms)</span>'
FROM     <span style="color: #ff0000;">dbo.sqlio_import imp</span>
where <span style="color: #ff0000;">ParameterRowID </span>is not null
GROUP BY <span style="color: #ff0000;">ParameterRowID</span>
GO

print '<span style="color: #ff0000;">Please import data now and then run:
-- 1)
UPDATE dbo.sqlio_import
SET    parameterrowid = (SELECT   TOP 1 rowid
 FROM     dbo.sqlio_import parm
 WHERE    parm.resulttext LIKE ''%&gt;sqlio %''
 AND parm.rowid &lt;= upd.rowid
 ORDER BY rowid DESC)
FROM   dbo.sqlio_import upd
GO</span>'
print '
<span style="color: #ff0000;">--2)
select * from v_sqlimport order by MBPS desc, IOPS desc
GO</span>'</span></pre>
<p>A cette étape, il ne reste plus qu&#8217;à importer le fichier results.txt avec SSIS (Database -&gt; Tasks -&gt; Import Data&#8230;).  Il faut penser notamment à indiquer que le type de données en entrée est du DT_TEXT:</p>
<p><a href="http://blog.capdata.fr/wp-content/uploads/2011/03/importSQLIO11.jpg"><img class="alignnone size-full wp-image-2106" title="importSQLIO1" src="http://blog.capdata.fr/wp-content/uploads/2011/03/importSQLIO11.jpg" alt="" width="535" height="505" /></a></p>
<p>Puis à sélectionner la table SQLIO_Import et colonne ResultText comme réceptacles du fichier results.txt:</p>
<p><a href="http://blog.capdata.fr/wp-content/uploads/2011/03/importSQLIO2.jpg"><img class="alignnone size-full wp-image-2107" title="importSQLIO2" src="http://blog.capdata.fr/wp-content/uploads/2011/03/importSQLIO2.jpg" alt="" width="534" height="497" /></a> <a href="http://blog.capdata.fr/wp-content/uploads/2011/03/importSQLIO3.jpg"><img class="alignnone size-full wp-image-2108" title="importSQLIO3" src="http://blog.capdata.fr/wp-content/uploads/2011/03/importSQLIO3.jpg" alt="" width="581" height="470" /></a></p>
<p>Une fois l&#8217;import du fichier terminé, il ne reste plus qu&#8217;à exécuter les tâches de post-import:</p>
<pre><span style="color: #0000ff;">UPDATE dbo.sqlio_import
SET    parameterrowid = (SELECT   TOP 1 rowid
 FROM     dbo.sqlio_import parm
 WHERE    parm.resulttext LIKE '%&gt;sqlio %'
 AND parm.rowid &lt;= upd.rowid
 ORDER BY rowid DESC)
FROM   dbo.sqlio_import upd
GO</span>
<em><span style="color: #008000;">(834 ligne(s) affectée(s))</span></em></pre>
<p>Puis appeler la vue:</p>
<pre><span style="color: #0000ff;">select * from v_sqlimport order by MBPS desc, IOPS desc
GO

<a href="http://blog.capdata.fr/wp-content/uploads/2011/03/importSQLIO5.jpg"><img class="alignnone size-full wp-image-2109" title="importSQLIO5" src="http://blog.capdata.fr/wp-content/uploads/2011/03/importSQLIO5.jpg" alt="" width="1254" height="230" /></a>
</span></pre>
<h2>Résultats</h2>
<p><strong>Légende: </strong></p>
<p>- R8random128: reads 8K random 128 outstanding IOs<br />
- R8sequential128: reads 8K sequential 128 outstanding IOs<br />
- W8random128: writes 8K random 128 outstanding IOs<br />
- etc&#8230;</p>
<ul>
<li><strong>BANC1 vs BANC2: </strong>pas besoin de photo finish pour les départager:</li>
</ul>
<p><a href="http://blog.capdata.fr/wp-content/uploads/2011/04/comp1.jpg"><img class="alignnone size-large wp-image-2171" title="comp1" src="http://blog.capdata.fr/wp-content/uploads/2011/04/comp1-1024x443.jpg" alt="" width="1024" height="443" /></a></p>
<p><a href="http://blog.capdata.fr/wp-content/uploads/2011/04/comp2.jpg"><img class="alignnone size-large wp-image-2172" title="comp2" src="http://blog.capdata.fr/wp-content/uploads/2011/04/comp2-1024x447.jpg" alt="" width="1024" height="447" /></a></p>
<p>680Mb/s sur du read-ahead, on peut difficilement battre un tel résultat à part peut être avec des SSD, qui eux offrent en plus un stockage résilient.</p>
<ul>
<li><strong>BANC1 vs BANC3: </strong>avec ou sans Datacore ? c&#8217;est le même constat.</li>
</ul>
<p><a href="http://blog.capdata.fr/wp-content/uploads/2011/04/IOPS.jpg"><img class="alignnone size-large wp-image-2196" title="IOPS" src="http://blog.capdata.fr/wp-content/uploads/2011/04/IOPS-1024x502.jpg" alt="" width="1024" height="502" /></a></p>
<p><a href="http://blog.capdata.fr/wp-content/uploads/2011/04/DEBIT.jpg"><img class="alignnone size-large wp-image-2197" title="DEBIT" src="http://blog.capdata.fr/wp-content/uploads/2011/04/DEBIT-1024x489.jpg" alt="" width="1024" height="489" /></a></p>
<p><a href="http://blog.capdata.fr/wp-content/uploads/2011/04/comp6.jpg"><img class="alignnone size-large wp-image-2175" title="comp6" src="http://blog.capdata.fr/wp-content/uploads/2011/04/comp6-1023x541.jpg" alt="" width="1023" height="541" /></a></p>
<p>Je dois avouer que j&#8217;ai été très surpris des performances offertes par la solution de Datacore. Ca peut être une arme terrible pour écraser tous les problèmes de latence IO dans des environnements virtualisés&#8230; En attendant la globalisation des SSD, et avec sa solution de réplication de blocs, Datacore a encore de beaux jours devant lui.</p>
<p>A+. David B.</p>
<p><em><strong>Références:</strong></em></p>
<p>[1]: <a href="http://www.google.com/url?sa=t&amp;source=web&amp;cd=1&amp;sqi=2&amp;ved=0CBQQFjAA&amp;url=http%3A%2F%2Fwww.vmware.com%2Ffiles%2Fpdf%2Fperf-vsphere-monitor_modes.pdf&amp;rct=j&amp;q=Virtual%20Machines%20Execution%20Modes%20in%20VSphere%204.0%2C%20Nikhil%20Bhatia%2C%20VMWare%20Corp&amp;ei=EfS2Tb6mK8qx8QOA8JFD&amp;usg=AFQjCNG245enzqG2IwvJAEqcNbRiJQtE7Q&amp;sig2=UhzTVj0JSkCmQES8GG7H_w&amp;cad=rja">Virtual Machines Execution Modes in VSphere 4.0, Nikhil Bhatia, VMWare Corp</a><br />
[2]: Storage Block Alignment with VMWare Virtual Infrastructure, Tim Coste, juillet 2007, TR-3593<strong>Continuez votre lecture sur le blog :</strong>
<ul class="similar-posts">
<li><a href="http://blog.capdata.fr/index.php/consistence-des-ecritures-avec-sata/" rel="bookmark" title="13 mars 2011">Consistence des écritures avec SATA</a> (David BAFFALEUF) [Operating SystemSQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/how-to-reduire-lenveloppe-de-tempdb/" rel="bookmark" title="7 juillet 2011">How-To : réduire l&#8217;enveloppe de tempdb</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/msg-2508-level-16-state-1-the-in-row-data-for-object-is-incorrect/" rel="bookmark" title="10 mai 2011">Msg 2508, Level 16, State 1: the In-Row data %% for object %% is incorrect</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/io-asynchrones-episode-1/" rel="bookmark" title="5 juillet 2011">I/O asynchrones (épisode 1)</a> (David BAFFALEUF) [Operating SystemSQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/point-in-time-recovery-et-fn_dump_dblog/" rel="bookmark" title="13 juillet 2011">Point-in-time recovery et fn_dump_dblog()</a> (David BAFFALEUF) [SQL Server]</li>
</ul>
<p><!-- Similar Posts took 5.319 ms -->
<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fbench-avec-netapp-datacore-esx%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fbench-avec-netapp-datacore-esx%2F&amp;style=normal&amp;b=2" height="61" width="50" /><br />
			</a>
		</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.capdata.fr/index.php/bench-avec-netapp-datacore-esx/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Consistence des écritures avec SATA</title>
		<link>http://blog.capdata.fr/index.php/consistence-des-ecritures-avec-sata/</link>
		<comments>http://blog.capdata.fr/index.php/consistence-des-ecritures-avec-sata/#comments</comments>
		<pubDate>Sun, 13 Mar 2011 22:43:50 +0000</pubDate>
		<dc:creator>David BAFFALEUF</dc:creator>
				<category><![CDATA[Operating System]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[FUA]]></category>
		<category><![CDATA[SATA]]></category>

		<guid isPermaLink="false">http://blog.capdata.fr/?p=1904</guid>
		<description><![CDATA[A l&#8217;origine, cet autre post de James Hamilton qui s&#8217;interroge sur le support de FUA par le protocole ATA/IDE/SATA.
C&#8217;est quoi FUA ?
FUA = Force Unit Access. Il s&#8217;agit d&#8217;un bit au sein d&#8217;un Command Disk Block Read ou Write SCSI qui permet d&#8217;indiquer au driver de ne pas lire ou écrire depuis ou à partir [...]]]></description>
			<content:encoded><![CDATA[<p>A l&#8217;origine, cet autre <a href="http://perspectives.mvdirona.com/2008/04/17/DisksLiesAndDamnDisks.aspx">post</a> de James Hamilton qui s&#8217;interroge sur le support de FUA par le protocole ATA/IDE/SATA.</p>
<h2>C&#8217;est quoi FUA ?</h2>
<p>FUA = Force Unit Access. Il s&#8217;agit d&#8217;un bit au sein d&#8217;un Command Disk Block Read ou Write SCSI qui permet d&#8217;indiquer au driver de ne pas lire ou écrire depuis ou à partir d&#8217;un cache mais bien depuis ou à partir du média physique, c&#8217;est à dire du disque magnétique. Par exemple un CDB de Write(10) (source <a href="http://en.wikipedia.org/wiki/SCSI_Write_Commands">wikipedia</a>):</p>
<p><a href="http://blog.capdata.fr/wp-content/uploads/2011/02/CDB_Write_10_SCSI1.jpg"><img class="alignnone size-full wp-image-1909" title="CDB_Write_10_SCSI" src="http://blog.capdata.fr/wp-content/uploads/2011/02/CDB_Write_10_SCSI1.jpg" alt="" width="481" height="163" /></a></p>
<p>C&#8217;est important d&#8217;avoir cette garantie pour les moteurs de bases de données car elle leur permet de supporter ce qu&#8217;on appelle le <em>WAL protocol</em> , le pilier qui soutient  la durabilité des transactions dans un SGBD. Lorsqu&#8217;une transaction est validée, quoi qu&#8217;il advienne ensuite cette information doit être physiquement inscrite quelque part sur un média persistant. Même si on perd un disque, l&#8217;alimentation de la machine ou toute la salle d&#8217;un seul coup, tout ce qui a été modifié avant la validation de cette transaction doit se retrouver sur disque avant fin du COMMIT, d&#8217;où le nom (WAL = Write Ahead Logging).</p>
<p>SQL Server utilise deux commutateurs lors de l&#8217;ouverture des fichiers de données et des journaux de transactions au démarrage de l&#8217;instance pour valider ce comportement (FILE_FLAG_WRITE_THROUGH  |  FILE_FLAG_NO_BUFFERING). Le premier indique que l&#8217;OS a le droit de bufferiser une écriture mais qu&#8217;il ne doit pas renvoyer d&#8217;acquittement avant que cette écriture n&#8217;ait touché le support physique (l&#8217;équivalent de O_DSYNC sur Linux, cf ce <a href="http://blog.capdata.fr/index.php/sybase-ase-direct-io-dsync-onoff-raw-device/">post</a>). La seconde indique que l&#8217;Os n&#8217;a pas le droit de lire une donnée depuis le cache, mais de forcer la lecture à partir du disque. Donc vous l&#8217;aurez compris, l&#8217;association des deux permet de bypasser complètement le cache.</p>
<h2>FUA et SATA:</h2>
<p>Ce bit FUA fait partie intégrante du protocole SCSI mais reste ignoré par ATA et SATA. Lorsque l&#8217;information arrive au driver atapi.sys, elle est tout simplement non interprétée, les écritures vont dans le cache et l&#8217;acquittement OK revient au moteur. Pas terrible pour la durabilité des transactions. La seule arme face au problème reste de désactiver le cache en écriture au niveau du disque (sous windows une petite case à décocher dans les propriétés du disque, voir au bas de cet article), mais le problème, c&#8217;est qu&#8217;il n&#8217;y a encore aujourd&#8217;hui aucun moyen de savoir réellement si le driver a bien pris en compte cette modification ou non.</p>
<p>Depuis, SATA II a annoncé le support de FUA, mais dans la pratique je vois encore beaucoup de :</p>
<pre><span style="color: #0000ff;"><em>sd 0:0:0:0: [sda] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA</em></span></pre>
<p>sur les forums linux, même avec des contrôleurs SATA II. Après une petite discussion avec <a href="http://blogs.msdn.com/b/psssql/">Bob Dorr</a> du support MS, je suis parti à la recherche de preuves. J&#8217;ai un laptop avec un disque SATA, un SQL Server 2005 SP3, je voudrais savoir si les écritures envoyées vers le disque seront écrites dans le cache ou réellement sur le disque. Capture du matériel pour mémo:</p>
<p><a href="http://blog.capdata.fr/wp-content/uploads/2011/03/satadrv.jpg"><img class="alignnone size-medium wp-image-2053" title="satadrv" src="http://blog.capdata.fr/wp-content/uploads/2011/03/satadrv-300x297.jpg" alt="" width="300" height="297" /></a></p>
<h2>La Descente vers le disque:</h2>
<p>Avant d&#8217;aller plus loin, il faut rappeler un peu la théorie des IOs sous Windows, et la relation avec SQL Server.</p>
<p>Lorsque SQL Server démarre, il ouvre les fichiers de données et les journaux de transactions des bases qu&#8217;il monte en ligne avec <a href="http://msdn.microsoft.com/en-us/library/aa363858%28v=vs.85%29.aspx">CreateFile</a>. Il récupère un type HANDLE qui sera utilisé ensuite pour toutes les opérations sur le fichier (lectures / écritures). Lorsqu&#8217;un processus demande l&#8217;ouverture ou la création d&#8217;un fichier, le noyau transfère le contrôle à l&#8217;Object Manager. Celui-ci créé un objet de type <a href="http://msdn.microsoft.com/en-us/library/ff545834%28v=vs.85%29.aspx">FILE_OBJECT</a> pour décrire le fichier, ses attributs, ses modes d&#8217;accès, la liste des IOs en cours, etc.. Lorsque notre CreateFile ouvre un fichier en FILE_FLAG_WRITE_THROUGH, le bit FO_WRITE_THROUGH (0&#215;00000010 *) va être ajouté au membre .<em>Flags</em> du FILE_OBJECT.</p>
<p>Ensuite, lorsque le programme doit  écrire dans le fichier, il utilisera une des primitives <a href="http://msdn.microsoft.com/en-us/library/aa365747%28v=vs.85%29.aspx">WriteFile</a>() / <a href="http://msdn.microsoft.com/en-us/library/aa365748%28v=vs.85%29.aspx">WriteFileEx</a>() / <a href="http://msdn.microsoft.com/en-us/library/aa365749%28v=vs.85%29.aspx">WriteFileGather</a>().  Lors de l&#8217;écriture, le noyau transfère le contrôle à l&#8217;IO Manager pour l&#8217;initialisation de l&#8217;IO. Celui-ci va créer un <a href="http://msdn.microsoft.com/en-us/library/ff550694%28v=vs.85%29.aspx">IRP </a>(IO Request Packet) pour décrire l&#8217;opération.  Chaque IRP a une structure en ascenceur. Elle contient un entête et plusieurs piles (une par driver dans la pile de drivers jusqu&#8217;au disque) qu&#8217;on appelle des <a href="http://msdn.microsoft.com/en-us/library/ff550659%28v=vs.85%29.aspx">IO_STACK_LOCATIONs</a>. L&#8217;entête sert à pointer vers l&#8217;IO_STACK_LOCATION correspondant au driver en cours dans la pile. Lorsque le driver courant a terminé son opération, il passe l&#8217;IRP au driver suivant dans la stack et fait pointer l&#8217;entête de l&#8217;IRP vers l&#8217;IO_STACK_LOCATION suivante, etc&#8230;(cf <a href="http://msdn.microsoft.com/en-us/library/ms810023.aspx">Handling IRPS : what every driver writer needs to know</a>).</p>
<p>Chaque IRP renseigne un bitmask (<em>.Flags</em>) pour indiquer quels sont les commutateurs actifs. Parmi ceux-ci, SL_WRITE_THROUGH (0&#215;04 *) propage notre contrainte d&#8217;écriture sur le média au driver, et c&#8217;est là que les choses se gâtent.</p>
<p>L&#8217;idée est donc de contrôler la propagation du writethrough à travers les différentes IO_STACK_LOCATIONS et voir quel driver réinitialise le bit.</p>
<p>J&#8217;ai tenté pendant trois ou quatre jours de traquer la bête avec livekd, mais c&#8217;est assez difficile de prendre SQL Server la main dans le sac. Les IOs sont assez rapides et les IRP vers les disques sont supprimées par l&#8217;IO Manager avant que je n&#8217;aie le temps de les inspecter. J&#8217;ai donc décidé (pas borné du tout le garçon <img src='http://blog.capdata.fr/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' />  ) d&#8217;écrire un petit programme très simple qui me ferait en boucle des écritures alignées sur les secteurs disque en write-through exactement comme SQL Server, avec le côté asynchrone en moins (pas important pour ce que l&#8217;on cherche, et en plus pas très simple à gérer, mais promis ce sera l&#8217;objet d&#8217;un prochain post).</p>
<p>Le source:</p>
<pre><span style="color: #008000;">// ------------------------------------------------------------------------------------------------------------
// writeThrough.cpp : Ecrit en boucle des caractères dans un fichier en mode Writethrough / No Buffering
// Utilisé avec livekd, permet de scruter le contenu des IO_STACK_LOCATION dans l'IRP correspondante
// pour contrôler si le bit SL_WRITE_THROUGH est bien propagé jusqu'au driver (pb ATA / IDE /S-ATA).
//
// On utilisera un IOCTL (IOCTL_DISK_GET_DRIVE_GEOMETRY) pour récupérer la taille du secteur
//
// dbaffaleuf@capdata.fr (c) CapData Consulting 2011
//
// ------------------------------------------------------------------------------------------------------------</span>

<span style="color: #008000;">#include &lt;stdafx.h&gt;
#include &lt;windows.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;winioctl.h&gt;
#include &lt;direct.h&gt;</span>

<span style="color: #0000ff;">int _tmain(int argc, _TCHAR* argv[])
{
     if (argc!=2)
    {
        printf("Usage is:  writeThrough outputfilename\n");
        return 1;
    }

 <span style="color: #008000;">// Récupération de la taille du secteur disque avec un IOCTL ---------------------------------------------------</span>
 DISK_GEOMETRY dg;
 HANDLE hDrive;
 DWORD ioctlJnk; 

 hDrive=CreateFile(TEXT("\\\\.\\PhysicalDrive0")
                          ,0
                          ,FILE_SHARE_READ
                          | FILE_SHARE_WRITE
                          ,NULL
                          ,OPEN_EXISTING
                          ,0
                          ,NULL);

 if (INVALID_HANDLE_VALUE==hDrive)
 {
     printf("Unable to open drive PhysicalDrive0. Last error=%d\n",GetLastError());
     return 1;
 }

 if (! DeviceIoControl(hDrive
                          ,IOCTL_DISK_GET_DRIVE_GEOMETRY
                          ,NULL
                          ,0
                          ,&amp;dg
                          ,sizeof(dg)
                          ,&amp;ioctlJnk
                          ,NULL) )
 {
     printf("Error on IOCTL (IOCTL_DISK_GET_DRIVE_GEOMETRY) to %s.  Last error=%d\n",argv[1],GetLastError());
     return 1;
 }

 CloseHandle(hDrive);

 <span style="color: #008000;">// Ouverture du fichier Writethrough + No Buffering ---------------------------------------------------------</span>
 HANDLE hOutputFile=CreateFile(argv[1]
                      ,GENERIC_READ
                      | GENERIC_WRITE
                      ,0
                      ,NULL
                      ,CREATE_ALWAYS
                      ,FILE_ATTRIBUTE_NORMAL
                      | FILE_FLAG_WRITE_THROUGH
                      | FILE_FLAG_NO_BUFFERING
                      ,NULL);

 if (INVALID_HANDLE_VALUE==hOutputFile)
 {
     printf("Unable to open file %s.  Last error=%d\n",argv[1],GetLastError());
     return 1;
 }

 <span style="color: #008000;">// Initialisation du tampon et écriture --------------------------------------------------------------------</span>
 wchar_t *csBuffer = (wchar_t *)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,dg.BytesPerSector * sizeof(wchar_t));
 int len = swprintf_s(csBuffer,dg.BytesPerSector,L"abcdefghijklmnopqrstuvwxyz");    
 DWORD dwBytesWritten;
 DWORD dwTotalBytesWritten=0;

 do
 {
     if (! WriteFile(hOutputFile
                       ,csBuffer
                       ,dg.BytesPerSector
                       ,&amp;dwBytesWritten
                       ,NULL))
     {
         printf ("WriteFile failed with error %d.\n", GetLastError());
         return 1;
     }

    dwTotalBytesWritten+=dwBytesWritten;
    printf("Operation terminée, %d octets écrits\n", dwTotalBytesWritten);
    Sleep (1);

 } while (TRUE);

 HeapFree(GetProcessHeap(),0,csBuffer);
 CloseHandle(hOutputFile);

 return 0;
}</span></pre>
<p>La seule contrainte d&#8217;utilisation de (FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH) est d&#8217;écrire en s&#8217;alignant sur les secteurs du disque, il nous faut donc récupérer la taille du secteur avec un <a href="http://msdn.microsoft.com/en-us/library/aa363219%28v=vs.85%29.aspx">IOCTL</a> (en l&#8217;occurence <em>IOCTL_DISK_GET_DRIVE_GEOMETRY</em>). Dans la plupart des cas ce sera 512 octets, mais des tailles de 4K vont commencer à arriver sur le marché, donc il ne faut pas présumer et bien calculer tout ça proprement. Dans la seconde partie, on initialise un buffer sur une taille multiple de la taille du secteur et on écrit dans notre fichier hOutputFile en boucle continue, avec une pause de 1 ms entre chaque écriture.</p>
<p>On lance le programme en boucle et on va passer à la partie livekd:</p>
<pre><span style="color: #0000ff;"><strong>C:\DBA\DEV\CPP\writeThrough\Debug&gt;writeThrough.exe test.txt</strong>
Operation terminée, 512 octets écrits
Operation terminée, 1024 octets écrits
Operation terminée, 1536 octets écrits
Operation terminée, 2048 octets écrits
Operation terminée, 2560 octets écrits
Operation terminée, 3072 octets écrits</span>
...</pre>
<h2>Observation des IRPs:</h2>
<p>Première chose à faire si vous souhaitez reproduire ce POC, il vous faudra récupérer les <a href="http://msdn.microsoft.com/en-us/windows/hardware/gg463009">outils de debug de Windows</a> et les <a href="http://msdn.microsoft.com/en-us/windows/hardware/gg463028">symboles </a>pour votre plateforme. Il vous faudra aussi récupérer <a href="http://technet.microsoft.com/en-us/sysinternals/bb897415">LiveKD</a>.</p>
<p>Ensuite pointer vers le serveur de symboles MS:</p>
<pre><span style="color: #0000ff;">set _NT_SYMBOL_PATH=srv*c:\temp\symbols*http://msdl.microsoft.com/download/symbols   </span></pre>
<p>Et lancer livekd. Pour assurer un peu plus la cohérence des résultats, il peut être nécessaire de passer par un bcdedit /debug on + reboot pour activer le débogage du noyau. La stratégie est de retrouver les IRPs actives associées au fichier dans lequel on écrit par le biais d&#8217;un !irpfind en lui passant l&#8217;adresse du DEVICE_OBJECT associé au disque. Dans un foreach, ensuite on affiche le contenu détaillé de toutes les irps que l&#8217;on retrouve. Attention, vous devrez sans doute le faire un certain nombre de fois pour pouvoir observer un IRP décrivant une écriture (MAJOR= 4 =&gt; IRP_MJ_WRITE).</p>
<p>Première chose, on va récupérer l&#8217;adresse de notre processus writeThrough.exe, et le passer en process par défaut sous livekd:</p>
<pre><span style="color: #0000ff;">lkd&gt; !sym quiet
quiet mode - symbol prompts on</span></pre>
<pre><span style="color: #0000ff;">lkd&gt; !process 0 0 writeThrough.exe
PROCESS <span style="color: #008000;">881b39d0  </span>SessionId: 0  Cid: 0c08    Peb: 7ffde000  ParentCid: 0960
 DirBase: 0aa80740  ObjectTable: e4168638  HandleCount:   9.
 Image: writeThrough.exe
</span>
<span style="color: #0000ff;">lkd&gt; .process <span style="color: #008000;">881b39d0  </span>
Implicit process is now 881b39d0
</span></pre>
<p>Ensuite, nous allons nous intéresser au fichier test.txt. Il nous faut récupérer le FILE_OBJECT créé par l&#8217;Object Manager lors du CreateFile():</p>
<pre><span style="color: #0000ff;">0: kd&gt; !handle 0 3
(...)
07dc: Object: <span style="color: #008000;">8701f1b0  </span>GrantedAccess: 0012019f Entry: e3baefb8
Object: 8701f1b0  Type: (8a57a900) File
 ObjectHeader: 8701f198 (old version)
 HandleCount: 1  PointerCount: 1
 Directory Object: 00000000  Name: \CAPDATA\DEV\DEV++\writeThrough\Debug\test.txt {HarddiskVolume3}
(...)

0: kd&gt; dt nt!_FILE_OBJECT <span style="color: #008000;">8701f1b0</span>
 +0x000 Type             : 0n5
 +0x002 Size             : 0n112
<span style="color: #008000;"> +0x004 DeviceObject     : </span></span><span style="color: #008000;">0x8a4a5a30</span><span style="color: #0000ff;"><span style="color: #008000;">_DEVICE_OBJECT</span>
 +0x008 Vpb              : 0x8a4a71e8 _VPB
 +0x00c FsContext        : 0xe6be4928 Void
 +0x010 FsContext2       : 0xe6be4a80 Void
 +0x014 SectionObjectPointer : 0x8839f5cc _SECTION_OBJECT_POINTERS
 +0x018 PrivateCacheMap  : (null)
 +0x01c FinalStatus      : 0n0
 +0x020 RelatedFileObject : 0x88752ec0 _FILE_OBJECT
 +0x024 LockOperation    : 0 ''
 +0x025 DeletePending    : 0 ''
 +0x026 ReadAccess       : 0x1 ''
 +0x027 WriteAccess      : 0x1 ''
 +0x028 DeleteAccess     : 0 ''
 +0x029 SharedRead       : 0 ''
 +0x02a SharedWrite      : 0 ''
 +0x02b SharedDelete     : 0 ''
<span style="color: #008000;"> +0x02c Flags            : 0x4101a</span>
 +0x030 FileName         : _UNICODE_STRING "\CAPDATA\DEV\DEV++\writeThrough\Debug\test.txt"
 +0x038 CurrentByteOffset : _LARGE_INTEGER 0x7c00
 +0x040 Waiters          : 0
 +0x044 Busy             : 0
 +0x048 LastLock         : (null)
 +0x04c Lock             : _KEVENT
 +0x05c Event            : _KEVENT
 +0x06c CompletionContext : (null)

</span></pre>
<p>Par la même occasion, on va vérifier si notre CreateFile() a bien propagé FO_WRITE_THROUGH au FILE_OBJECT:</p>
<pre><span style="color: #0000ff;">0: kd&gt; ? (<span style="color: #008000;">0x4101a </span>&amp; 0x00000010)
Evaluate expression: 16 = 00000010</span></pre>
<p>OK. On peut donc poursuivre en gardant sous le coude l&#8217;adresse du DEVICE OBJECT renvoyé par le dt nt!_FILE_OBJECT&#8230; :</p>
<pre><span style="color: #008000;">(...)
+0x004 DeviceObject     : <strong>0x8a4a5a30</strong>_DEVICE_OBJECT
(...)</span></pre>
<h2>Courage !</h2>
<p>Et c&#8217;est là que la partie de pêche commence vraiment. Je vous laisse le soin de regarder comment les instructions sous un déboggeur fonctionnent mais l&#8217;idée est de récupérer l&#8217;adresse de chaque IRP associée au DEVICE OBJECT de notre disque et d&#8217;afficher son contenu, en vérifiant qu&#8217;il provient bien de notre petit programme. Il faut être patient. Au bout d&#8217;un certain nombre de tentatives, on arrive à en attraper une :</p>
<pre><span style="color: #0000ff;">lkd&gt; .foreach /pS 1 /ps 3 (irpaddr {.foreach /pS 21 /ps 9 (irpaddressblk {!irpfind 0 0 device <span style="color: #008000;">0x8a4a5a30</span>}) { .echo ${irpaddressblk} } }) { !irp ${irpaddr} 1 }
Irp is active with 10 stacks 6 is current (= 0x882a312c)
 Mdl=88195a60: No System Buffer: Thread 881059e8:  Irp stack trace.  
<span style="color: #008000;">Flags = 00000043</span>
ThreadListEntry.Flink = 882a3018
ThreadListEntry.Blink = 882a3018
IoStatus.Status = 00000000
IoStatus.Information = 00000000
RequestorMode = 00000000
Cancel = 00
CancelIrql = 0
ApcEnvironment = 00
UserIosb = a0e60878
UserEvent = a0e60820
Overlay.AsynchronousParameters.UserApcRoutine = 00000000
Overlay.AsynchronousParameters.UserApcContext = 00000000
Overlay.AllocationSize = 00000000 - 00000000
CancelRoutine = 00000000   
UserBuffer = 88083000
&amp;Tail.Overlay.DeviceQueueEntry = 882a3048
Tail.Overlay.Thread = 881059e8
Tail.Overlay.AuxiliaryBuffer = 00000000
Tail.Overlay.ListEntry.Flink = 00000000
Tail.Overlay.ListEntry.Blink = 00000000
Tail.Overlay.CurrentStackLocation = 882a312c
Tail.Overlay.OriginalFileObject = 899ef028
Tail.Apc = 00000000
Tail.CompletionKey = 00000000
 cmd  flg cl Device   File     Completion-Context
 [  0, 0]   0  0 00000000 00000000 00000000-00000000    

 Args: 00000000 00000000 00000000 00000000
 [  0, 0]   0  0 00000000 00000000 00000000-00000000    

 Args: 00000000 00000000 00000000 00000000
 [  0, 0]   0  0 00000000 00000000 00000000-00000000    

 Args: 00000000 00000000 00000000 00000000
 [  0, 0]   0  0 00000000 00000000 00000000-00000000    

 Args: 00000000 00000000 00000000 00000000
 [  0, 0]   0  0 00000000 00000000 00000000-00000000    

 Args: 00000000 00000000 00000000 00000000
<span style="color: #008000;">&gt;[  4, 0]   0  0 8a483770 00000000 f77077ca-8a483548    
 \Driver\Disk    
 Args: 00000000 00000000 00000000 00000000</span>
 [  4, 0]   0  0 8a483548 00000000 f7238962-8a4a5ae8    
 \Driver\PartMgr    
 Args: 00000000 00000000 00000000 00000000
 [  4, 0]   0  0 8a4a5a30 00000000 f74a7680-8a48a7b8    
 \Driver\Ftdisk    
 Args: 00000000 00000000 00000000 00000000
 [  4, 0]   0  0 8a48a700 00000000 f7063e3f-a0e6045c    
 \Driver\VolSnap    
 Args: 00000000 00000000 00000000 00000000
 [  4, 0]   0  0 8979c020 00000000 00000000-00000000    
 \FileSystem\Ntfs
 Args: 00000000 00000000 00000000 00000000</span></pre>
<p>J&#8217;ai volontairement colorié le membre .Flags et l&#8217;IO_STACK_LOCATION courante. On voit bien ici l&#8217;empilement des drivers: Ntfs -&gt; VolSnap -&gt; Ftdisk -&gt; PartMgr -&gt; Disk. Entre crochets, notre couple [MAJOR,Minor] qui indique le type d&#8217;opération en cours (pour plus d&#8217;infos cf l&#8217;aide de la commande !irp dans le <a href="http://msdn.microsoft.com/en-us/windows/hardware/gg487428">DDK</a>). [4,0] indique que la commande en cours est un IRP_MJ_WRITE, donc une écriture, et la petite indirection à l&#8217;extrême gauche montre que la besogne est entre les mains du driver le plus bas dans la stack (c:\Windows\system32\Disk.sys). Quant à la valeur de .Flags:</p>
<pre><span style="color: #0000ff;">lkd&gt; ? (0x00000043 &amp; 0x4)
Evaluate expression: 0 = 00000000</span></pre>
<p>Bingo. Pas de SL_WRITE_THROUGH. J&#8217;ai pû faire le test avec et sans cache activé au niveau du disque, et le résultat est le même:</p>
<p><a href="http://blog.capdata.fr/wp-content/uploads/2011/03/cacheactivation.jpg"><img class="alignnone size-medium wp-image-2063" title="cacheactivation" src="http://blog.capdata.fr/wp-content/uploads/2011/03/cacheactivation-282x300.jpg" alt="" width="282" height="300" /></a></p>
<p>Donc potentiellement si je débranche ma batterie et que je coupe mon alimentation au moment d&#8217;une grosse écriture il y a un risque de perdre une transaction validée. Je vous avoue que je ne l&#8217;ai testé qu&#8217;une seule fois (je tiens aussi un peu à mon matériel), mais la coupure est intervenue quelques millisecondes avant le commit, et SQL Server a rollbacké tout ça proprement. Il faudrait faire une batterie de tests mais je n&#8217;ai pas les moyens de cramer du disque comme ça. Après, celà jette tout de même de lourds soupçons sur la capacité de SATA à répondre aux besoins des SGBD.</p>
<p>Ce n&#8217;est plus qu&#8217;une question de coût ou de performance que de comparer SCSI à SATA. Celà devient une question d&#8217;intégrité des données, et donc un problème fondamental. Affaire à suivre&#8230;</p>
<p>A+. David B.</p>
<p><em>* Sources: winddk.h</em></p>
<p><em>Références:<br />
<a href="http://support.microsoft.com/kb/234656">http://support.microsoft.com/kb/234656</a><br />
<a href="http://support.microsoft.com/kb/46091">http://support.microsoft.com/kb/46091</a><br />
<a href="http://support.microsoft.com/kb/86903">http://support.microsoft.com/kb/86903</a></em><strong>Continuez votre lecture sur le blog :</strong>
<ul class="similar-posts">
<li><a href="http://blog.capdata.fr/index.php/io-asynchrones-episode-1/" rel="bookmark" title="5 juillet 2011">I/O asynchrones (épisode 1)</a> (David BAFFALEUF) [Operating SystemSQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/bench-avec-netapp-datacore-esx/" rel="bookmark" title="26 avril 2011">Bench avec NetApp / Datacore / ESX</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/point-in-time-recovery-et-fn_dump_dblog/" rel="bookmark" title="13 juillet 2011">Point-in-time recovery et fn_dump_dblog()</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/installation-asm-sur-suse-10-en-64-bits-avec-multipathing-emc-powerpath/" rel="bookmark" title="5 juin 2009">Installation ASM sur SUSE 10 en 64 Bits avec multipathing (EMC Powerpath)</a> (Thierry GASCARD) [Oracle]</li>
<li><a href="http://blog.capdata.fr/index.php/fragmentation-sur-des-tables-stockees-en-s-gam/" rel="bookmark" title="20 août 2010">Fragmentation sur des tables stockées en S-GAM</a> (David BAFFALEUF) [SQL Server]</li>
</ul>
<p><!-- Similar Posts took 5.373 ms -->
<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fconsistence-des-ecritures-avec-sata%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fconsistence-des-ecritures-avec-sata%2F&amp;style=normal&amp;b=2" height="61" width="50" /><br />
			</a>
		</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.capdata.fr/index.php/consistence-des-ecritures-avec-sata/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Alter table rebuild</title>
		<link>http://blog.capdata.fr/index.php/alter-table-rebuild/</link>
		<comments>http://blog.capdata.fr/index.php/alter-table-rebuild/#comments</comments>
		<pubDate>Wed, 02 Mar 2011 16:52:57 +0000</pubDate>
		<dc:creator>Louis HOCHBERG</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[alter table rebuild]]></category>
		<category><![CDATA[dbcc ind]]></category>

		<guid isPermaLink="false">http://blog.capdata.fr/?p=1943</guid>
		<description><![CDATA[Apparue  avec SQL Server 2008, cette nouvelle option permet de reconstruire une table qui n&#8217;a pas d&#8217;index cluster.  Une table de ce type est appelé un heap.  Avant cette version, la solution souvent utilisée pour reconstruire une table heap consiste à créer artificiellement un index cluster sur une ou plusieurs colonnes de la table puis de supprimer le [...]]]></description>
			<content:encoded><![CDATA[<p>Apparue  avec SQL Server 2008, cette nouvelle option permet de reconstruire une table qui n&#8217;a pas d&#8217;index cluster.  Une table de ce type est appelé un heap.  Avant cette version, la solution souvent utilisée pour reconstruire une table heap consiste à créer artificiellement un index cluster sur une ou plusieurs colonnes de la table puis de supprimer le nouvel index. Le choix des colonnes pour créer l&#8217;index cluster est parfois complexe, car il détermine l&#8217;ordre de stockage des lignes dans les pages de la table.  La commande Alter table rebuild est intéressante à plus d&#8217;un titre :</p>
<ul>
<li>Plus de question à se poser sur le choix des colonnes pour reconstruire la table</li>
<li>Elle est simple à utiliser : <span style="color: #0000ff">Alter table TABLE1 rebuild</span></li>
<li><span style="font-size: 12px;line-height: 18px"><span style="color: #3366ff"> </span></span>Elle permet de diminuer le nombre de &laquo;&nbsp;forwarding record&nbsp;&raquo; dans les pages de données.</li>
<li>Lorsque l&#8217;on met à jour les données d&#8217;une ligne et qu&#8217;il ne reste pas suffisamment d&#8217;espace libre dans la page, SQL Server enregistre une information &laquo;&nbsp;forwarding record&nbsp;&raquo; qui contient un pointeur vers l&#8217;adresse de la page qui contient la suite de la ligne. Ce phénomène est pénalisant pour les performances, car il impose des déplacements coûteux entre les pages lors des lectures de lignes.</li>
<li>Elle permet de réduire le nombre de pages utilisées par une table après une réduction importante des données des lignes.</li>
<li>Les indexes non cluster sont reconstruits en même temps que la table</li>
<li>Si on est en version Enterprise, il est possible de reconstruire la table ONLINE , c&#8217;est à dire en optimisant le verrouillage de la table pour pénaliser le moins possible les accès à celle_ci lors de la reconstruction.</li>
<p>Voici 2 exemples d&#8217;utilisation</ul>
<p><span style="font-family: Consolas, Monaco, 'Courier New', Courier, monospace;font-size: 12px;line-height: 18px">&#8211; #######################################################################<br />
</span><span style="font-family: Consolas, Monaco, 'Courier New', Courier, monospace;font-size: 12px;line-height: 18px">&#8211; # Demo1 : Reconstruire un HEAP pour réduire les &laquo;&nbsp;forwarding record&nbsp;&raquo; </span><span style="font-family: Consolas, Monaco, 'Courier New', Courier, monospace;font-size: 12px;line-height: 18px">&#8211;<br />
&#8211; #######################################################################</span></p>
<pre><span style="color: #008000">-- creation base de tests</span>
drop database B1
Create database B1
alter database B1 set recovery simple
use B1
go

<span style="color: #008000">-- creation table HEAP exemple</span>
create table TABLE1(a numeric identity, b varchar(4000), c varchar(4000))
go

<span style="color: #008000">-- ajoute 1000 lignes dans la table</span>
insert into TABLE1 values ('b','c')
go 1000

<span style="color: #008000">-- Inspection de son plan d'allocation
</span>dbcc traceon (3604)dbcc ind('B1', 'TABLE1',-1)
<span style="color: #008000">PageFID PagePID     IAMFID IAMPID      ObjectID    IndexID     PartitionNumber PartitionID        
------- ----------- ------ ----------- ----------- ----------- --------------- --------------------
1       77          NULL   NULL        2105058535  0           1               72057594038779904  
1       55          1      77          2105058535  0           1               72057594038779904  
1       78          1      77          2105058535  0           1               72057594038779904  
1       79          1      77          2105058535  0           1               72057594038779904  
1       80          1      77          2105058535  0           1               72057594038779904   ...
(5 row(s) affected)
</span>=&gt; 5 pages (1 IAM et 4 Data)</pre>
<pre><span style="color: #008000">-- Mise à jour de la table pour provoquer des débordements de lignes :</span>
update TABLE1 set b=(replicate('b',1000)), c=(replicate('c',1000))

<span style="color: #008000">-- Nouveau plan d'allocation
<span style="color: #000000">dbcc ind('B1', 'TABLE1',-1)</span></span></pre>
<pre><span style="color: #008000">PageFID PagePID     IAMFID IAMPID      ObjectID    IndexID     PartitionNumber PartitionID        
------- ----------- ------ ----------- ----------- ----------- --------------- -------------------
1       77          NULL   NULL        2105058535  0           1               72057594038779904  
1       55          1      77          2105058535  0           1               72057594038779904  
1       78          1      77          2105058535  0           1               72057594038779904  
1       79          1      77          2105058535  0           1               72057594038779904  
1       80          1      77          2105058535  0           1               72057594038779904  
1       89          1      77          2105058535  0           1               72057594038779904  
1       90          1      77          2105058535  0           1               72057594038779904  
1       93          1      77          2105058535  0           1               72057594038779904  
1       94          1      77          2105058535  0           1               72057594038779904  
1       176         1      77          2105058535  0           1               72057594038779904  
1       177         1      77          2105058535  0           1               72057594038779904  
1       178         1      77          2105058535  0           1               72057594038779904  ...
...
--(336 row(s) affected)
</span>--=&gt; 336 pages (1 page IAM + 335 pages de donnees)</pre>
<pre><span style="color: #008000">-- Affiche fragmentation
</span>select object_name(F.object_id) Object, index_type_desc, page_count, record_count, forwarded_record_count, avg_fragmentation_in_percent from sys.indexes I
cross apply sys.dm_db_index_physical_stats(db_id(),object_id('TABLE1'), -1,NULL,'DETAILED') F where I.object_id = object_id('TABLE1')</pre>
<pre><span style="color: #008000">Object                         index_type_desc                page_count           record_count         forwarded_record_count avg_fragmentation_in_percent
------------------------------ ------------------------------ -------------------- -------------------- ---------------------- ----------------------------
TABLE1                         HEAP                           335                  1993                 993                    4,44444444444444
=&gt; 335 pages
</span>=&gt; 1993 records pour 1000 lignes
=&gt; 993 forwarded_record_count - 993 lignes sur 1000 contiennent un pointeur vers une autre page qui contient la suite de la page, c'est pas terrible
=&gt; fragmentation : 4.4 %

<span style="color: #008000">-- Affiche le contenu de la page 78</span>
dbcc page('B1', 1, 78, 3)
<span style="color: #008000">Slot 0 Offset 0x258 Length 9
Record Type = FORWARDING_STUB        Record Attributes =                  Record Size = 9
Memory Dump @0x46E6C258
00000000:   040d0100 00010001 00†††††††††††††††††.........
Forwarding to  =  file 1 page 269 slot 1
Slot 1 Offset 0x261 Length 9
</span>=&gt; informations pour le row forwarding

<span style="color: #008000">-- reconstruit la table TABLE1</span>
alter table TABLE1 rebuild

-- Nouveau plan d'allocation
dbcc ind('B1', 'TABLE1',-1)
<span style="color: #008000">PageFID PagePID     IAMFID IAMPID      ObjectID    IndexID     PartitionNumber PartitionID          iam_chain_type      
------- ----------- ------ ----------- ----------- ----------- --------------- -------------------- --------------------
1       110         NULL   NULL        2105058535  0           1               72057594038845440    In-row data          
1       109         1      110         2105058535  0           1               72057594038845440    In-row data          
1       504         1      110         2105058535  0           1               72057594038845440    In-row data          
1       505         1      110         2105058535  0           1               72057594038845440    In-row data          
...
(252 row(s) affected)</span>
=&gt; la table a été reconstruite a un nouvel emplacement, elle utilise 252 pages au lieu de 335</pre>
<pre><span style="color: #008000">-- Affiche fragmentation
</span>select object_name(F.object_id) Object, index_type_desc, page_count, record_count, forwarded_record_count, avg_fragmentation_in_percent from sys.indexes I
cross apply sys.dm_db_index_physical_stats(db_id(),object_id('TABLE1'), -1,NULL,'DETAILED') F where I.object_id = object_id('TABLE1')

<span style="color: #008000">Object                         index_type_desc                page_count           record_count         forwarded_record_count avg_fragmentation_in_percent
------------------------------ ------------------------------ -------------------- -------------------- ---------------------- ----------------------------
TABLE1                         HEAP                           251                  1000                 0                      3,03030303030303
</span>=&gt; 3 lignes par page, le niveau de fragmentation a légèrement baissé
<strong>=&gt; et surtout il n'y a plus de forwarded record</strong>

#########################################
-- Demo2 : On continue l'exemple précédent, on va Reconstruire la table pour optimiser le nombre de pages après une Réduction des données dans les lignes:
#########################################</pre>
<pre><span style="color: #008000">-- reduction des donnees dans les lignes
</span>update TABLE1 set b='b', c='c'
<span style="color: #008000">(1000 row(s) affected)
</span></pre>
<pre><span style="color: #008000">-- Affiche fragmentation
</span>select object_name(F.object_id) Object, avg_page_space_used_in_percent, index_type_desc, page_count, record_count, forwarded_record_count, avg_fragmentation_in_percent from sys.indexes I
cross apply sys.dm_db_index_physical_stats(db_id(),object_id('TABLE1'), -1,NULL,'DETAILED') F where I.object_id = object_id('TABLE1')
<div><span style="color: #008000">
Object                         avg_page_space_used_in_percent index_type_desc                page_count           record_count         forwarded_record_count avg_fragmentation_in_percent</span></div>
<div><span style="color: #008000">------------------------------ ------------------------------ ------------------------------ -------------------- -------------------- ---------------------- ----------------------------
</span><span style="color: #008000">TABLE1                         1,25517667407957               HEAP                           251                  1000                 0                      3,03030303030303</span></div>
<div>=&gt; avg_page_space_used_in_percent : 1,25% d'espace utilise par page c'est faible !</div>
<div><span style="color: #008000">-- reconstruction de la table</span></div>
<div>alter table TABLE1 rebuild</div>
</pre>
<pre><span style="color: #008000">-- plan d'allocation</span>
dbcc ind('B1', 'TABLE1',-1)
<span style="color: #008000">PageFID PagePID     IAMFID IAMPID      ObjectID    IndexID     PartitionNumber PartitionID          iam_chain_type      
------- ----------- ------ ----------- ----------- ----------- --------------- -------------------- --------------------
1       121         NULL   NULL        2105058535  0           1               72057594038976512    In-row data          
1       120         1      121         2105058535  0           1               72057594038976512    In-row data          
1       400         1      121         2105058535  0           1               72057594038976512    In-row data          
1       401         1      121         2105058535  0           1               72057594038976512    In-row data          
1       402         1      121         2105058535  0           1               72057594038976512    In-row data          
1       403         1      121         2105058535  0           1               72057594038976512    In-row data          
(6 row(s) affected)
<span style="color: #000000">=&gt; A nouveau 6 pages</span></span></pre>
<pre><span style="color: #008000">Object                         avg_page_space_used_in_percent index_type_desc                page_count           record_count         forwarded_record_count avg_fragmentation_in_percent
------------------------------ ------------------------------ ------------------------------ -------------------- -------------------- ---------------------- ----------------------------
TABLE1                         64,225352112676                HEAP                           5                    1000                 0                      50
</span>=&gt; avg_page_space_used_in_percent : 64,22 % c'est mieux !
fragmentation 50% : pas genant pour une table de cette taille (- de 8 pages)</pre>
<p><strong>Continuez votre lecture sur le blog :</strong>
<ul class="similar-posts">
<li><a href="http://blog.capdata.fr/index.php/error-8976-8978-problemes-de-chainage-comment-recuperer-les-donnees/" rel="bookmark" title="30 mai 2011">Error 8976 / 8978, problèmes de chaînage, comment récupérer les données</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/how-to-reduire-la-taille-du-journal-de-transactions-sur-disque/" rel="bookmark" title="11 juillet 2011">How-To: réduire la taille du journal de transactions sur disque</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/msg-2508-level-16-state-1-the-in-row-data-for-object-is-incorrect/" rel="bookmark" title="10 mai 2011">Msg 2508, Level 16, State 1: the In-Row data %% for object %% is incorrect</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/fragmentation-sur-des-tables-stockees-en-s-gam/" rel="bookmark" title="20 août 2010">Fragmentation sur des tables stockées en S-GAM</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/regenerer-le-ddl-des-indexes-full-text/" rel="bookmark" title="12 octobre 2011">Regénérer le DDL des indexes FULL TEXT</a> (David BAFFALEUF) [SQL Server]</li>
</ul>
<p><!-- Similar Posts took 3.812 ms -->
<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Falter-table-rebuild%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Falter-table-rebuild%2F&amp;style=normal&amp;b=2" height="61" width="50" /><br />
			</a>
		</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.capdata.fr/index.php/alter-table-rebuild/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Voucher certification SQL Server 70-432 offert !</title>
		<link>http://blog.capdata.fr/index.php/certification-sql-server-70-432-offerte/</link>
		<comments>http://blog.capdata.fr/index.php/certification-sql-server-70-432-offerte/#comments</comments>
		<pubDate>Fri, 25 Feb 2011 16:17:05 +0000</pubDate>
		<dc:creator>David BAFFALEUF</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[70-432]]></category>
		<category><![CDATA[certification]]></category>

		<guid isPermaLink="false">http://blog.capdata.fr/?p=1885</guid>
		<description><![CDATA[Un petit post rapide pour vous rappeler que CapData Consulting est centre de formation PROMETRIC depuis novembre 2010 et offre à chaque stagiaire du cursus DBA SQL Server Production un coupon de passage de la certif MCTS 70-432 ainsi que le livre Micrososft Press de préparation.
Si vous êtes intéressés par une formation orientée opérationnelle associée [...]]]></description>
			<content:encoded><![CDATA[<p>Un petit post rapide pour vous rappeler que CapData Consulting est centre de formation <a href="http://www.capdata.fr/certification-sql-server-2008-DBA.htm">PROMETRIC</a> depuis novembre 2010 et offre à chaque stagiaire du cursus DBA SQL Server Production un coupon de passage de la certif MCTS 70-432 ainsi que le <a href="http://www.amazon.com/Self-Paced-Training-70-432-2008-Implementation-PRO-Certification/dp/0735626057">livre Micrososft Press de préparation</a>.</p>
<p>Si vous êtes intéressés par une formation orientée opérationnelle associée à une reconnaissance de vos compétences sur SQL Server , n&#8217;hésitez pas à contacter notre service commercial au 0820 620 400 (0.12€/min) ou par mail à <a href="mailto:formation@capdata.fr">formation@capdata.fr</a>.</p>
<p>En espérant vous voir bientôt dans nos salles de formation !</p>
<p>A+. David B.<strong>Continuez votre lecture sur le blog :</strong>
<ul class="similar-posts">
<li><a href="http://blog.capdata.fr/index.php/formation-optimisation-de-requetes/" rel="bookmark" title="22 septembre 2010">Formation Optimisation de requêtes</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/abonnez-vous-au-blog-de-la-capdata-team/" rel="bookmark" title="23 juin 2010">Abonnez-vous au blog de la CapData team !</a> (Cédric PEINTRE) [GénéralMySQLOracleSQL ServerSybase]</li>
<li><a href="http://blog.capdata.fr/index.php/interet-de-creer-des-indexes-cluster-uniques/" rel="bookmark" title="16 mars 2010">Intérêt de créer des indexes cluster uniques</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/scruter-les-journaux-devenements-windows-avec-logparser/" rel="bookmark" title="12 mars 2010">Scruter les journaux d&#8217;évènements Windows avec LogParser</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/cours-mcm-en-ligne/" rel="bookmark" title="7 janvier 2011">Cours MCM en ligne</a> (David BAFFALEUF) [SQL Server]</li>
</ul>
<p><!-- Similar Posts took 3.782 ms -->
<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fcertification-sql-server-70-432-offerte%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fcertification-sql-server-70-432-offerte%2F&amp;style=normal&amp;b=2" height="61" width="50" /><br />
			</a>
		</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.capdata.fr/index.php/certification-sql-server-70-432-offerte/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Cours MCM en ligne</title>
		<link>http://blog.capdata.fr/index.php/cours-mcm-en-ligne/</link>
		<comments>http://blog.capdata.fr/index.php/cours-mcm-en-ligne/#comments</comments>
		<pubDate>Fri, 07 Jan 2011 14:24:51 +0000</pubDate>
		<dc:creator>David BAFFALEUF</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[MCM]]></category>

		<guid isPermaLink="false">http://blog.capdata.fr/?p=1860</guid>
		<description><![CDATA[Un petit mot rapide (beaucoup de travail en ce moment) pour signaler que tous les cours d&#8217;entraînement au MCM dispensés par SQLSkills sont disponibles en streaming et au téléchargement:  http://www.sqlskills.com/T_MCMVideos.asp
Encore une mine d&#8217;or à ciel ouvert, merci SQLSkills !
A+. David B.Continuez votre lecture sur le blog :

Intérêt de créer des indexes cluster uniques (David BAFFALEUF) [...]]]></description>
			<content:encoded><![CDATA[<p>Un petit mot rapide (beaucoup de travail en ce moment) pour signaler que tous les cours d&#8217;entraînement au MCM dispensés par <a href="http://www.sqlskills.com">SQLSkills</a> sont disponibles en streaming et au téléchargement:  <a href="http://www.sqlskills.com/T_MCMVideos.asp">http://www.sqlskills.com/T_MCMVideos.asp</a></p>
<p>Encore une mine d&#8217;or à ciel ouvert, merci SQLSkills !</p>
<p>A+. David B.<strong>Continuez votre lecture sur le blog :</strong>
<ul class="similar-posts">
<li><a href="http://blog.capdata.fr/index.php/interet-de-creer-des-indexes-cluster-uniques/" rel="bookmark" title="16 mars 2010">Intérêt de créer des indexes cluster uniques</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/certification-sql-server-70-432-offerte/" rel="bookmark" title="25 février 2011">Voucher certification SQL Server 70-432 offert !</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/reunion-guss-le-1er-juillet/" rel="bookmark" title="23 juin 2010">Réunion GUSS le 1er juillet !</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/scruter-les-journaux-devenements-windows-avec-logparser/" rel="bookmark" title="12 mars 2010">Scruter les journaux d&#8217;évènements Windows avec LogParser</a> (David BAFFALEUF) [SQL Server]</li>
<li><a href="http://blog.capdata.fr/index.php/replication-sql-server-retrouver-la-vilaine-transaction-en-echec/" rel="bookmark" title="21 septembre 2009">Retrouver une transaction en échec</a> (David BAFFALEUF) [SQL Server]</li>
</ul>
<p><!-- Similar Posts took 3.703 ms -->
<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fcours-mcm-en-ligne%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fcours-mcm-en-ligne%2F&amp;style=normal&amp;b=2" height="61" width="50" /><br />
			</a>
		</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.capdata.fr/index.php/cours-mcm-en-ligne/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Récupérer l&#8217;espace consommé par le versionning de lignes</title>
		<link>http://blog.capdata.fr/index.php/recuperer-lespace-consomme-par-le-versionning-de-ligne/</link>
		<comments>http://blog.capdata.fr/index.php/recuperer-lespace-consomme-par-le-versionning-de-ligne/#comments</comments>
		<pubDate>Thu, 09 Dec 2010 17:04:10 +0000</pubDate>
		<dc:creator>David BAFFALEUF</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[snapshot isolation]]></category>

		<guid isPermaLink="false">http://blog.capdata.fr/?p=1790</guid>
		<description><![CDATA[Pour cet article, je pars du principe que vous êtes familier du mode d&#8217;isolation SNAPSHOT ou READ_COMMITTED_SNAPSHOT (RCSI) qui permettent à SQL Server de se rapprocher du mode READ_COMMITTED par défaut sous Oracle ou InnoDB. Pour plus d&#8217;informations, cf la page MSDN concernant ces modes: http://msdn.microsoft.com/en-us/library/tcbchxcb%28v=vs.80%29.aspx
Lorsque SNAPSHOT_ISOLATION ou READ_COMMITTED_SNAPSHOT est activé sur une base, les [...]]]></description>
			<content:encoded><![CDATA[<p>Pour cet article, je pars du principe que vous êtes familier du mode d&#8217;isolation SNAPSHOT ou READ_COMMITTED_SNAPSHOT (RCSI) qui permettent à SQL Server de se rapprocher du mode READ_COMMITTED par défaut sous Oracle ou InnoDB. Pour plus d&#8217;informations, cf la page MSDN concernant ces modes: <a href="http://msdn.microsoft.com/en-us/library/tcbchxcb%28v=vs.80%29.aspx">http://msdn.microsoft.com/en-us/library/tcbchxcb%28v=vs.80%29.aspx</a></p>
<p>Lorsque SNAPSHOT_ISOLATION ou READ_COMMITTED_SNAPSHOT est activé sur une base, les UPDATES / DELETES vont pousser les lignes originales dans le version store. Donc un des inconvénients d&#8217;utiliser ce mécanisme est la pression qui s&#8217;ajoute sur tempdb.</p>
<p>Un autre impact moins connu est l&#8217;accroissement de l&#8217;espace utilisé dans les lignes modifiées: 14 octets de plus vont être utilisés par ligne, 6 pour pour stocker l&#8217;identifiant de la transaction qui a modifié la ligne, et 8 pour stocker le RID de la copie dans le version store. On essaie de s&#8217;intéresser au nombre de pages avant et après modification en RCSI, et au nombre de page splits potentiels qui peuvent survenir lorsque l&#8217;on modifie des données en masse dans une base en mode RCSI.</p>
<p>On créé une table clusterisée d&#8217;une seule page que l&#8217;on va remplir au maximum. A tâtons on arrive à voir que 250 lignes permettent de remplir la table au maximum de la capacité d&#8217;une page:</p>
<pre><span style="color: #0000ff;">create database testRCSI
GO
use testRCSI
GO
create table RCSI(a numeric identity, b varchar(10))
GO
insert into RCSI values (replicate('b',10))
GO 250
create unique clustered index PK_RCSI on RCSI(a)
GO
dbcc ind('testRCSI','RCSI',-1)
GO</span>

<span style="color: #008000;">/*
PageFID PagePID     IAMFID IAMPID      ObjectID    IndexID     PartitionNumber (...)
------- ----------- ------ ----------- ----------- ----------- --------------- (...)
1       114         NULL   NULL        2073058421  1           1               (...)
1       109         1      114         2073058421  1           1               (...)

PartitionID          iam_chain_type       PageType IndexLevel NextPageFID (...)
-------------- -------------------- -------------------- -------- --------(...)
72057594038386688    In-row data          10       NULL       0           (...)
72057594038386688    In-row data          1        0          0           (...)</span></pre>
<p><span style="color: #008000;">*/</span></p>
<pre><span style="color: #0000ff;">select * from sys.dm_db_index_physical_stats(11,2073058421,1,NULL, 'DETAILED')
where Index_Level=0
GO</span></pre>
<pre><span style="color: #008000;">/* <span style="color: #008000;">La sortie est simplifiée pour plus de lisibilité</span>
<span style="color: #008000;">avg_page_space_used_in_percent=</span></span><span style="color: #008000;">98,8139362490734
max_record_size_in_bytes=30</span><span style="color: #008000;"><span style="color: #008000;">
</span>*/</span></pre>
<p>Donc nous savons que notre table est stockée sur la page (1:109), que la page est dense à 98,8% et que la taille max d&#8217;une ligne est 30 octets. On passe la base en mode RCSI et on note la valeur de Page Splits courante.</p>
<pre><span style="color: #008000;"><span style="color: #0000ff;">alter database testRCSI set read_committed_snapshot on
GO
select cntr_value from sys.dm_os_performance_counters where counter_name like 'Page Splits%'
GO</span>
/* cntr_value
--------------------
4013
*/</span></pre>
<p>Ensuite, on va mettre à jour les lignes par une valeur de dimension égale (on remplace le caractère &#8216;b&#8217; par un &#8216;c&#8217; par exemple), et on s&#8217;intéresse en premier lieu à la nouvelle taille max de chaque ligne:</p>
<pre><span style="color: #0000ff;">update RCSI set b = (replicate('c',10))
GO
select * from sys.dm_db_index_physical_stats(11,2073058421,1,NULL, 'DETAILED')
where Index_Level=0
GO</span><span style="color: #008000;">
 /* La sortie est simplifiée pour plus de lisibilité
 avg_page_space_used_in_percent=</span><span style="color: #008000;">71,0155670867309 </span>
<span style="color: #008000;"> max_record_size_in_bytes=44
 */</span></pre>
<p>On voit que chaque ligne a bien grossi de 14 octets. Le version store quant à lui a bien stocké les 250 lignes modifiées (il faut le faire dans la minute qui suit le commit sinon le version store est recyclé):</p>
<pre><span style="color: #0000ff;">select version_store_reserved_page_count from sys.dm_db_file_space_usage
GO
<span style="color: #008000;">/*
version_store_reserved_page_count
----------------------------------------------
16
*/</span></span></pre>
<p>Si on regarde le plan d&#8217;allocation de notre table mise à jour:</p>
<pre><span style="color: #008000;"><span style="color: #0000ff;">dbcc ind('testRCSI','RCSI',-1)
GO</span></span></pre>
<pre><span style="color: #008000;">/*
PageFID PagePID     IAMFID IAMPID      ObjectID    IndexID     PartitionNumber
------- ----------- ------ ----------- ----------- ----------- ---------------
1       114         NULL   NULL        2073058421  1           1              
1       109         1      114         2073058421  1           1              
1       41          1      114         2073058421  1           1              
1       73          1      114         2073058421  1           1              
</span></pre>
<pre><span style="color: #008000;">PartitionID          iam_chain_type       PageType IndexLevel (...)
-------------------- -------------------- -------- ---------- (...)
72057594038386688    In-row data          10       NULL       (...)
72057594038386688    In-row data          1        0          (...)
72057594038386688    In-row data          2        1          (...)
72057594038386688    In-row data          1        0          (...)</span></pre>
<p><span style="color: #008000;">*/</span></p>
<p>Une page root a été ajoutée pour l&#8217;index clusterisé (PageType=2) plus une nouvelle page leaf (PageType=1). On n&#8217;est plus sur une seule page de données mais sur deux maitenant pourtant pour la même quantité de données, ce qui signifie qu&#8217;un page split est intervenu pour couper notre page initiale en deux.  Un rapide coup d&#8217;oeil au compteur perfmon va nous le confirmer (je précise que l&#8217;instance est idle):</p>
<pre><span style="color: #0000ff;">select cntr_value from sys.dm_os_performance_counters where counter_name like 'Page Splits%'
GO
</span><span style="color: #008000;">/*
cntr_value
--------------------
4015
*/
</span></pre>
<p>Pour récupérer cet espace, reconstruire l&#8217;index cluster devrait suffire:</p>
<pre><span style="color: #0000ff;">alter index PK_RCSI on RCSI rebuild
GO</span><span style="color: #0000ff;"><span style="color: #0000ff;">
</span>dbcc ind('testRCSI','RCSI',-1)
GO</span></pre>
<pre><span style="color: #008000;">/*
PageFID PagePID     IAMFID IAMPID      ObjectID    IndexID     PartitionNumber (...)
------- ----------- ------ ----------- ----------- ----------- --------------- (...)
1       89         NULL   NULL        2073058421  1           1               (...)
1       80         1      114         2073058421  1           1               (...)

PartitionID          iam_chain_type       PageType IndexLevel NextPageFID (...)
-------------- -------------------- -------------------- -------- --------(...)
72057594038386688    In-row data          10       NULL       0           (...)
72057594038386688    In-row data          1        0          0           (...)</span></pre>
<pre><span style="color: #008000;">*/</span></pre>
<p>L&#8217;objet a été déplacé et réorganisé en entier (les pages sont différentes). On retrouve notre page unique du début. Quant à la taille des lignes, elle est revenue à sa valeur nominale:</p>
<pre><span style="color: #0000ff;">select * from sys.dm_db_index_physical_stats(11,2073058421,1,NULL, 'DETAILED') where Index_Level=0
GO
<span style="color: #008000;">/* La sortie est simplifiée pour plus de lisibilité
avg_page_space_used_in_percent=98,8139362490734
max_record_size_in_bytes=30
*/</span></span></pre>
<p>Morale de l&#8217;histoire, attention aux page splits et à l&#8217;espace qu&#8217;occupent des lignes versionnées en mode SNAPSHOT_ISOLATION ou READ_COMMITTED_SNAPSHOT. Et reconstruire fréquemment les indexes sur ces tables permet de récupérer l&#8217;espace perdu.</p>
<p>A+. David B.<strong>Continuez votre lecture sur le blog :</strong>
<ul class="similar-posts">
<li><a href="http://blog.capdata.fr/index.php/se-connecter-a-sql-server-a-travers-oracle-quelle-drale-didae/" rel="bookmark" title="12 juin 2008">Se connecter à SQL Server à travers Oracle, quelle drôle d&#8217;idée ?</a> (Thierry GASCARD) [Oracle]</li>
<li><a href="http://blog.capdata.fr/index.php/installation-oracle-11gr2-64-bits-sur-red-hat-5-partie-1/" rel="bookmark" title="3 mars 2010">Installation Oracle 11gR2 64 bits sur Red Hat 5 Partie 1</a> (Thierry GASCARD) [Oracle]</li>
<li><a href="http://blog.capdata.fr/index.php/installation-oracle-64-bits-sur-red-hat-5/" rel="bookmark" title="5 juin 2009">Installation Oracle 64 bits sur Red Hat 5</a> (Thierry GASCARD) [Oracle]</li>
<li><a href="http://blog.capdata.fr/index.php/creation-et-utilisation-docfs2/" rel="bookmark" title="5 juin 2009">Création et utilisation d&#8217;OCFS2</a> (Thierry GASCARD) [Oracle]</li>
<li><a href="http://blog.capdata.fr/index.php/utiliser-asmcmd/" rel="bookmark" title="5 juin 2009">Utiliser ASMCMD</a> (Thierry GASCARD) [Oracle]</li>
</ul>
<p><!-- Similar Posts took 3.764 ms -->
<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Frecuperer-lespace-consomme-par-le-versionning-de-ligne%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fblog.capdata.fr%2Findex.php%2Frecuperer-lespace-consomme-par-le-versionning-de-ligne%2F&amp;style=normal&amp;b=2" height="61" width="50" /><br />
			</a>
		</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.capdata.fr/index.php/recuperer-lespace-consomme-par-le-versionning-de-ligne/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

