<?xml version="1.0" encoding="ISO-8859-1"?>

<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
	<channel>
		<title>Forum du club des développeurs et IT Pro - Blogs - SQLpro</title>
		<link>https://www.developpez.net/forums/blogs/3170-sqlpro/</link>
		<description>Developpez.com, le Club des Développeurs et IT Pro</description>
		<language>fr</language>
		<lastBuildDate>Tue, 14 Apr 2026 15:33:31 GMT</lastBuildDate>
		<generator>vBulletin</generator>
		<ttl>15</ttl>
		<image>
			<url>https://forum.developpez.be/images/misc/rss.jpg</url>
			<title>Forum du club des développeurs et IT Pro - Blogs - SQLpro</title>
			<link>https://www.developpez.net/forums/blogs/3170-sqlpro/</link>
		</image>
		<item>
			<title>La haute disponibilité des données, un comparatif SGBD PostGreSQL versus Microsoft SQL Server</title>
			<link>https://www.developpez.net/forums/blogs/3170-sqlpro/b10619/haute-disponibilite-donnees-comparatif-sgbd-postgresql-versus-microsoft-sql-server/</link>
			<pubDate>Fri, 12 Jul 2024 11:21:42 GMT</pubDate>
			<description>*Introduction* 
 
La haute...</description>
			<content:encoded><![CDATA[<blockquote class="blogcontent restore"><b><font size="4">Introduction</font></b><br />
<br />
La haute disponibilité des données est un facteur fondamental dans l’élaboration d’une architecture de bases de données, notamment pour des données sensibles dont la disponibilité doit être continue. C’est le cas pour les applications du monde de l’hôpital, les services de secours ou encore la régulation des transports.<br />
<br />
Cet article à pour but de comparer les solutions de deux systèmes de base de données relationnelles PostGreSQL et Microsoft SQL Server.<br />
<br />
<div style="text-align: center;"><img src="https://www.developpez.net/forums/attachment.php?attachmentid=657076&amp;d=1720782126" border="0" alt="Nom : BANDEAU-pont-768x171.jpg
Affichages : 17786
Taille : 31,2 Ko"  style="float: CONFIG" /></div><br />
<b><font size="4">1 – Qu’est-ce que la haute disponibilité&nbsp;?</font></b><br />
<br />
La haute disponibilité - en anglais <i>high availability (HA)</i> - consiste à mettre en œuvre une architecture physique et logique pour redonder les données des bases de données (aspect physique) et faire en sorte que le système bascule d’une unité à l’autre (aspect logique) de la manière la plus rapide et la plus transparente au regard des besoins d’accès aux données.<br />
<br />
<b><font size="4">2 – Qu’est-ce que le taux de disponibilité&nbsp;?</font></b><br />
<br />
C’est une mesure communément admise, calculée en pourcentage du temps où le système est indisponible par rapport au  temps passé. Par exemple un système qui serait en moyenne indisponible quelques jours par an, se verrait alors attribuer un taux de disponibilité compris entre 95&nbsp;% (18 jours / an) et 99&nbsp;% (3 jours / an).<br />
<br />
Aujourd’hui, il est courant de viser un taux de disponibilité de l’ordre de 99,99 à 99,999&nbsp;% du temps, soit une limite d’indisponibilité de 9,75 heures à 5 minutes et 30 secondes…<br />
<br />
<b><font size="4">3 – Différence entre haute disponibilité, PRA et PCA</font></b><br />
<br />
Un Plan de Reprise d’Activité, <b>PRA</b>, vise à rétablir le système d’information de l’entreprise au plus vite en cas de sinistre majeur (incendie, inondation…).<br />
<br />
Un Plan de Continuité des Activités <b>PCA </b>vise la poursuite des activités de l’entreprise face à un incident dont la gravité est circonscrite (panne machine, erreur logique…).<br />
<br />
Les mécanismes spécifiques introduits dans les bases de données permettent aussi bien d’assurer le PCA ou le PRA pour l’unique périmètre des bases de données, ce qui constitue, il ne faut pas l’oublier, le principal capital économique de l’entreprise.<br />
<br />
En règle générale, les mécanismes intégrés de haute disponibilité permettent d’atteindre un PCA sans perte de données, grâce à un mode synchrone pour lequel la distance entre les données répliquées doit être relativement faible (LAN) ou bien à longue distance (WAN) via des réseaux à très haut débit extrêmement résilients.<br />
<br />
Dans le cas du PRA, une réplication asynchrone est préférable, car les machines se trouvent généralement à grande distance afin de ne pas être affectées par un sinistre de grande étendue (inondation, tremblement de terre, panne du réseau public de distribution d’électricité…), le mode asynchrone étant plus performant du point de vue du service des données, mais induisant une perte potentielle des données…<br />
<br />
<b><font size="4">4 – Architecture technique</font></b><br />
<br />
<b><font size="3">4.1 – Le principe</font></b><br />
<br />
Une première instance du serveur de bases de données concentre les bases actives dites «&nbsp;primaires&nbsp;» et un mécanisme, généralement basé sur les transactions, propage aux nœuds passifs (généralement appelés esclaves), c’est-à-dire aux autres instances de serveurs de bases de données, les informations nécessaires à reconstruire les données qui ont été modifiées dans les différentes bases.<br />
<br />
Une fonctionnalité complémentaire surveille la disponibilité des machines, et en cas de problème, alerte le DBA sur la défaillance ou bascule directement sur l’un des nœuds de secours.<br />
<br />
<b><font size="3">4.2 – L’implémentation</font></b><br />
<br />
Nous n’allons pas recopier bêtement les aides en lignes officielles et les exemples documentés sur Internet, mais en voici les liens&nbsp;:<br />
<br />
<ul><li style=""><a href="https://docs.postgresql.fr/12/high-availability.html" target="_blank">Pour PostGreSQL</a></li><li style=""><a href="https://learn.microsoft.com/fr-fr/sql/sql-server/failover-clusters/windows/windows-server-failover-clustering-wsfc-with-sql-server?view=sql-server-ver16" target="_blank">Pour Microsoft SQL Server</a></li></ul><br />
<br />
<b><font size="4">5 – Différences entre les deux approches</font></b><br />
<br />
Parlons maintenant de ce qui différencie PostGreSQL de SQL Server en matière de haute disponibilité…<br />
<br />
<b><font size="3">5.1 – Quelles bases sont concernées&nbsp;?</font></b><br />
<br />
Dans PostGreSQL du fait qu’il n’existe qu’un seul journal de transactions, toutes les bases, sans exception, doivent être répliquées de manière physique d’un nœud à l’autre…<br />
<br />
Au contraire dans SQL Server, chaque base de données dispose de son propre journal de transactions. On peut alors répliquer certaines bases d’un nœud A vers un nœud B et d’autres du nœud A vers un nœud C et enfin d’autres peuvent ne pas être répliquées du tout. Pour simplifier la chose, SQL Server propose de regrouper les bases de données dans des groupes de disponibilité afin que les opérations mutuelles soient conjointes aux bases d’un même groupe…<br />
<br />
<b><font size="3">5.2 – Toutes les opérations sont-elles répliquées&nbsp;?</font></b><br />
<br />
Dans PostGreSQL, la réplication est arrêtée dès qu’une commande propage l’exécution d’instructions au niveau externe (par exemple la création d’un «&nbsp;storage&nbsp;» <span style="font-family: monospace; padding: 2px; background: #ddd; display: inline-block"><span style="color: #0000ff;">CREATE</span> TABLESPACE</span>… qui induit la création d’un répertoire). Il faut donc effectuer une reprise en reconstruisant l’ensemble de la réplication, car malheureusement et toujours à cause de l’unique journal de transactions, cette reconstruction doit concerner toutes les bases, même si une seule est en cause.<br />
<br />
Ceci ne pose aucun problème à SQL Server, à condition que le chemin dans lequel sera créé le nouvel espace de stockage existe dans les différents nœuds et le nouveau répertoire sera créé par SQL Server.<br />
<br />
<b><font size="3">5.3 – Synchrone&nbsp;?</font></b><br />
<br />
Par défaut asynchrone dans PostGreSQL, le mécanisme permet un mode «&nbsp;synchrone&nbsp;» qui peut s’avérer désastreux en production, car il tire les performances vers le bas&nbsp;: le nœud actif n’envoie la transaction qu’après quelle a été finalisée sur le «&nbsp;master&nbsp;» et le système attend la confirmation de l'unique nœud passif pour continuer, ce qui peut prendre de nombreuses minutes pour des transactions longues.<br />
<br />
Au contraire, SQL Server agit en amont au démarrage de la transaction&nbsp;: les différentes transactions sont propagées en parallèle, immédiatement sur tous les nœuds, y compris le primaire. Il en résulte que, si les machines sont équilibrées, le délai n’est que celui du transit réseau dont le seuil d’alerte est de l’ordre de 15 ms, au-delà duquel SQL Server passe transitoirement en mode asynchrone pour éviter les blocages (phase de rattrapage).<br />
<br />
<b><font size="3">5.4 – Combien de nœuds&nbsp;?</font></b><br />
<br />
Le mode synchrone de PostGreSQL est toujours limité à deux nœuds, les autres étant asynchrones, car le mécanisme de réplication est en cascade dans PostGreSQL. Par exemple, à 3 nœuds (A, B, C), les deux premiers (A et B, le premier A étant actif et le second B passif) pourront être synchrones, mais pas le 3<sup>e</sup>, car la réplication se fait d’abord du nœud A vers le B puis, lorsque le nœud B a finalisé ses transactions, le nœud C les reçoit de B…<br />
<br />
Dans SQL Server, tous les nœuds reçoivent les transactions à répliquer en parallèle, qu’ils soient en mode synchrone (dans la limite de 5 nœuds synchrones) ou asynchrone (dans la limite de 8 nœuds)…<br />
<br />
<b><font size="3">5.5 – Quid du quorum&nbsp;?</font></b><br />
<br />
Le quorum est un algorithme dans lequel un élément interne ou externe agit en tant que témoin pour assurer une majorité de votes afin de déclencher le basculement. Par exemple, avec une solution de réplication à deux nœuds, il faut un mécanisme tiers pour décider quel nœud défaillant isoler et faire en sorte que le nœud résistant prenne le rôle de master si ce n’est pas le cas. L’imparité des votes devant être la règle pour décider de comment traiter le problème.<br />
<br />
PostGreSQL ne disposant pas d’un mécanisme de quorum indépendant et au niveau système, il faut impérativement au moins 3 nœuds, c’est-à-dire trois instances de PostGreSQL pour pouvoir prétendre à un basculement automatique en mode synchrone.<br />
<br />
En comparaison, SQL Server utilise le cluster Windows et un quorum (disque ou partage de fichier) pour assurer le vote majoritaire qui décide du basculement.<br />
<br />
<b><font size="3">5.6 – En combien de temps le système bascule-t-il&nbsp;?</font></b><br />
<br />
Bien que PostgreSQL propose un basculement automatique, celui-ci met beaucoup de temps en mode synchrone (30 secondes environ).<br />
<br />
SQL Server en comparaison ne met que quelques millisecondes…<br />
<br />
<b><font size="3">5.7 – Les applications se reconnectent-elles sans problème&nbsp;?</font></b><br />
<br />
PostGreSQL ne dispose pas de manière interne de la notion de «&nbsp;listener&nbsp;» qui permet à toute application de ne jamais être coupée de la base opérationnelle, quel que soit le nœud actif (le listener étant constitué dans SQL Server d’une adresse IP de redirection vers le nœud actif). Compte tenu de ceci, il faudra donc modifier les chaines de connexion des applicatifs pour que le service des données fonctionne de nouveau.<br />
On comprend donc que, si le rétablissement de la disponibilité des bases peut être très rapide en cas de sinistre dans PostGreSQL, il n’en est pas de même pour les applicatifs, car il faudra agir manuellement&nbsp;!<br />
<br />
Dans SQL Server, chaque groupe de disponibilité rassemblant différentes bases est généralement doté d’un listener qui redirige le flux des requêtes applicatives sur le serveur actif de manière totalement transparente du point de vue des applications. Il n’y a donc aucune action à entreprendre au niveau des applications pour que celles-ci continuent d’accéder aux données de la base active en cas de basculement automatique.<br />
<br />
<b><font size="3">5.8 – Quel volume transite sur le réseau&nbsp;?</font></b><br />
<br />
PostGreSQL ne disposant que d’un seul journal de transactions commun à toutes les bases de données, si la réplication n’a d'intérêt que pour certaines bases, le volume des communications entre nœuds est pollué par des informations inutiles qui obèrent les ressources.<br />
<br />
En comparaison, dans SQL Server, chaque base de données possède son propre journal de transactions ce qui minimise le volume du transit. De plus, SQL Server pratique la compression des données des tables et index ce qui permet de diminuer encore plus le volume du transit. La compression des données étant une fonctionnalité inconnue de PostGreSQL [1].<br />
<br />
De surcroit, les informations transmises sont elles aussi compressées avant d'être envoyées sur le réseau, en particulier si elles ne le sont déjà pas au niveau des tables et des index...<br />
<br />
<b><font size="3">5.9 – La réplication peut-elle assurer la répartition de charge&nbsp;?</font></b><br />
<br />
Là, encore une fois, le mauvais choix d’une architecture avec un seul journal de transactions pour toutes les bases de données de PostGreSQL empêche de panacher les différents nœuds en ayant une partie des bases actives sur le nœud A et l’autre sur le nœud B…<br />
Avec PostGreSQL vous aurez donc toujours un nœud dont toutes les bases sont actives et sur l’autre toutes passives avec l’étrange impression que le serveur accueillant toutes les bases passives dispose de ressources presque toutes totalement inexploitées.<br />
<br />
Ceci n’est pas le cas dans SQL Server, car grâce au concept de Groupe de Disponibilité, vous pouvez par exemple, enrôler 50&nbsp;% de vos bases dans un groupe et le reste dans l’autre, le groupe 1 étant actif sur le nœud A et le groupe 2 actif sur le nœud B. Ceci améliore grandement les performances globales du service des données, ou encore, permet de choisir des serveurs moins «&nbsp;costauds&nbsp;» au niveau des ressources afin d’économiser sur le matériel et les licences…<br />
<br />
<b><font size="3">5.10 – Quel est le coût des licences&nbsp;?</font></b><br />
<br />
Nous savons tous que PostGreSQL est un outil gratuit… Mais dans une certaine mesure&nbsp;! En effet, plusieurs entreprises proposent des versions payantes de PostGreSQL (Enterprise DB, Fujitsu, Citus…) dont le coût est loin d’être négligeable et qui deviennent vite indispensables dès que la volumétrie augmente ou que l’on a besoin de telle ou telle fonctionnalité manquante dans la version «&nbsp;libre&nbsp;» de PostGreSQL…  N’oublions pas que les développeurs de PostGreSQL ont eux aussi besoin de manger et que bon nombre d’entre eux sont salariés de la société Enterprise DB qui bride sciemment les fonctionnalités de PostGreSQL pour permettre de vendre leurs produits…<br />
<br />
Cette avancée masquée n’est pas le mode de fonctionnement de Microsoft dont les coûts sont les plus bas des SGBDR d’entreprise. L’édition Standard limitée à 24 cœurs physiques (soit 48 logiques) et 192 Go de cache (RAM&nbsp;: 128 tables et index relationnels + 32 tables in memory + 32 index columnstore… deux fonctionnalités qui n’existent pas dans PostGreSQL) coute un peu moins de 2000&nbsp;€ par cœur physique auxquels il faut ajouter la Software Insurance (nécessaire pour la haute disponibilité AlwaysOn, 700&nbsp;€ par cœur physique et par an) ce qui donne droit à la version future gratuitement.<br />
Autre gratuité, celle des instances passives… Microsoft ne fait jamais payer les licences SQL Server des machines passives. Vous n’aurez donc rien à payer en licence pour le second nœud par exemple.<br />
<br />
Ainsi, pour une machine à 16 cœurs logiques, avec un amortissement sur 5 ans, pour lequel vous serez passé par deux à trois versions de SQL Server (2017, 2019, 2022… par exemple), le budget mensuel sera donc de moins de 1000&nbsp;€, soit un peu moins que le TJM de 2 journées d’un développeur…<br />
J’oubliais le prix des licences Windows… environ 1000&nbsp;€ pour 16 cœurs physiques. En amortissement sur 5 ans, cela représente donc 5&nbsp;€ par mois…<br />
Cher non&nbsp;?<br />
<br />
<b><font size="3">5.11 – Quels sont les outils pour faciliter la mise en œuvre et l’exploitation&nbsp;?</font></b><br />
<br />
PostGreSQL ne dispose d’aucun assistant pour la mise en place de cette réplication (il faut tout scripter) et encore moins de tableaux de bord permettant la surveillance de l’état du système, ni, bien entendu d’alertes intégrées pour être informé des dysfonctionnements… Ceci oblige à rajouter, à un outil de monitoring externe, de nombreuses requêtes tant au niveau de PostGreSQL que de l’OS afin de capturer les informations essentielles de la surveillance…<br />
Bref un travail complexe, couteux et casse-gueule, que seules quelques entreprises comme Dalibo, Enterprise DB, … maitrisent…<br />
<br />
En comparaisons, SQL Server dispose d’un assistant de mise en œuvre et de nombreux tableaux de bord pour la surveillance…<br />
<br />
<div style="text-align: center;"><img src="https://www.developpez.net/forums/attachment.php?attachmentid=657080&amp;d=1720782719" border="0" alt="Nom : Haute-dispo-assistant.jpg
Affichages : 4783
Taille : 73,9 Ko"  style="float: CONFIG" /><br />
<i>L’assistant de mise en œuvre de la haute disponibilité dans Microsoft SQL Server</i><br />
<br />
<img src="https://www.developpez.net/forums/attachment.php?attachmentid=657081&amp;d=1720782733" border="0" alt="Nom : Haute-dispo-tableau-de-bord.jpg
Affichages : 4752
Taille : 69,3 Ko"  style="float: CONFIG" /><br />
<i>Un des tableaux de bord de la haute disponibilité SQL Server AlwaysOn</i></div><br />
<b><font size="4">6 – En guide de conclusion</font></b><br />
<br />
Certains des problèmes évoqués ci-avant au sujet de PostGreSQL peuvent être contournés en ajoutant des outils complémentaires comme Barman, repmgr, Slony I, Pgpool II, Patroni, Pacemaker…<br />
Mais cela complexifie encore plus l’architecture et nécessite une administration complémentaire lourde, complexe et chère en exploitation et pour ce dernier point parce qu’il n’existe quasiment pas d’assistance de niveau professionnelle à contacter en cas de problème…<br />
<br />
Bref, la soi-disant économie réalisée du fait de la gratuité des licences PostGreSQL en comparaison de l’explosion du coût d’exploitation et du hardware complémentaire montre vite que SQL Server est plus que compétitif, et il restera toujours de nombreuses lacunes à PostGreSQL par rapport aux fonctionnalités incluses dans Microsoft SQL Server en matière de haute disponibilité…<br />
<br />
Pour information, les <a href="https://www.lemondeinformatique.fr/actualites/lire-adagio-un-outil-unique-pour-la-gestion-operationnelle-sapeurs-pompiers-de-paris-33708.html" target="_blank">pompiers de Paris</a> (BSPP) utilisent SQL Server pour la base de données des secours depuis 2005 d’abord avec le mirroring, puis depuis la version 2012 avec AlwaysOn…<br />
<br />
Également, le site web «&nbsp;leboncoin&nbsp;» avait mis en place une réplication pour ses 70 instances de PostGreSQL et avait perdu le mécanisme de haute disponibilité le 1er mars 2013 nécessitant 5 jours d’efforts pour le remettre en fonctionnement… Et Jean-Louis Bergamo (@JLB666 ça ne s’invente pas…) était fier de présenter ce désastre comme une réussite aux <a href="https://fr.slideshare.net/slideshow/pgday-fr-2014-presentation-de-postgres-chez-leboncoinfr/35584768" target="_blank">PGDays 2014</a>…<br />
Il est a noter que «&nbsp;leboncoin&nbsp;» utilise une seule instance MS SQL Server pour leur base analytique&nbsp;!<br />
<br />
 <br />
<br />
<i>[1]</i> la compression des données dans SQL Server concerne les données des tables et des index et s’opère a différents niveaux qui permettent d’économiser plus ou moins d’octets, mais elle n’affecte pas les lectures dont les performances sont améliorées grâce au gain de place en cache lié à cette compression.  Les techniques de compression étant spécifiques aux SGBDR. Dans SQL Server, ces algorithmes consistent en deux familles&nbsp;: l’élimination des données non significatives d’une part (compression «&nbsp;ROW&nbsp;») et la réalisation de dictionnaires de racines d’autre part (compression de type «&nbsp;PAGE&nbsp;») dont on trouvera, pour cette dernière, quelques les détails techniques ici&nbsp;: Implémentation de la compression de page.<br />
<br />
<div style="text-align: center;"><img src="https://www.developpez.net/forums/attachment.php?attachmentid=657082&amp;d=1720783053" border="0" alt="Nom : Capture d’écran 2024-07-11 175940.png
Affichages : 4728
Taille : 37,0 Ko"  style="float: CONFIG" /><br />
<i><font color="#696969">Ce contenu a été publié avec comme mot(s)-clé(s) AlwaysOn, base de données, continuité, haute disponibilité, hot standby, PCA, PostGreSQL, PostGreSQL vs SQL Server, PRA, Replication, sql server.</font></i></div></blockquote>

]]></content:encoded>
			<dc:creator>SQLpro</dc:creator>
			<guid isPermaLink="true">https://www.developpez.net/forums/blogs/3170-sqlpro/b10619/haute-disponibilite-donnees-comparatif-sgbd-postgresql-versus-microsoft-sql-server/</guid>
		</item>
		<item>
			<title><![CDATA[Dangers du chiffrement (cryptage) des données d'une base de données MySQL/MariaDB]]></title>
			<link>https://www.developpez.net/forums/blogs/3170-sqlpro/b10593/dangers-chiffrement-cryptage-donnees-d-base-donnees-mysql-mariadb/</link>
			<pubDate>Tue, 26 Mar 2024 08:28:23 GMT</pubDate>
			<description>*Le chiffrement des données...</description>
			<content:encoded><![CDATA[<blockquote class="blogcontent restore"><b>Le chiffrement des données dans MySQL/MariaDB est trompeur, inefficace et même dangereux, dans les versions communautaires libres et même dans certaines autres distributions payantes. </b><br />
<br />
Voici pourquoi...<br />
<br />
En effet, bien qu'il existe quelques possibilités de chiffrement dans MySQL/MariaDB aucune n'est efficace ni pertinente...<br />
<br />
Dans les grands SGBDR <b>le chiffrement le plus employé est le &quot;<a href="https://en.wikipedia.org/wiki/Transparent_data_encryption" target="_blank">Transparent Data Encryption</a>&quot; aussi appelé TDE</b>, qui consiste à chiffrer le stockage des bases au niveau du disque. Les données restent claires en mémoire et ce n'est que lorsqu'il faut lire les données sur les disques ou les écrire que le chiffrement déchiffrement s'effectue. <b>Pour cela le SGBDR doit impérativement gérer directement son stockage sans passer par la couche système </b>(ce que font IBM DB2; Oracle Database ou Microsoft SQL Server). <br />
Or ce n'est pas le cas de MySQL/MariaDB qui délègue lectures et écritures disque à la couche OS....<br />
Bien que MySQL propose le chiffrement &quot;InnoDB Data-at-rest Encryption&quot; cela ne concerne que les tables innodb...<br />
Il y a bien un TDE dans l'édition Enterprise.... (payante...), mais le problème est que dans le TDE de l'édition Enterprise, les tables temporaires ne sont pas chiffrées, les sauvegardes (dump) non plus, et les performances sévèrement dégradées...<br />
<br />
Quant à <b>chiffrer certaines colonnes de certaines tables</b>, là aussi les méthodes mises en œuvre dans MySQL sont pauvres, inefficaces et inutiles !<br />
<b>Le chiffrement des mots de passe est basé sur l'algorithme SHA-1 (160 bits) non salé... </b><br />
Or <a href="https://www.cnil.fr/sites/cnil/files/atoms/files/recommandation-mots-de-passe_annexe2_tableau-correspondance.pdf" target="_blank">la CNIL indique que pour les données de santé</a> (un exemple parmi d'autres... idem dans le monde bancaire notamment) le hachage doit se faire avec un algorithme d'au moins 80 bits et <b>salé </b>! Ce que ne permet pas MySQL... <br />
C'est pourquoi il fait souvent l'objet de pénalités pour les clients qui utilisent des bases de données MySQL/MariaDB...<br />
À titre d'exemple, Microsoft SQL Server utilise un chiffrement de type SHA_512 (512 bits, donc trois fois plus lourd que MySQL/MariaDB) avec salage interne.<br />
<br />
<b><font color="#FF0000">NOTE </font></b>: <b>qu'est-ce que le salage du chiffrement ?</b><br />
Cela consiste à introduire une information relativement &quot;aléatoire&quot;, en complément de l'information que l'on veut chiffrer, afin que deux valeurs identiques une fois chiffrées avec le paramètre de salage ne donnent pas la même valeur de chiffrement afin de lutter contre l'attaque d'informations chiffrée par analyse fréquentielle. Par exemple le chiffrement de deux patronymes identiques &quot;DUPONT&quot; afférents à deux personnes physiques différentes devrait donner pour l'un une valeur binaire différente de l'autre. La figure ci-après donne un exemple de chiffrement dans Microsoft SQL Server ou l'on voit bien que le même patronyme est chiffré de deux manières différentes :<br />
<br />
<img src="https://www.developpez.net/forums/attachments/p652582d1711441247/c-cpp/c/reseau/librairies-standard-slang-h-curses-h/chiffrement-sale-sql-server.jpg/" border="0" alt="Nom : Chiffrement salé dans SQL Server.jpg
Affichages : 28072
Taille : 55,7 Ko"  style="float: CONFIG" /><br />
<br />
<b><font color="#FF0000">NOTE </font></b>: <b>qu'est-ce que l'analyse fréquentielle ?</b><br />
C'est une technique de cassage du chiffre par analyse de la fréquence d'apparition des mêmes données chiffrées pour toute ou partie d'une valeur chiffrée dans une collection de valeurs dont on sait que certaines valeurs peuvent être répétitives et dont on connait la loi de distribution, même de manière approximative. Par exemple le nom DUPONT est le 22e nom de famille le plus fréquent en France...<br />
<br />
<img src="https://www.developpez.net/forums/attachments/p652583d1711441247/c-cpp/c/reseau/librairies-standard-slang-h-curses-h/distribution-25-premiers-patronymes.jpg/" border="0" alt="Nom : Distribution des 25 premiers patronymes.jpg
Affichages : 9937
Taille : 82,3 Ko"  style="float: CONFIG" /><br />
<br />
Autre problématique, <b>le chiffrement des colonnes des tables n'existe pas en standard dans MySQL.</b> Il faut recourir à un plugin externe, qui limite le chiffrement à l'algorithme AES  en128, 192 ou 256 bits.<br />
Quatre inconvénients : (1) un module externe sensible que des personnes malveillantes peuvent modifier facilement ou supprimer et (2)qui impose d'utiliser le mot de passe en clair à un moment dans les applications, (3) la limitation de la clé de chiffrement et le (4) fait que les données chiffrées ne sont pas salées... <br />
Cette dernière problématique est extrêmement grave, car <b>le salage du chiffrement est indispensable dans les données des bases</b>. En effet, les bases de données portant d’importantes collections de données, une attaque par analyse fréquentielle des données chiffrées est aisé sur des données dont on connait la distribution statistique, comme les patronymes (voir <a href="https://www.geneanet.org/genealogie/" target="_blank">https://www.geneanet.org/genealogie/</a>), les prénoms (<a href="https://www.insee.fr/fr/statistiques/7633685?sommaire=7635552" target="_blank">https://www.insee.fr/fr/statistiques...mmaire=7635552</a>), les données comptables (loi de Benford), les numéros de sécurité sociale… <br />
De même, les applications devant chiffrer ou déchiffrer doivent à un moment avoir le mot de passe en clair.... Ce qui veut dire qu'il suffit d'avoir accès au code de l'application pour péter le chiffrement... C'est comme si vous fermiez votre voiture à clé en laissant les clés sur la porte !<br />
<br />
<b>Autrement dit le chiffrement dans MySQL ne sert à rien...</b><br />
<br />
À titre d'exemple, Microsoft SQL Server utilise un chiffrement interne systématiquement salé, par clé symétrique, asymétrique, certificat, mot de passe, et avec les algorithmes : RC2, RC4, RC4_128, DES, TRIPLE_DES, TRIPLE_DES_3KEY, DESX, AES (128, 192, 256), RSA (512, 1024, 2048, 3072, 4096)...<br />
De plus pour sécuriser le chiffrement, les clés sont protégées par une hiérarchie de clefs depuis la clé d’instance puis la clé de la base, qui protège les clés crées par SQL Server pour le chiffrement des données. Et pour qu'il ne soit pas nécessaire d'exposer la moindre clé dans les applications, le déchiffrement peut être automatisé sans jamais exposer le mot de passe (DECRYPTBYKEYAUTOASYMKEY, DECRYPTBYKEYAUTOCERT) ou encore par des vues de déchiffrement situées dans d’autres bases...<br />
Enfin, il est possible de déléguer la gestion des clés de ,SQL Server par un boitier électronique sécurisé (« Hardware Security Module » technologie appelé EKM : <a href="https://learn.microsoft.com/fr-fr/sql/relational-databases/security/encryption/extensible-key-management-ekm?view=sql-server-ver16" target="_blank">Extensible Key Management</a>) qui s'autodétruit en cas d'intrusion... Exemple <a href="https://cpl.thalesgroup.com/encryption/hardware-security-modules/network-hsms" target="_blank">Thales Luna</a><br />
<br />
Enfin, le fin du fin est d'utiliser le chiffrement de bout en bout qui laisse les données chiffrées dans la base et les véhicules chiffrées jusqu'à l'application qui peut les déchiffrer pour les afficher. Par exemple la technologie &quot;<a href="https://learn.microsoft.com/fr-fr/sql/relational-databases/security/encryption/always-encrypted-database-engine?view=sql-server-ver16" target="_blank">AlwaysEncrypted</a>&quot; de Microsoft SQL Server. De même le traitement des données chiffrées peut utiliser des zones de mémoire réservées, inaccessibles à d'autres processus, car la mémoire peut aussi être lue par des processus malveillants. On appelle cela des enclaves mémoire sécurisées et cela existe pour Microsoft SQL Server avec la technologie <a href="https://learn.microsoft.com/fr-fr/sql/relational-databases/security/encryption/always-encrypted-enclaves?view=sql-server-ver16" target="_blank">Secure Enclaves</a>...<br />
<br />
Tout cela n'existe pas dans MySQL, même dans la version Enterprise, par ce que MySQL/mariaDB n'est pas un SGBD d'entreprise, mais sert surtout à des CMS ou des blogs et n'a aucune vocation a traiter des données de santé ou des données financières... ou de quelconques données sensibles comme la paye...</blockquote>

]]></content:encoded>
			<dc:creator>SQLpro</dc:creator>
			<guid isPermaLink="true">https://www.developpez.net/forums/blogs/3170-sqlpro/b10593/dangers-chiffrement-cryptage-donnees-d-base-donnees-mysql-mariadb/</guid>
		</item>
		<item>
			<title>Souci suite à la Migration de bases de 2014 vers 2019</title>
			<link>https://www.developpez.net/forums/blogs/3170-sqlpro/b10569/souci-suite-migration-bases-2014-vers-2019/</link>
			<pubDate>Sat, 20 Jan 2024 09:18:23 GMT</pubDate>
			<description>---Citation (Envoyé par...</description>
			<content:encoded><![CDATA[<blockquote class="blogcontent restore"><div class="bbcode_container">
	<div class="bbcode_quote">
		<div class="quote_container">
			<div class="bbcode_quote_container"></div>
			
				<div class="bbcode_postedby">
					<img src="https://forum.developpez.be/images/misc/quote_icon.png" alt="Citation" /> Envoyé par <strong>ulmeen</strong>
					<a href="showthread.php?p=11994568#post11994568" rel="nofollow"><img class="inlineimg" src="https://forum.developpez.be/images/buttons/viewpost-right.png" alt="Voir le message" /></a>
				</div>
				<div class="message">Bonjour (et bonne année 2024)<br />
<br />
Je rencontre un souci suite à la migration de mon serveur SQL de 2014 à 2019<br />
<br />
J'ai une fonction scalaire fn_IdentiteNomPrenomCodeCEA qui renvoi une chaine<br />
<br />
<div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:36px;">SELECT dbo.fn_IdentiteNomPrenomCodeCEA(CodeUtil) as IdentiteCIInverse</pre>
</div>donne ceci sur mon serveur 2014<br />
NOM PRENOM BADGE<br />
<br />
Lancée sur mon serveur 2019, j’ai une erreur de requête<br />
Msg 208, Level 16, State 1, Procedure fn_IdentiteNomPrenomCodeCEA, Line 48<br />
Nom d'objet 'Annuaire.dbo.AnnuaireUnifie' non valide.<br />
<br />
La fonction regarde sur quel serveur elle est lancée pour choisir la base concernée<br />
<div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:108px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="26"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br />5<br />6<br /></div></td><td valign="top"><pre style="margin: 0">
       set @serveur=@@SERVERNAME
       set @Regime=(SELECT Regime FROM ParamServeurAppliSTIC.dbo.ServeursSQL WHERE NomServeur=@@servername)

       if(@Regime='Test') set @Retour= (SELECT Nom+' '+Prenom+' '+CodeCEA FROM TestAnnuaire.dbo.AnnuaireUnifie WHERE ConnectId=@Code)
       else set @Retour= (SELECT Nom+' '+Prenom+' '+CodeCEA FROM Annuaire.dbo.AnnuaireUnifie WHERE ConnectId=@Code)</pre></td></tr></table></pre>
</div>Ma variable @Regime=Test donc seule la 1° requête est sensée s'exécuter<br />
Mais il semble que la fonction veuille quand même interpréter la requête du else<br />
<br />
Ce type de test est très présent dans mes procédures stockées et fonctions diverses. Ce serait un gros souci pour moi si je devais l’abandonner.<br />
<br />
Avez-vous une idée ?<br />
<br />
Merci</div>
			
		</div>
	</div>
</div>En fait ce qui s'est passé a été prévu par SQL Server...<br />
<br />
Depuis la version 2017, Microsoft a beaucoup travaillé sur de nouvelles optimisations dans le cadre d'un projet appelé &quot;<a href="https://learn.microsoft.com/fr-fr/sql/relational-databases/performance/intelligent-query-processing?view=sql-server-ver16" target="_blank">Intelligent Query Processor</a>&quot; (IQP), dont l'une des fonctionnalité consiste à supprimer l'appel de la fonction (UDF) en l'intégrant directement dans la requête afin que le tout soit optimisé. Cela s'appelle<a href="https://learn.microsoft.com/fr-fr/sql/relational-databases/user-defined-functions/scalar-udf-inlining?view=sql-server-ver16" target="_blank"> l'inligning</a> (enlignement)...<br />
En effet, les requêtes ont un comportement <b>ensembliste </b>(toutes les opérations s'effectuent en même temps &lt;=&gt; parallélisme) alors qu'une fonction est toujours <b>itérative </b> (c'est à dire que le code se déroule séquentiellement) ce qui fait que l'introduction de fonctions dans le code d'une requête casse ordinairement le fonctionnement ensembliste de la requête et la rend itérative et par conséquent lente... Pour palier à ce problème de performance Microsoft Research a initié le projet &quot;<a href="https://www.microsoft.com/en-us/research/project/froid/" target="_blank">FROID</a>&quot; a eut pour but de récrire les requêtes en réintégrant le code SQL de la fonction dans la requête. . On obtient alors une requête parfaitement ensembliste et les performances n'ont plus rien à voir.... <br />
Cependant, ce mode a un inconvénient : il étudie toutes les possibilités d'exécution simultanément, alors que l'itération dans le code d'une UDF coupe des branches via les commande de contrôle telles que IF THEN ELSE...<br />
Sachant cela, il y a quelques rares cas ou cet &quot;enlignement&quot; de la fonction peut devenir un problème et c'est votre cas... En effet, une requête est compilée d'un seul bloc et l'absence de certains objets à ce moment peut se révéler critique...<br />
Pour palier à cet effet de bord, Microsoft SQL Server a rajouté une option dans la création des fonctions (<a href="https://learn.microsoft.com/fr-fr/sql/t-sql/statements/create-function-transact-sql?view=sql-server-ver16" target="_blank">INLINE = OFF</a>) afin d'interdire l'enlignement d'une UDF au cas ou cela produirait une anomalie...<br />
<br />
Vous êtes tombé dessus !<br />
<br />
A +</blockquote>

]]></content:encoded>
			<dc:creator>SQLpro</dc:creator>
			<guid isPermaLink="true">https://www.developpez.net/forums/blogs/3170-sqlpro/b10569/souci-suite-migration-bases-2014-vers-2019/</guid>
		</item>
		<item>
			<title><![CDATA[Explication d'un double left join ?]]></title>
			<link>https://www.developpez.net/forums/blogs/3170-sqlpro/b10542/explication-d-double-left-join/</link>
			<pubDate>Tue, 24 Oct 2023 17:09:34 GMT</pubDate>
			<description>---Citation (Envoyé par...</description>
			<content:encoded><![CDATA[<blockquote class="blogcontent restore"><div class="bbcode_container">
	<div class="bbcode_quote">
		<div class="quote_container">
			<div class="bbcode_quote_container"></div>
			
				<div class="bbcode_postedby">
					<img src="https://forum.developpez.be/images/misc/quote_icon.png" alt="Citation" /> Envoyé par <strong>SQLpro</strong>
					<a href="showthread.php?p=11980062#post11980062" rel="nofollow"><img class="inlineimg" src="https://forum.developpez.be/images/buttons/viewpost-right.png" alt="Voir le message" /></a>
				</div>
				<div class="message">Soit les tables suivantes :<br />
<br />
<div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:120px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="26"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br />5<br />6<br />7<br /></div></td><td valign="top"><pre style="margin: 0">CREATE TABLE T_PERSONNE 
   (PRS_ID     INT PRIMARY KEY, 
    PRS_NOM    VARCHAR(16) NOT NULL);
INSERT INTO T_PERSONNE VALUES
   (1, 'DUPONT'), 
   (2, 'MARTIN'), 
   (3, 'SMITH');</pre></td></tr></table></pre>
</div>Table des personnes<br />
<br />
<div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:132px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="26"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br /></div></td><td valign="top"><pre style="margin: 0">CREATE TABLE T_DNS 
   (DNS_ID     INT PRIMARY KEY, 
    DNS_NAME   VARCHAR(128) NOT NULL);
INSERT INTO T_DNS VALUES 
   (99, 'gmail.com'), 
   (98, 'outlook.fr'), 
   (97, 'free.fr'), 
   (96, 'orange.fr');</pre></td></tr></table></pre>
</div>Table des DNS<br />
<br />
<div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:144px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="26"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br /></div></td><td valign="top"><pre style="margin: 0">CREATE TABLE T_EMAIL 
   (EML_ID     INT PRIMARY KEY, 
    PRS_ID     INT NOT NULL REFERENCES T_PERSONNE (PRS_ID), 
    DNS_ID     INT NOT NULL REFERENCES T_DNS (DNS_ID),
    EML_USER   VARCHAR(128) NOT NULL);
INSERT INTO T_EMAIL VALUES 
   (33, 1, 99, 'paul.dupont356'), 
   (35, 1, 98, 'pdupont45'), 
   (37, 3, 99, 'smith-et-wesson');</pre></td></tr></table></pre>
</div>Tables des emails liés aux deux autres : personnes et DNS (un email est composé d'un nom d'utilisateur et d'un nom de DNS séparé par le caractères &quot;@&quot; : arobe)<br />
<br />
La requête suivante présentes les utilisateurs avec leurs emails :<br />
<div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:84px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="26"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br /></div></td><td valign="top"><pre style="margin: 0">SELECT P.*, CONCAT(EML_USER, '@', DNS_NAME) AS EMAIL
FROM   T_PERSONNE AS P
       INNER JOIN T_EMAIL AS E ON P.PRS_ID = E.PRS_ID
       INNER JOIN T_DNS AS D ON E.DNS_ID = D.DNS_ID;</pre></td></tr></table></pre>
</div><div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:96px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="26"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br />5<br /></div></td><td valign="top"><pre style="margin: 0">PRS_ID      PRS_NOM          EMAIL
----------- ---------------- -----------------------------
1           DUPONT           paul.dupont356@gmail.com
1           DUPONT           pdupont45@outlook.fr
3           SMITH            smith-et-wesson@gmail.com</pre></td></tr></table></pre>
</div>Les jointures étant internes, la personne 2 (MARTIN) n'y figure pas...<br />
<br />
Rectifions avec des jointures externes :<br />
<div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:84px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="26"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br /></div></td><td valign="top"><pre style="margin: 0">SELECT P.*, CONCAT(EML_USER, '@', DNS_NAME) AS EMAIL
FROM   T_PERSONNE AS P
       LEFT OUTER JOIN T_EMAIL AS E ON P.PRS_ID = E.PRS_ID
       LEFT OUTER JOIN T_DNS AS D ON E.DNS_ID = D.DNS_ID;</pre></td></tr></table></pre>
</div><div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:108px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="26"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br />5<br />6<br /></div></td><td valign="top"><pre style="margin: 0">PRS_ID      PRS_NOM          EMAIL
----------- ---------------- ---------------------------
1           DUPONT           paul.dupont356@gmail.com
1           DUPONT           pdupont45@outlook.fr
2           MARTIN            
3           SMITH            smith-et-wesson@gmail.com</pre></td></tr></table></pre>
</div>La personne 2 (MARTIN) y figure mais sans préciser son email...<br />
<br />
Qu'en est-il si nous faisons une jointure externe suivie d'une jointure interne ? <br />
Après tout le DNS est obligatoire (NOT NULL) dans la table des EMAILs....<br />
<div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:84px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="26"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br /></div></td><td valign="top"><pre style="margin: 0">SELECT P.*, CONCAT(EML_USER, '@', DNS_NAME) AS EMAIL
FROM   T_PERSONNE AS P
       LEFT OUTER JOIN T_EMAIL AS E ON P.PRS_ID = E.PRS_ID
       INNER JOIN T_DNS AS D ON E.DNS_ID = D.DNS_ID;</pre></td></tr></table></pre>
</div><div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:96px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="26"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br />5<br /></div></td><td valign="top"><pre style="margin: 0">PRS_ID      PRS_NOM          EMAIL
----------- ---------------- -----------------------------
1           DUPONT           paul.dupont356@gmail.com
1           DUPONT           pdupont45@outlook.fr
3           SMITH            smith-et-wesson@gmail.com</pre></td></tr></table></pre>
</div>À nouveau, la personne 2 (MARTIN) n'y figure plus... Quel est ce mystère ???<br />
<br />
Vous allez commencer à comprendre si j'indente ma requête comme ceci :<br />
<br />
<div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:84px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="26"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br /></div></td><td valign="top"><pre style="margin: 0">SELECT P.*, CONCAT(EML_USER, '@', DNS_NAME) AS EMAIL
FROM   T_PERSONNE AS P
       LEFT OUTER JOIN T_EMAIL AS E ON P.PRS_ID = E.PRS_ID
          INNER JOIN T_DNS AS D ON E.DNS_ID = D.DNS_ID;</pre></td></tr></table></pre>
</div>La jointure se fait entre personnes et email puis entre email et dns... Dès lors si une personne n'a pas d'email... Elle n'a pas non plus de DNS, puisque le DNS_ID figure dans la table des emails !<br />
<br />
Avec toutes les colonnes la situation est plus évidente :<br />
<br />
<div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:108px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="26"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br />5<br />6<br /></div></td><td valign="top"><pre style="margin: 0">PRS_ID      PRS_NOM          EML_ID      PRS_ID      DNS_ID      EML_USER            DNS_ID      DNS_NAME
----------- ---------------- ----------- ----------- ----------- ------------------- ----------- -------------
1           DUPONT           33          1           99          paul.dupont356      99          gmail.com
1           DUPONT           35          1           98          pdupont45           98          outlook.fr
2           MARTIN           37          2           99          smith-et-wesson     99          gmail.com
3           SMITH            <b><font color="#0000FF">NULL        NULL        NULL        NULL</font>                <font color="#B22222">NULL        NULL</font></b></pre></td></tr></table></pre>
</div>En bleu la partie de la table des EMAILs qui est vide et en rouge la partie des DNS qui est aussi vide car il n'y a pas d'email...<br />
<br />
La clause FROM présente en fait une arborescence des jointure. La table citée derrière la clause FROM est la table racine de l'arbre de jointure. Les JOINs successifs dessinent des branches successives plus ou moins profondes. Voici un exemple qui présente mieux les différentes branches de l'arbre de jointure :<br />
<br />
<img src="https://www.developpez.net/forums/attachments/p645671d1698167328/parkings/restreint-parking-admin/version-2-nouveau-gabarit-dynamique-vos-articles/5.20-exemple-d-arbre-jointure.jpg/" border="0" alt="Nom : 5.20 – Exemple d’arbre de jointure.jpg
Affichages : 304
Taille : 178,8 Ko"  style="float: CONFIG" /><br />
<br />
De manière plus détaillé voici ce que je montre dans mon livre sur SQL à paraître dans quelques semaines...<br />
<br />
<br />
A +<br />
<img src="https://www.developpez.net/forums/attachments/p645672d1698167328/parkings/restreint-parking-admin/version-2-nouveau-gabarit-dynamique-vos-articles/sql-synthese-jointure-annulation-externe.jpg/" border="0" alt="Nom : SQL la synthese jointure annulation externe.jpg
Affichages : 316
Taille : 415,1 Ko"  style="float: CONFIG" /></div>
			
		</div>
	</div>
</div></blockquote>

]]></content:encoded>
			<dc:creator>SQLpro</dc:creator>
			<guid isPermaLink="true">https://www.developpez.net/forums/blogs/3170-sqlpro/b10542/explication-d-double-left-join/</guid>
		</item>
		<item>
			<title>Accès aux données dans un SGBD relationnel</title>
			<link>https://www.developpez.net/forums/blogs/3170-sqlpro/b10469/acces-aux-donnees-sgbd-relationnel/</link>
			<pubDate>Sun, 30 Apr 2023 07:55:29 GMT</pubDate>
			<description>---Citation (Envoyé par...</description>
			<content:encoded><![CDATA[<blockquote class="blogcontent restore"><div class="bbcode_container">
	<div class="bbcode_quote">
		<div class="quote_container">
			<div class="bbcode_quote_container"></div>
			
				<div class="bbcode_postedby">
					<img src="https://forum.developpez.be/images/misc/quote_icon.png" alt="Citation" /> Envoyé par <strong>SQLpro</strong>
					<a href="showthread.php?p=11942216#post11942216" rel="nofollow"><img class="inlineimg" src="https://forum.developpez.be/images/buttons/viewpost-right.png" alt="Voir le message" /></a>
				</div>
				<div class="message">Dans la littérature consacrée, les lignes sont enregistrées dans des pages (Oracle pour se distinguer parle de &quot;bloc&quot;) dont la taille est estimée en tenant compte :<br />
<ul><li style="">du type du système d'exploitation</li><li style="">du file système sous-jacent</li></ul><br />
Par exemple <br />
<ul><li style="">sous Windows c'est 8 Ko</li><li style="">Sous Linux c'est souvent du 8 Ko aussi pour la plupart des FS</li><li style="">Pour ZOS / DB2 c'est de 4 à 32 Ko en fonction de la teille des partitions de stockage...</li></ul><br />
<br />
Cette métrique est calculée par des abaques... Page trop petite = beaucoup d'IO (goulet d'étranglement de la fille d'attente pour accès concurrentiel au disque), trop grande = moins rapide à lire/écrire (encombrement de la mémoire, augmentation du temps d'accès).<br />
<br />
Pour les SGBDR d'entreprise qui gèrent leur propre stockage comme Microsoft SQL Server ou Oracle (mais ni MySQL ni PostgreSQL), c'est le moteur de stockage qui assure directement les entrées/sorties (appelées IO en anglais pour Input/Output) dans ce cas la plupart du temps ces pages sont elle même mise dans des groupes de pages contiguës (voire figure 1). Par exemple pour MS SQL Server ce sont des blocs de 8 pages, soit 64 Ko, appelés &quot;extensions&quot; (extents en anglais). Ceci toujours pour des raisons d'efficacité...  Autrement dit, les lectures physiques (remontées du disque vers la mémoire) font 64 Ko, tandis que les écritures physiques (descentes de la mémoire vers le disque) concernent 8 Ko. Ceci pour MS SQL Server, mais Oracle ou IBM DB2 font la même chose...<br />
<br />
<div style="text-align: center;"><img src="https://www.developpez.net/forums/attachments/p638058d1682840954/environnements-developpement/delphi/composants-vcl/faire-dragdrop-vers-windows/figure-10-011-extensions-mixtes-uniformes.jpg/" border="0" alt="Nom : Figure 10-011 - Extensions mixtes et uniformes.jpg
Affichages : 212
Taille : 117,4 Ko"  style="float: CONFIG" /><br />
<i>Figure 1 - les blocs de 8 pages contigües de SQL Server sont appelées EXTENSIONS (extents en anglais) et peuvent être uniforme (les 8 pages appartiennent à un seul objet : table ou index) ou mixtes (chaque page peut appartenir à un objet différent).</i></div><br />
En effet, les SGBDR manipulent les données uniquement en mémoire... Toute lecture (SELECT...), comme toute écriture INSERT, UPDATE, DELETE, MERGE, TRUNCATE, CREATE, ALTER, DROP... est effectuée en mémoire, mais les écritures seront reportées de la mémoire vers les fichiers de données, de temps en temps, de manière asynchrone, par le biais d'une commande interne nommée CHECKPOINT. Cette commande parcoure la mémoire à la recherche des pages &quot;sales&quot; (dirty pages) c'est à dire des pages désynchronisées par rapport aux fichiers de données. Toute écriture physique nécessite la réécriture de l'intégralité de la page, car dans chaque page il y a des métadonnées connexes (par exemple dans MS SQL Server chaque page contient, en sus des lignes et des identifiants de l'objet auquel appartient la page, le nombre d'octets libre, le nombre de ligne vivante et une somme de contrôle pour vérifier l'intégrité du stockage).<br />
<br />
Autrement dit chaque écriture mémoire vers disque (IO) est une page.<br />
<br />
Au niveau lecture disque vers mémoire, c'est par blocs de page que cela se passe, ce qui permet de faire des lectures anticipées (Par exemple pour MS SQL Server mettre en mémoire 8 pages, même si une seule a été demandée du fait de la lecture par extension).<br />
<br />
À l'intérieur de la page (voire figure 2), les lignes sont mises à la queue-leu-leu, sans ordre particulier si c'est une table, avec un placement spécifique pour un index (BTree, hash, bitmap....)<br />
<br />
<div style="text-align: center;"><img src="https://www.developpez.net/forums/attachments/p638059d1682840954/environnements-developpement/delphi/composants-vcl/faire-dragdrop-vers-windows/figure-10-008-structure-page.jpg/" border="0" alt="Nom : Figure 10-008 structure page.jpg
Affichages : 200
Taille : 158,9 Ko"  style="float: CONFIG" /><br />
<i>Figure 2 - Structure interne d'une page de données</i></div><br />
<ul><li style=""><b>L'entête de page </b>contient les métadonnées (par exemple pour SQL Server id de la base, id du fichier, id de la page, id de la table, id de l'index, nombre de lignes vivantes, nombre d'octets libres... en tout 96 octets),</li><li style="">le bas de la page contient un <b>tableau des offsets de ligne</b> qui indique à quel offset en octet commence telle ou telle ligne (le tableau se lit à l'envers...)</li><li style="">Entre l'entête et le tableau des offsets de ligne figurent <b>les lignes</b> qui sont généralement de longueur variable (VARCHAR, VARBINARY...).</li></ul><br />
Cette disposition permet de restreindre l'espace libre par le fait que les lignes croissent vers le bas tandis que que le tableau croit vers le haut.... À la fin il ne reste quasiment plus de place ! Notez que dans Oracle, toujours pour se distinguer des autres, le tableau est en haut de la page et les lignes en bas.<br />
<br />
Du point de vue &quot;page&quot;, les lignes sont appelées &quot;slot&quot;, c'est à ,dire en fait &quot;emplacement de lignes&quot;<br />
<br />
Enfin en ce qui concerne les données qui sont dans les lignes, ces dernières sont mise à la queue-leu-leu, soit par leur position ordinale dans la définition de la table (voir dans SELECT * FROM INFORMATION_SCHEMA.COLUMNS) mais dans certains SGBDR haut de gamme comme MS SQL Server une disposition particulière permet d'en accélérer les accès (voire figure 3)...<br />
<br />
<div style="text-align: center;"><img src="https://www.developpez.net/forums/attachments/p638060d1682840954/environnements-developpement/delphi/composants-vcl/faire-dragdrop-vers-windows/figure-10-013-structure-ligne.jpg/" border="0" alt="Nom : Figure 10-013 - Structure ligne.jpg
Affichages : 165
Taille : 86,8 Ko"  style="float: CONFIG" /><br />
<i>Figure 3 - Disposition particulière des informations dans SQL Server afin d'optimiser les accès aux données</i></div><br />
En effet dans le cas de SQL Server une colonne de taille fixe verra sa données toujours stocké à un offset identique par rapport au début de la ligne et une colonne de taille variable, nécessitera une seule lecture supplémentaire. Dans PostGreSQL, les informations étant stockées les unes après les autres, à l'aide d'un code séparateur, il faut lire successivement toutes les valeurs précédentes pour accéder à la bonne. Par exemple si l'information est cherché dans la 10e colonne, alors il faut lire la ligne octets par octets et s'arrêter après le 9e octets séparateur pour lire le début de l'information recherchée, et s'arrêter au séparateur suivant... ce qui est plus long...<br />
Autre optimisation SQL Server pour les données de taille variables, si l'on cherche un littéral dont la longueur est de 23 octets par exemple, inutile d'aller lire les zones de plus de moins de 23 octets... Donc, une seule lecture...<br />
<br />
Enfin, pour les mises à jour, c'est dernières sont écrites d'abord dans le journal de transaction qui assure la persistance pour pouvoir :<br />
<ul><li style="">revenir à l'état initial des données en cas d'annulation de la transaction (toute écriture est une transaction journalisée)</li><li style="">permettre de récupérer les dernières mise à jours non encore répercutées au niveau des fichiers de données en cas de crash du système.</li></ul><br />
<br />
L'ensemble des commandes est décrit en figure 4... depuis la mise à jour d'une information par l'utilisateur jusqu'au CHECKPOINT.<br />
<br />
<div style="text-align: center;"><img src="https://www.developpez.net/forums/attachments/p638061d1682840954/environnements-developpement/delphi/composants-vcl/faire-dragdrop-vers-windows/figure-10-014-cache-checkpoint.jpg/" border="0" alt="Nom : Figure 10-014 cache et checkpoint.jpg
Affichages : 146
Taille : 182,0 Ko"  style="float: CONFIG" /><br />
<i>Figure 4 - Gestion des données entre cache (RAM) et disques lors des transactions d'écriture</i></div><br />
Tout ceci est extrait de mon livre sur MS SQL Server...<br />
<img src="https://www.developpez.net/forums/attachments/p638062d1682840954/environnements-developpement/delphi/composants-vcl/faire-dragdrop-vers-windows/couverture-livre-sql-server-eyrolles.jpg/" border="0" alt="Nom : Couverture livre SQL server Eyrolles.jpg
Affichages : 150
Taille : 105,0 Ko"  style="float: CONFIG" /><br />
<br />
A +</div>
			
		</div>
	</div>
</div></blockquote>

]]></content:encoded>
			<dc:creator>SQLpro</dc:creator>
			<guid isPermaLink="true">https://www.developpez.net/forums/blogs/3170-sqlpro/b10469/acces-aux-donnees-sgbd-relationnel/</guid>
		</item>
		<item>
			<title><![CDATA[Audit global des performances d'un serveur MS SQL Server]]></title>
			<link>https://www.developpez.net/forums/blogs/3170-sqlpro/b10029/audit-global-performances-d-serveur-ms-sql-server/</link>
			<pubDate>Tue, 16 Feb 2021 17:57:23 GMT</pubDate>
			<description>* La première des choses à...</description>
			<content:encoded><![CDATA[<blockquote class="blogcontent restore"><ul><li style="">La première des choses à voir sur un serveur MS SQL est de voir si toutes les bonnes pratiques ont été appliquées. La plupart du temps cela débloque 50 à 90 % des problèmes.</li><li style="">La seconde des choses à faire consiste à mener une campagne d'indexation : rajouter les index manquants et supprimer les index inutiles, redondants (doublons) ou inclus.</li><li style="">La troisième des choses est de voir quels sont les objets lents (pas seulement les requêtes) : requêtes, procédures, déclencheurs et UDF.</li></ul><br />
<br />
<br />
Si, et seulement si, ces trois choses-là n'ont pas résorbé le problème, alors on peut commencer à utiliser le profileur SQL et l'analyseur de performances. Si cela ne suffit pas, on peut alors utiliser les événements étendus.<br />
<br />
<font size="4"><b>1 - les bonnes pratiques </b></font><br />
<br />
<font size="3">1.1 - Configuration OS </font><br />
<br />
Si VM, voir si <br />
1.1.1 - les cœurs ne sont pas flottant<br />
1.1.2 - la RAM n'est pas en &quot;balooning&quot;<br />
1.1.3 - le stockage est dédié<br />
<br />
Voir les bonnes pratiques VMWare : <br />
<a href="https://www.vmware.com/content/dam/digitalmarketing/vmware/en/pdf/solutions/sql-server-on-vmware-best-practices-guide.pdf" target="_blank">https://www.vmware.com/content/dam/d...ices-guide.pdf</a><br />
<br />
Au niveau Windows, vérifier si le mode économie d'énergie est actif. Si oui, le désactiver pour rouler au max de fréquence des CPU.<br />
<br />
<font size="3">1.2 - Configuration périmétrique </font><br />
<br />
Vérifiez qu'il n'y a pas d'antivirus.<br />
S'il y en a, isolez tous les répertoires de données des bases de production et système.<br />
Préférez l'antivirus MS qui n'est pas plus mauvais que les autres, mais plus léger et ne s'occupe pas des éléments MS et notamment de SQL Server.<br />
<br />
Vérifiez que SQL Server tourne sur une machine dédiée, ce qui signifie :<br />
aucune autre instance MS SQL Server<br />
aucun autre service applicatif d'aucun genre (autre SGBD, application tierce....)<br />
<br />
Désactivez le maximum de services Windows inutiles.<br />
<br />
<font size="3">1.3 - configuration d'instance SQL Server </font><br />
<br />
Vérifiez que votre instance SQL Server soit à jour des derniers SP pour les versions jusqu'à 2016 et CU pour les versions à partir de 2017<br />
aidez vous de : <a href="https://sqlserverbuilds.blogspot.com/" target="_blank">https://sqlserverbuilds.blogspot.com/</a><br />
<br />
<br />
Vérifiez les paramètres suivants (sp_configure) :<br />
<b>affinity I/O mask, affinity mask, affinity64 I/O mask, affinity64 mask</b> à 0<br />
<br />
<b>cost threshold for parallelism</b> : à mettre entre 12 et 100 suivant importance des bases (12 petites bases, 24 moyennes bases, 50 grosses bases, 100 VLDB)<br />
<b>max degree of parallelism</b> : à positionner en fonction du nombre de cœurs si pas fait au niveau installation, avec le calcul suivant :<br />
<br />
<div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:156px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="33"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br /></div></td><td valign="top"><pre style="margin: 0">Nombre de cœurs   MAXDOP
----------------- -------
    &lt; 8            2
 8 à 12            3
14 à 20            4
22 à 32            5
34 à 48            6
50 à 64            7
66 à 96            8
   &gt; 96           10</pre></td></tr></table></pre>
</div>Pour une solution OLTP<br />
<br />
Pour de l'OLAP, 50 % des cœurs.<br />
<br />
On compte les cœurs hyperthreadés (cœurs logiques)<br />
<br />
<b>max server memory (MB)</b> : à positionner en fonction de la quantité de RAM, si pas fait au niveau installation, avec le calcul suivant :<br />
<br />
<div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:204px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="33"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br />19<br />20<br />21<br />22<br />23<br />24<br /></div></td><td valign="top"><pre style="margin: 0">DECLARE @RAM_MACHINE_MB INT = (SELECT physical_memory_kb / 1024 
                               FROM sys.dm_os_sys_info);
WITH
T AS
(
SELECT @RAM_MACHINE_MB AS RAM_MACHINE_MB, 
       CASE WHEN @RAM_MACHINE_MB &lt; 8192 
               THEN 2048
            WHEN @RAM_MACHINE_MB &lt; 16384
               THEN 2048 + 1024 * (@RAM_MACHINE_MB - 8192) / 8192 
            WHEN @RAM_MACHINE_MB &lt; 32768
               THEN 3096 + 1024 * (@RAM_MACHINE_MB - 16384) / 16384
            WHEN @RAM_MACHINE_MB &lt; 131072
               THEN 4096 + 2048 * (@RAM_MACHINE_MB - 32768) / 98304
            WHEN @RAM_MACHINE_MB &lt; 1048576
               THEN 6144 + 2048 * (@RAM_MACHINE_MB - 131072) / 917504
            ELSE 8192
       END AS RAM_OS_MB
)
SELECT *, RAM_MACHINE_MB - RAM_OS_MB AS RAM_SQL_KB, 
       'EXEC sp_configure ''max server memory (MB)'', ' 
       + CAST(RAM_MACHINE_MB - RAM_OS_MB AS VARCHAR(32)) + ';' + CHAR(13) + CHAR(10)
       + 'RECONFIGURE;'
FROM   T;</pre></td></tr></table></pre>
</div><b>optimize for ad hoc workloads</b> à 1<br />
<br />
Vous pouvez aussi activer :<br />
<b>backup checksum default</b> à 1<br />
<b>backup compression default</b> à 1 si aucune base en chiffrement TDE<br />
<br />
traceflag :<br />
Si version antérieure à 2016 activez le (TF 2371)<br />
Si version égale à 2016 sp1 et antérieure à 2019, activez le TF 7412<br />
Si version 2014 SP1 et +, activez le TF 7471<br />
Si version 2014 Sp2 et inférieure à 2016, activez le TF 8079<br />
<br />
Vérifiez que vous êtes OK sur le cache en utilisant la requête suivante :<br />
<div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:84px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="26"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br /></div></td><td valign="top"><pre style="margin: 0">SELECT instance_name AS NUMA_NODE, cntr_value AS PAGE_LIFE_EXPECTANSY
FROM   sys.dm_os_performance_counters
WHERE  object_name LIKE N'%Buffer Node%'
  AND  counter_name = N'Page life expectancy'</pre></td></tr></table></pre>
</div>Au minimum à 3600, bien à 15 000, parfait à 30 000 secondes<br />
Vérifiez, si vous avez plusieurs noeuds NUMA qu'il n'y ait pas un fort déséquilibre (moins de 10 %)<br />
<br />
Vérifiez l'état global de votre stockage disques à l'aide de la requête suivante :<br />
<br />
<div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:204px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="33"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br /></div></td><td valign="top"><pre style="margin: 0">SELECT db.name AS &quot;DATABASE_NAME&quot;, mf.name AS LOGICAL_FILE_NAME, 
       mf.type_desc, size / 128.0 AS SIZE_MB, is_percent_growth, 
       volume_mount_point, 100.0 * available_bytes / NULLIF(total_bytes, 0) AS DISK_PERCENT_FREE,
       io_stall_read_ms / NULLIF(num_of_reads, 0) AS READ_LATENCY_MS,
       io_stall_write_ms / NULLIF(num_of_writes,0) AS WRITE_LATENCY_MS,
       SUM(io_stall_read_ms) OVER (PARTITION BY mf.database_id) / 
          NULLIF(SUM(num_of_reads) OVER(PARTITION BY mf.database_id), 0) AS DB_READ_LATENCY_MS,
       SUM(io_stall_write_ms) OVER (PARTITION BY mf.database_id) / 
          NULLIF(SUM(num_of_writes) OVER(PARTITION BY mf.database_id), 0) AS DB_WRITE_LATENCY_MS,
       SUM(io_stall_read_ms) OVER (PARTITION BY vs.volume_mount_point) / 
          NULLIF(SUM(num_of_reads) OVER(PARTITION BY vs.volume_mount_point), 0) AS DRIVE_READ_LATENCY_MS,
       SUM(io_stall_write_ms) OVER (PARTITION BY vs.volume_mount_point) / 
          NULLIF(SUM(num_of_writes) OVER(PARTITION BY vs.volume_mount_point), 0) AS DRIVE_WRITE_LATENCY_MS
FROM   sys.master_files AS mf
       JOIN sys.databases AS db ON mf.database_id = db.database_id
       CROSS APPLY sys.dm_os_volume_stats(mf.database_id, mf.file_id) AS vs
       CROSS APPLY sys.dm_io_virtual_file_stats(mf.database_id, mf.file_id) AS vfs</pre></td></tr></table></pre>
</div>Si vous avez des grossissements en pourcentage autres que sur les bases système, remédiez à cela.<br />
Si vous avez moins de 10 % de libre sur vos disques, remédiez à cela<br />
Si vos latences disques sont supérieures à :<br />
<ul><li style="">15 ms en écriture</li><li style="">8 ms en lecture</li></ul><br />
<br />
Vous avez un problème de stockage. Remédiez-y.<br />
<br />
<font size="3">1.4 - Réglages pour tempdb</font><br />
<br />
Vérifiez bien qu'il y ait au moins 1 fichier par paire de cœurs, jusqu'à concurrence de 8 fichiers.<br />
<br />
Si version antérieure à 2019, activez les traceflag 1117 et 1118<br />
<br />
Dimensionnez correctement vos fichiers de la base de tempdb pour qu'il n'y ait aucune opération de croissance durant l'exploitation ordinaire.<br />
<br />
<font size="3">1.5 - Réglages par base</font><br />
<br />
Quelques-uns des réglages au niveau <a href="https://docs.microsoft.com/fr-fr/sql/t-sql/statements/alter-database-scoped-configuration-transact-sql?view=sql-server-ver15" target="_blank">ALTER DATABASE SCOPED CONFIGUATION</a> peuvent être appliqués en fonction de la nature de la base de données. Notamment ceux jouant sur l'optimisation et en particulier l'estimateur de cardinalité.<br />
<br />
<font size="5">2 - Campagne d'indexation</font><br />
<br />
Deux méthodes : <br />
<font size="3">2.1 - la méthode brute</font><br />
<br />
Consiste à créer entre 20 et 60 % des index manquants (global au serveur) en prenant ceux ayant le plus de gains (pas ma méthode favorite, car surindex les grosses tables au détriment des petites...). Requête pour se faire :<br />
<div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:204px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="33"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br /></div></td><td valign="top"><pre style="margin: 0">SELECT ROW_NUMBER() OVER(ORDER BY migs.avg_total_user_cost * migs.avg_user_impact DESC) AS RN,
       100.0 * ROW_NUMBER() OVER(ORDER BY migs.avg_total_user_cost * migs.avg_user_impact DESC) 
       / COUNT(*) OVER() AS PERCENT_CREATED,
       N'CREATE INDEX X_' + REPLACE(CAST(NEWID() AS NVARCHAR(36)), N'-', N'_') + 
       N'_' + CONVERT(CHAR(8), GETDATE(), 112) + N' ON ' + statement +
       N' (' + COALESCE(equality_columns + N', ' + inequality_columns, equality_columns, inequality_columns) + N') ' + 
       CASE WHEN included_columns IS NULL THEN N'' 
            ELSE N' INCLUDE(' + included_columns + ') ' END + 
       N';' AS SQL_CREATE_INDEX,
       migs.avg_total_user_cost * migs.avg_user_impact AS INDICE
FROM   sys.dm_db_missing_index_details AS mid
       JOIN sys.dm_db_missing_index_groups AS mig
          ON mid.index_handle = mig.index_handle
       JOIN sys.dm_db_missing_index_group_stats AS migs
          ON migs.group_handle = mig.index_group_handle
ORDER BY RN</pre></td></tr></table></pre>
</div><br />
<br />
<font size="3">2.2 - la méthode intelligente</font><br />
<br />
Plus intelligent : auditer chaque index à créer en évitant les inclusions et pseudo redondances à l'aide du processus suivant :<br />
<a href="http://mssqlserver.fr/aide-au-diagnostic-dindexation-dune-base-ms-sql-server/" target="_blank">http://mssqlserver.fr/aide-au-diagno...ms-sql-server/</a><br />
<br />
<font size="3">2.3 - Supprimer les index inutiles</font><br />
<br />
Utiliez la requête suivante (par base) <br />
<div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:204px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="33"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br /></div></td><td valign="top"><pre style="margin: 0">
SELECT s.name AS TABLE_SCHEMA, o.name AS TABLE_NAME, o.type_desc, i.name AS INDEX_NAME, ius.user_updates 
FROM   sys.dm_db_index_usage_stats AS ius
       JOIN sys.indexes AS i ON ius.object_id = i.object_id AND ius.index_id = i.index_id
       JOIN sys.objects AS o ON ius.object_id = o.object_id
       JOIN sys.schemas AS s ON o.schema_id = s.schema_id
WHERE  database_id = DB_ID()
  AND  ius.user_seeks = 0 
  AND  ius.user_lookups = 0 
  AND  ius.user_scans = 0
  AND  i.is_hypothetical = 0
  AND  i.is_primary_key = 0
  AND  i.is_unique = 0
  AND  i.is_unique_constraint = 0
ORDER BY ius.user_updates;</pre></td></tr></table></pre>
</div><font size="3">2.4 - supprimez les index redondants ou  inclus</font><br />
<br />
Aidez-vous de la requête suivante pour choisir les index à supprimer (par base) :<br />
<br />
<div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:204px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="33"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br />19<br />20<br />21<br />22<br />23<br />24<br />25<br />26<br />27<br />28<br />29<br />30<br />31<br />32<br />33<br />34<br />35<br />36<br />37<br />38<br />39<br />40<br />41<br />42<br />43<br />44<br />45<br />46<br />47<br />48<br />49<br />50<br />51<br />52<br />53<br />54<br />55<br />56<br />57<br />58<br />59<br />60<br />61<br />62<br />63<br />64<br />65<br />66<br />67<br />68<br />69<br />70<br />71<br />72<br />73<br />74<br />75<br />76<br />77<br />78<br />79<br />80<br />81<br />82<br />83<br />84<br />85<br />86<br />87<br />88<br />89<br />90<br />91<br />92<br />93<br />94<br />95<br />96<br />97<br /></div></td><td valign="top"><pre style="margin: 0">WITH 
TK AS 
(
-- clés
SELECT o.object_id, i.index_id, i.type_desc, i.filter_definition,
       STUFF((SELECT ', ' + c.name + CASE WHEN ic.is_descending_key = 1 THEN ' DESC' ELSE '' END
              FROM   sys.index_columns AS ic  
                     INNER JOIN sys.columns AS c  
                        ON ic.object_id = c.object_id  
                        AND ic.column_id = c.column_id 
              WHERE  ic.object_id = i.object_id AND ic.index_id = i.index_id
                AND  ic.key_ordinal &gt; 0
                AND  ic.is_included_column = 0
              ORDER BY ic.key_ordinal
              FOR XML PATH('')), 1, 2, '') AS KEY_LIST,
       STUFF((SELECT ', ~' + CAST(ic.column_id AS VARCHAR(32)) + CASE WHEN ic.is_descending_key = 1 THEN ' DESC' ELSE '' END + '~'
              FROM   sys.index_columns AS ic  
              WHERE  ic.object_id = i.object_id AND ic.index_id = i.index_id
                AND  ic.key_ordinal &gt; 0
                AND  ic.is_included_column = 0
              ORDER BY ic.key_ordinal
              FOR XML PATH('')), 1, 2, '') AS KEY_ID_LIST
FROM   sys.indexes AS i
       JOIN sys.objects AS o ON i.object_id = o.object_id
WHERE  o.is_ms_shipped = 0 AND index_id &gt; 0
),
TI AS
(
-- colonnes incluses
SELECT o.object_id, i.index_id,
       STUFF((SELECT ', ' + c.name 
              FROM   sys.index_columns AS ic  
                     INNER JOIN sys.columns AS c  
                        ON ic.object_id = c.object_id  
                        AND ic.column_id = c.column_id 
              WHERE  ic.object_id = i.object_id AND ic.index_id = i.index_id
                AND  ic.key_ordinal = 0
                AND  ic.is_included_column = 1
              ORDER BY c.name
              FOR XML PATH('')), 1, 2, '') AS INC_LIST
FROM   sys.indexes AS i
       JOIN sys.objects AS o ON i.object_id = o.object_id
WHERE  o.is_ms_shipped = 0 AND index_id &gt; 0
  AND  EXISTS(SELECT * 
              FROM   sys.index_columns AS ic  
              WHERE  ic.object_id = i.object_id AND ic.index_id = i.index_id
                AND  ic.is_included_column = 1)
),
TS AS
(
-- synthèses
SELECT TK.object_id, TK.index_id, TK.type_desc, TK.filter_definition, TK.KEY_LIST, TK.KEY_ID_LIST, TI.INC_LIST,
       1 + LEN(KEY_ID_LIST) - LEN(REPLACE(KEY_ID_LIST, ',', '')) AS COLS
FROM   TK
       LEFT OUTER JOIN TI ON TK.object_id = TI.object_id AND TI.index_id = TK.index_id
),
TX AS
(
-- comparaisons
SELECT T2.object_id, T2.index_id,  
              T3.index_id AS index_id_anomalie,  
              T2.KEY_LIST AS CLEF_INDEX,  
              T3.KEY_LIST AS CLEF_INDEX_ANORMAL,
              T2.INC_LIST, T3.INC_LIST AS COLONNES_INCLUSES_ANORMAL,  
              CASE  
                 WHEN T2.KEY_ID_LIST = T3.KEY_ID_LIST  
                   THEN 'DOUBLONS'  
                 WHEN T2.KEY_ID_LIST LIKE T3.KEY_ID_LIST +'%'  
                   THEN 'INCLUS'  
              END AS ANOMALIE,  
              ABS(T2.COLS - T3.COLS) AS DISTANCE  
       FROM   TS AS T2  
              INNER JOIN TS AS T3  
             ON T2.object_id = T3.object_id  
                AND T2.index_id &lt;&gt; T3.index_id  
                AND T2.KEY_ID_LIST LIKE T3.KEY_ID_LIST +'%'
)
-- résultat final
SELECT TX.*,  
       s.name +'.' + o.name AS NOM_TABLE,  
       i1.name AS NOM_INDEX,  
       i2.name AS NOM_INDEX_ANORMAL
       , i1.filter_definition AS FILTRE_INDEX
       , i2.filter_definition AS FILTRE_INDEX_ANORMAL
FROM   TX  
       INNER JOIN sys.objects AS o  
             ON TX.object_id = o.object_id  
             INNER JOIN sys.schemas AS s  
                   ON o.schema_id = s.schema_id  
       INNER JOIN sys.indexes AS i1  
             ON TX.object_id = i1.object_id  
                AND TX.index_id = i1.index_id  
       INNER JOIN sys.indexes AS i2  
             ON TX.object_id = i2.object_id  
                AND TX.index_id_anomalie = i2.index_id  
WHERE  o.&quot;type&quot; IN ('U', 'V')  
ORDER  BY NOM_TABLE, NOM_INDEX;</pre></td></tr></table></pre>
</div><br />
<font size="3">2.5 - supprimez les statistiques inutiles</font><br />
<br />
Utilisez la requête suivante (par base)<br />
<br />
<div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:204px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="33"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br />19<br />20<br />21<br />22<br />23<br />24<br />25<br />26<br />27<br />28<br />29<br />30<br />31<br />32<br />33<br />34<br />35<br />36<br />37<br />38<br />39<br />40<br />41<br />42<br />43<br />44<br />45<br /></div></td><td valign="top"><pre style="margin: 0">WITH T0 AS
(
SELECT s.object_id, s.name AS STAT_NAME, i.name AS INDEX_NAME,
       CASE WHEN i.is_primary_key = 1 THEN 'PRIMAY KEY'
            WHEN i.is_unique_constraint = 1 THEN 'UNIQUE'
       END AS CONSTRAINT_TYPE,
       i.has_filter AS HAS_FILTER,
       (SELECT TOP 1 c.name
        FROM   sys.stats_columns AS sc
               JOIN sys.columns AS c
                    ON sc.object_id = c.object_id
                    AND sc.column_id = c.column_id
        WHERE  s.object_id = sc.object_id  
          AND  s.stats_id = sc.stats_id
        ORDER BY sc.stats_column_id) AS STAT_COLUMN,
       STUFF((SELECT ', ' + c.name
              FROM   sys.stats_columns AS sc
                     JOIN sys.columns AS c
                          ON sc.object_id = c.object_id
                          AND sc.column_id = c.column_id
              WHERE  s.object_id = sc.object_id  
                AND  s.stats_id = sc.stats_id
              ORDER BY sc.stats_column_id
              FOR XML PATH('')), 1, 1, '') AS KEY_COLS  
FROM   sys.stats AS s
       LEFT OUTER JOIN sys.indexes AS i
            ON s.object_id = i.object_id
            AND s.name = i.name
)
SELECT A.*, B.STAT_NAME AS STAT_NAME2, B.INDEX_NAME AS INDEX_NAME2,
       B.CONSTRAINT_TYPE AS CONSTRAINT_TYPE2, B.HAS_FILTER AS HAS_FILTER2,
       B.KEY_COLS AS KEY_COLS2,
       N'DROP STATISTICS [' + s.name + N'].[' + o.name + N'].[' +
                         B.STAT_NAME + N'];' AS DROP_STAT
FROM   T0 AS A
       JOIN T0 AS B
            ON  A.STAT_COLUMN = B.STAT_COLUMN
            AND A.STAT_NAME &gt; B.STAT_NAME
            AND A.object_id = B.object_id
       JOIN sys.objects AS o
            ON A.object_id = o.object_id
       JOIN sys.schemas AS s
            ON o.schema_id = s.schema_id                
WHERE  B.INDEX_NAME IS NULL
AND    o.&quot;type&quot; IN ('V', 'U');</pre></td></tr></table></pre>
</div><br />
<b><font color="#FF0000"><font size="5">ATTENTION !</font></font></b><br />
<br />
Pour tous ces diagnostics, soyez sûr que l'instance n'a pas redémarré depuis au moins 31 jours, à l'aide de la requête :<br />
<br />
<div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:144px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="26"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br /></div></td><td valign="top"><pre style="margin: 0">DECLARE @SQL_START DATETIME = (SELECT sqlserver_start_time 
                               FROM   sys.dm_os_sys_info);
DECLARE @SQL_START_STR CHAR(21) = CONVERT(CHAR(19), @SQL_START, 121);
IF @SQL_START &gt; DATEADD(day, -31, GETDATE())
BEGIN
   RAISERROR ('The server restarted on %s which is insufficient for a good diagnosis. Try again in a few days', 
              16, 1, @SQL_START_STR);
   RETURN;
END;</pre></td></tr></table></pre>
</div><br />
<font size="5">3 - objets lents</font><br />
<br />
Après toutes ces mises en oeuvre et passé un délai de 31 jours, utilisez les requêtes suivantes qui extraient les 25 objets les plus pourris au niveau perf dans chacune des 4 catégories suivantes :<br />
<br />
<div class="bbcode_container">
	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>
	<td style="border: 0; padding: 0; text-align: left">Code  :</td>
	<td style="border: 0; padding: 0; text-align: right"><a href="#" onclick="return ano_selectionnerCode(this);">Sélectionner tout</a> -
	<a href="#" onclick="return ano_etendreCode(this);">Visualiser dans une fenêtre à part</a></td></tr></table>
	<pre class="bbcode_code" style="height:204px;"><table cellspacing="0" cellpadding="0"><tr><td valign="top" width="33"><div style="border: 1px dashed gray; padding-left: 5px; padding-right: 5px; margin-right: 5px; text-align: right; font-family: monospace">1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br />19<br />20<br />21<br />22<br />23<br />24<br />25<br />26<br />27<br />28<br />29<br />30<br />31<br />32<br />33<br />34<br />35<br />36<br />37<br />38<br />39<br />40<br />41<br />42<br />43<br />44<br />45<br />46<br />47<br />48<br />49<br />50<br />51<br />52<br />53<br />54<br />55<br />56<br />57<br />58<br />59<br />60<br />61<br />62<br /></div></td><td valign="top"><pre style="margin: 0">DECLARE @TOP INT = 25;
WITH 
Q AS
(
SELECT TOP(@TOP) 'QUERY' AS CATEGORY, 
       SUBSTRING(st.text, 
                        1 + s.statement_start_offset/2,  
                       ((CASE s.statement_end_offset   
                            WHEN -1 THEN DATALENGTH(st.text)  
                            ELSE s.statement_end_offset 
                         END  - s.statement_start_offset)/2) + 1) AS SQL_STATEMENT,
       s.execution_count, s.total_worker_time, s.max_elapsed_time, s.min_elapsed_time,
       CAST(ROUND(1.0 * s.total_worker_time / s.execution_count, 3) AS DECIMAL(32, 3)) AS AVG_EXEC_TIME
FROM   sys.dm_exec_query_stats AS s
       CROSS APPLY sys.dm_exec_sql_text(sql_handle) AS st
       CROSS APPLY sys.dm_exec_query_plan(plan_handle)
ORDER BY total_worker_time DESC
),
P AS
(
SELECT TOP(@TOP) 'PROCEDURE' AS CATEGORY, 
       st.text AS SQL_STATEMENT,
       s.execution_count, s.total_worker_time, s.max_elapsed_time, s.min_elapsed_time,
       CAST(ROUND(1.0 * s.total_worker_time / s.execution_count, 3) AS DECIMAL(32, 3)) AS AVG_EXEC_TIME
FROM   sys.dm_exec_procedure_stats AS s
       CROSS APPLY sys.dm_exec_sql_text(sql_handle) AS st
       CROSS APPLY sys.dm_exec_query_plan(plan_handle)
ORDER BY total_worker_time DESC
),
T AS
(
SELECT TOP(@TOP) 'TRIGGER' AS CATEGORY, 
       st.text AS SQL_STATEMENT,
       s.execution_count, s.total_worker_time, s.max_elapsed_time, s.min_elapsed_time,
       CAST(ROUND(1.0 * s.total_worker_time / s.execution_count, 3) AS DECIMAL(32, 3)) AS AVG_EXEC_TIME
FROM   sys.dm_exec_trigger_stats AS s
       CROSS APPLY sys.dm_exec_sql_text(sql_handle) AS st
       CROSS APPLY sys.dm_exec_query_plan(plan_handle)
ORDER BY total_worker_time DESC
),
F AS
(SELECT TOP(@TOP) 'PROCEDURE' AS CATEGORY, 
       st.text AS SQL_STATEMENT,
       s.execution_count, s.total_worker_time, s.max_elapsed_time, s.min_elapsed_time,
       CAST(ROUND(1.0 * s.total_worker_time / s.execution_count, 3) AS DECIMAL(32, 3)) AS AVG_EXEC_TIME
FROM   sys.dm_exec_function_stats AS s
       CROSS APPLY sys.dm_exec_sql_text(sql_handle) AS st
       CROSS APPLY sys.dm_exec_query_plan(plan_handle)
ORDER BY total_worker_time DESC
)
SELECT *
FROM   Q 
UNION ALL
SELECT *
FROM   P 
UNION ALL
SELECT *
FROM   T 
UNION ALL
SELECT *
FROM   F
ORDER BY total_worker_time</pre></td></tr></table></pre>
</div>Enfin, s'il vous reste quelque chose à moudre, utilisez le profiler puis les extended events !</blockquote>

]]></content:encoded>
			<dc:creator>SQLpro</dc:creator>
			<guid isPermaLink="true">https://www.developpez.net/forums/blogs/3170-sqlpro/b10029/audit-global-performances-d-serveur-ms-sql-server/</guid>
		</item>
	</channel>
</rss>
