{"id":703,"date":"2009-10-09T00:00:00","date_gmt":"2009-10-09T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/reducing-io-with-the-missing-indexes-dmvs\/"},"modified":"2021-06-03T16:44:19","modified_gmt":"2021-06-03T16:44:19","slug":"reducing-io-with-the-missing-indexes-dmvs","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/performance-sql-server\/reducing-io-with-the-missing-indexes-dmvs\/","title":{"rendered":"Reducing I\/O with the &#8216;Missing Indexes&#8217; DMVs"},"content":{"rendered":"<div id=\"PRETTY\">\n<p class=\"INDENTED\"><em><strong>Editor&#8217;s Note<\/strong>: As these screenshots incorporate elements of Brazilian Portuguese, please bear in mind that the word &#8216;<strong>Codigo&#8217; <\/strong>means <strong>Code<\/strong>, and for consistency we&#8217;ve decided to use the Portuguese term whenever refering to the columns in the test database this article features.<\/em><\/p>\n<p><strong>Here was the scene to start with:<\/strong> The client had purchased a really nice, latest-generation server (SAN and Blade, 32 GB RAM, 16 processors, W2K8 64, SQL2K8 64), and the whole disc apparatus was set up according to Microsoft best practices (block size of 64K, stripe size of 128K, set raids 0+1 to separate log and data).. But some counters were completely out of the ordinary. On one of the more accessible databases (Approx 400 GB &#8211; out of a total of almost 4 TB of user Databases), the disk readings were really quite scary:<\/p>\n<table>\n<tbody>\n<tr>\n<td colspan=\"4\" valign=\"top\">\n<p><em><strong>Physical Disk<\/strong><\/em><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>Date <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p><strong>% Disk Read Time <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p><strong>Avg Disk Queue length<\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p><strong>Avg Disk Sec\/transfer <\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>&#8216;Normal&#8217; values<\/strong><\/p>\n<\/td>\n<td valign=\"top\">\u00a0<\/td>\n<td valign=\"top\">\n<p>should be &lt; 2 per physical disc in the RAID\u00a0 array<\/p>\n<\/td>\n<td valign=\"top\">\n<p>Typically, if the value is larger than 20 ms, the disk is over-loaded:<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>2009\/07\/28 <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>3300<\/p>\n<\/td>\n<td valign=\"top\">\n<p>542<\/p>\n<\/td>\n<td valign=\"top\">\n<p>37<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>2009\/07\/29 <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>2700<\/p>\n<\/td>\n<td valign=\"top\">\n<p>580<\/p>\n<\/td>\n<td valign=\"top\">\n<p>35<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>2009\/07\/30 <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>3000<\/p>\n<\/td>\n<td valign=\"top\">\n<p>540<\/p>\n<\/td>\n<td valign=\"top\">\n<p>30<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><\/p>\n<table>\n<tbody>\n<tr>\n<td colspan=\"2\" valign=\"top\">\n<p><strong><em>SQL Server<\/em><\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>Date <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p><strong>Page Life Expectancy <\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>&#8216;Normal&#8217; value<\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>Should be &gt; 600<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>2009\/07\/28 <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>80<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>2009\/07\/29 <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>60<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>2009\/07\/30 <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>75<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>To be honest, I only looked at the % Disk Read Time because it seemed pretty extreme. I couldn&#8217;t really tell what the norm was in this case, but the counter was very, very high, so I assumed there was a problem even though 100+% Disc Read Times are <a href=\"http:\/\/support.microsoft.com\/default.aspx?scid=kb;en-us;310067\">fairly normal in RAID arrays<\/a>:<\/p>\n<p class=\"INDENTED\">&#8220;<em>This behavior can occur because some controllers allow the operating system to use overlapping input\/output operations for multiple outstanding requests&#8230; \u00a0If you have multiple disks in a Raid arrangement, the overlapped input\/output happens because the operating system can read and write to multiple disks, and this could show values that are higher than 100 percent for this counter.<\/em>&#8220;<br \/>\n&#8211; <strong>Microsoft Support Knowledge Base<\/strong><\/p>\n<p>I haven&#8217;t included all the counters I looked at here, just the more interesting ones. Obviously the &#8216;normal&#8217; values for these counters are not hard rules; I would not be concerned if the values were 20 &#8211; 30% above these (partly because I did not have any baselines set up- as we say in Brazil, I &#8220;fell from an airplane&#8221; into this project). But as you can see, that wasn&#8217;t exactly the case, and the values were pretty strange &#8211; Daily averages of &#8216;lumbering-dinosaur&#8217; disk read times, absurd disk queues and ridiculously low page life expectancies. If you&#8217;re interested, you can read more about these metrics over at Microsoft TechNet, <a href=\"http:\/\/technet.microsoft.com\/en-us\/library\/cc938959.aspx\">here<\/a>, <a href=\"http:\/\/technet.microsoft.com\/en-us\/library\/bb402839.aspx\">here<\/a> and <a href=\"http:\/\/technet.microsoft.com\/en-us\/library\/cc966412.aspx\">here<\/a>.<\/p>\n<p>\u00a0<em>(I automated this collection rather nicely- The Perfmon captured data from this database to a .csv file in 15 seconds, and a SQL Server job uploaded that file into a table. I will be posting this routine at a later date, as it is very simple and functional, and I am always in favor of simplicity<\/em>)<\/p>\n<p>The worst thing about this situation was that in two days time we would be getting 4 more subsidiaries and an online Warehouse. There&#8217;s no way the disc would bear up to that kind of load, so I had two days to do something drastic. I could investigate with the Profiler easily enough, but there was no way I&#8217;d have time to rewrite the queries (which <em>were<\/em> very poorly written; the cursors looked like Christmas lights, and anyone who knows me knows that I think of them). Basically, I needed some kind of miracle, and luckily that&#8217;s exactly what was available in the fantastic &#8220;missing indexes&#8221; DMVs. In case you&#8217;ve never seen these before, they are:<\/p>\n<table>\n<tbody>\n<tr>\n<td valign=\"top\">\n<p><a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms345421.aspx\">sys.dm_db_missing_index_group_stats (Transact-SQL)<\/a><\/p>\n<\/td>\n<td valign=\"top\">\n<p>Returns summary information about missing index groups. For example, the performance improvements that could be gained by implementing a specific group of missing indexes<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms345407.aspx\">sys.dm_db_missing_index_groups (Transact-SQL)<\/a><\/p>\n<\/td>\n<td valign=\"top\">\n<p>Returns information about a specific group of missing indexes, such as the group identifier and the identifiers of all missing indexes that are contained in that group.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms345434.aspx\">sys.dm_db_missing_index_details (Transact-SQL)<\/a><\/p>\n<\/td>\n<td valign=\"top\">\n<p>Returns detailed information about a missing index. For example, it returns the name and identifier of the table where the index is missing, and the columns and column types that should make up the missing index.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms345364.aspx\">sys.dm_db_missing_index_columns (Transact-SQL)<\/a><\/p>\n<\/td>\n<td valign=\"top\">\n<p>Returns information about the database table columns that are missing an index.<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>&#8211; <em>Retrieved from BOL 2008.<\/em><\/p>\n<p>You can find out more about them <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms345524.aspx\">here<\/a>. Now, I&#8217;m all for not reinventing the wheel, so I went to my SQL Server bible, <a href=\"http:\/\/www.sqlservercentral.com\/\">SQLServerCentral<\/a>, to find an implementation. I found the <a href=\"https:\/\/www.sqlservercentral.com\/scripts\/the-ultimate-missing-index-finder\">Util_MissingIndexes<\/a> script by <strong>Jesse Roberge <\/strong>(with many thanks for the author) which, taking a table as a parameter, &#8220;<em>reports stats on what the query optimizer records in the DMVs as &#8216;missing indexes&#8217;, and what it says the cost savings will be if they were present<\/em>&#8220;. It returns a few columns, but the most important are:<\/p>\n<table>\n<tbody>\n<tr>\n<td valign=\"top\">\n<p><strong>Column <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p><strong>Description <\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>unique_compiles <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>Number of compilations and recompilations that would benefit from this missing index group. Compilations and recompilations of many different queries can contribute to this column value.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>user_seeks <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>Number of seeks caused by user queries that the recommended index in the group could have been used for.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>last_user_seek <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>Date and time of last seek caused by user queries that the recommended index in the group could have been used for.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>avg_total_user_cost<\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>Average cost of the user queries that could be reduced by the index in the group.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>equality_column<\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>Comma-separated list of columns that contribute to equality predicates of the form:<br \/>\n<em>table.column<\/em> = <em>constant_value<\/em><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>inequality_columns<\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>Comma-separated list of columns that contribute to inequality predicates, for example, predicates of the form:<br \/>\n<em>table.column<\/em> &gt; <em>constant_value<\/em><br \/>\nAny comparison operator other than &#8220;=&#8221; expresses inequality..<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>included_columns <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>Comma-separated list of columns needed as covering columns for the query.<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><em>&#8211; Retrieved from BOL 2008.<\/em><\/p>\n<p>The columns that were used in my case were user_seeks, last_user_seek ,avg_user_impact and, obviously, columns suggested as an index. Before I show you what these DMVs can do, I&#8217;ll cut to the chase now and tell you that, on the day that the subsidiaries and Warehouse were due to be brought online, and with two hours to spare, I finished the improvements and the (massively) improved counters were:<\/p>\n<table>\n<tbody>\n<tr>\n<td colspan=\"4\" valign=\"top\">\n<p><em><strong>Physical Disk<\/strong><\/em><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>Date <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p><strong>% Disk Read Time <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p><strong>Avg Disk Queue length<\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p><strong>Avg Disk Sec\/transfer <\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>2009\/07\/31 <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>1225<\/p>\n<\/td>\n<td valign=\"top\">\n<p>129<\/p>\n<\/td>\n<td valign=\"top\">\n<p>12.98293<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>2009\/08\/01 <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>340\u00a0\u00a0\u00a0<\/p>\n<\/td>\n<td valign=\"top\">\n<p>74<\/p>\n<\/td>\n<td valign=\"top\">\n<p>0.99393<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>2009\/08\/02<\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>37<\/p>\n<\/td>\n<td valign=\"top\">\n<p>0.16785<\/p>\n<\/td>\n<td valign=\"top\">\n<p>0.38722<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>2009\/08\/03 <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>20<\/p>\n<\/td>\n<td valign=\"top\">\n<p>0.15478<\/p>\n<\/td>\n<td valign=\"top\">\n<p>0.12452<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><\/p>\n<table>\n<tbody>\n<tr>\n<td colspan=\"2\" valign=\"top\">\n<p><strong><em>SQL Server<\/em><\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>Date <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p><strong>Page Life Expectancy <\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>2009\/07\/31 <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>900<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>2009\/08\/01 <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>1400<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>2009\/08\/02<\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>7000<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><strong>2009\/08\/03 <\/strong><\/p>\n<\/td>\n<td valign=\"top\">\n<p>6800<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Much better!<\/p>\n<h1>The DMV Surprise<\/h1>\n<p>When I set out to solve this problem, I focused on the avg_user_impact and user_seek columns because I was more interested in improving 40% of queries by a factor of 50 than 90% of queries by factors of just 2 or 3. \u00a0Admittedly, once I was done with that I had a second job of reviewing the unused and little-used indexes, review queries, take off cursors etc., but that&#8217;s an ongoing project, and not something I&#8217;m going to even mention here. I didn&#8217;t bother with fill factor at this point (although it <em>is<\/em> very important) because I just didn&#8217;t have time to analyze the load that these indexes would come under. As it turns out, I would be in a better position monitoring them <em>after<\/em> they were being used, and this is something I&#8217;ll tell you about another time.<\/p>\n<p>To tell the truth I was <em>expecting<\/em> an improvement, just not one like this, as I am deeply skeptical of anything suggested automatically. Sure, I&#8217;ll use the Index Tuning Wizard very occasionally, but I really prefer to manually review queries. In this case, because of those phenomenal improvements to the numbers, I decided to understand a little more about these DMV&#8217;s, and I had some questions:<\/p>\n<ul>\n<li>When a query which uses this index is run, is the <em>user_seeks<\/em> column really updated?<\/li>\n<li>When a query which uses this index is run, is the <em>last_user_seek<\/em> column really updated?<\/li>\n<li>When columns are suggested for the index, does the optimizer take into account selectivity, density and other statistical information to make that suggestion, or does it just look at the order of WHERE clauses?<\/li>\n<li>Does the avg_user_impact column report the actual percentage of potential performance improvement?<\/li>\n<\/ul>\n<h1>Does the Query Optimizer know what it&#8217;s doing?<\/h1>\n<p>To help slake my curiosity, I decided to do some tests. I created a table of 1000000 rows with different selectivity between the columns:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">CREATE TABLE [dbo].[TestDmv](\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0[Codigo1] [int] NOT NULL,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0[Codigo2] [int] NULL,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0[Codigo3] [int] NULL,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0[Codigo4] [int] NULL,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0[Field1] [varchar](50) NULL,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0[Field2] [varchar](50) NULL,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0[Field3] [varchar](50) NULL,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0[Field4] [varchar](50) NULL,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0CONSTRAINT [PK_TestDmv] PRIMARY KEY CLUSTERED\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0[Codigo1] ASC\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0= OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]\r\n\u00a0\u00a0\u00a0\u00a0) ON [PRIMARY]<\/pre>\n<p>&#8230; and then populated the columns:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">set nocount on\r\ndeclare @Codigo1 int = 0\r\ndeclare @Codigo2 int = 0\r\ndeclare @Codigo3 int = 0\r\ndeclare @Codigo4 int = 0\r\nwhile @Codigo1 &lt; 1000000\r\nbegin\r\n\u00a0\u00a0\u00a0\u00a0if (@Codigo1 between 0 and 1000) or\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(@Codigo1 between 2000 and 5000) or\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(@Codigo1 between 7000 and 9000)or\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(@Codigo1 between 10000 and 20000) or\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(@Codigo1 between 30000 and 40000) or\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(@Codigo1 between 40000 and 60000) or\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(@Codigo1 between 50000 and 80000) or\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(@Codigo1 between 80000 and 100000) or\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(@Codigo1 between 100000 and 200000) or\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(@Codigo1 between 300000 and 400000) or\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(@Codigo1 between 500000 and 500100) or\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(@Codigo1 between 600000 and 700000) or\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(@Codigo1 between 800000 and 900000) \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0set @Codigo2 = @Codigo2\r\n\u00a0\u00a0\u00a0\u00a0else\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0set @Codigo2 = @Codigo2 + 1 \r\n\u00a0\u00a0\u00a0\u00a0if (@Codigo1 between 30000 and 60000) \r\n\u00a0\u00a0\u00a0\u00a0OR (@Codigo1 between 200000 and 200100) \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0set @Codigo3 = @Codigo3 \r\n\u00a0\u00a0\u00a0\u00a0else\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0set @Codigo3 = @Codigo3 + 1\r\n\u00a0\u00a0\u00a0\u00a0if (@Codigo1 between 10000 and 20000) \r\n\u00a0\u00a0\u00a0\u00a0set @Codigo4 = @Codigo4 + 1\r\n\u00a0\u00a0\u00a0\u00a0else\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0set @Codigo4 = @Codigo4 \r\n\u00a0\u00a0\u00a0\u00a0insert into TestDmv(Field1,Field2,Field3,Field4,Codigo1,Codigo2,Codigo3,Codigo4) \u00a0\u00a0\u00a0\u00a0values ('field1 ' + CAST(@Codigo1 as CHAR(10)),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0'field2 ' + CAST(@Codigo2 as CHAR(10)),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0'field3 ' + CAST(@Codigo3 as CHAR(10)),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0'field4 ' + CAST(@Codigo4 as CHAR(10)),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0@Codigo1,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0@Codigo2,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0@Codigo3,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0@Codigo4\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0) \r\n\u00a0\u00a0\u00a0\u00a0set @Codigo1 = @Codigo1 + 1 \r\nend <\/pre>\n<p>Then I ran the Missing Indexes process:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/837-Missing_index_DMVs_clip_image002.jpg\" alt=\"837-Missing_index_DMVs_clip_image002.jpg\" \/><\/p>\n<p>No suggestions yet, so I ran this query next:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SELECT field1,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0field2,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0field3,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0field4\r\n\u00a0\u00a0\u00a0\u00a0FROM testdmv\r\n\u00a0\u00a0\u00a0\u00a0WHERE Codigo1 = 10 \r\nGO\r\nEXEC Util_MissingIndexes '','testdmv'<\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/837-Missing_index_DMVs_clip_image002_0000.jpg\" alt=\"837-Missing_index_DMVs_clip_image002_000\" \/><\/p>\n<p>Still no suggestions; the clustered index was used. Moving on to the next test, with this query&#8230;<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SELECT field1,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0field2,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0field3,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0field4\r\n\u00a0\u00a0\u00a0\u00a0FROM testdmv\r\n\u00a0\u00a0\u00a0\u00a0WHERE Codigo3 BETWEEN 10 \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND 10000\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND Codigo2 BETWEEN 1 \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND 1000 \r\nGO\r\nEXEC Util_MissingIndexes '','testdmv' <\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/837-Missing_index_DMVs_clip_image004.jpg\" alt=\"837-Missing_index_DMVs_clip_image004.jpg\" \/><\/p>\n<p>&#8230; Woohoo! I had something:<\/p>\n<ul>\n<li>user_seeks : 1<\/li>\n<li>last_user_seek : 2009-09-30 23:06:04.820<\/li>\n<\/ul>\n<p>I ran the query again&#8230;<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/837-Missing_index_DMVs_clip_image006.jpg\" alt=\"837-Missing_index_DMVs_clip_image006.jpg\" \/><\/p>\n<ul>\n<li>user_seeks : 2<\/li>\n<li>last_user_seek : 2009-09-30 23:14:22.927<\/li>\n<\/ul>\n<p>It looks like we can trust the updates to the user_seek and last_user_seek Columns, so that was my first two questions pretty quickly answered &#8211; so far so good. Using the result of that little procedure, I could also see that some columns had been suggested as indexes:<\/p>\n<ul>\n<li>Equality_columns = NULL<\/li>\n<li>Inequality_columns = [Codigo2], [Codigo3]<\/li>\n<li>Included_columns = [Field1], [Field2], [Field3], [Field4]<\/li>\n<\/ul>\n<p>Why was the equality_columns suggestion null, you ask? Because of my query conditions, which didn&#8217;t use the equality operator (=), but rather &#8220;<strong>between &#8230;<\/strong>&#8221; operators which were populated with values.<\/p>\n<p>SQL server does not always need to go into the data pages to respond to a query if \u00a0the columns requested by the query are fully &#8220;covered&#8221; or documented in the index, hence the term &#8220;covered index&#8221;. If they are <em>not<\/em> covered, SQL server has to go to the data pages to return the missing columns. This process is called BookMark Lookup (SQL2K) or RID Lookup (SQL2K5), and is computationally expensive to perform.<\/p>\n<p>Yet the index order suggested by the Optimizer was <em>Codigo2, Codigo3<\/em> and not <em>Codigo3, Codigo2<\/em>, as I wrote in my WHERE clause, and this could potentially be an &#8216;expensive&#8217; index. Clearly, in this case. the order would not affect the use of the index, but the optimizer is smart enough to switch the order around if necessary, so there must have been <em>some<\/em> reason for the ordering. I created the index as suggested to see if I could figure it out:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">CREATE INDEX idx_test_01 ON testdmv (Codigo2,Codigo3)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0include (field1,field2,field3,field4)\r\nGO\r\nEXEC Util_MissingIndexes '','testdmv'<\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/837-Missing_index_DMVs_clip_image008.jpg\" alt=\"837-Missing_index_DMVs_clip_image008.jpg\" \/><\/p>\n<p>As you can see, the suggested index did not appear, so I decided to take a look at the IO statistics and execution plan:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">TABLE 'TestDmv'. Scan COUNT 1, logical reads 52, physical reads 0, READ-ahead reads 0, lob logical reads 0, lob physical reads 0, lob READ-ahead reads 0. <\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/837-Missing_index_DMVs_clip_image010.jpg\" alt=\"837-Missing_index_DMVs_clip_image010.jpg\" \/><\/p>\n<p>Now that I had another benchmark to compare against, I switched the order of the WHERE clause to <em>Codigo3, Codigo2<\/em>, and the result was:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">TABLE 'TestDmv'. Scan COUNT 1, logical reads 123, physical reads 0, READ-ahead reads 0, lob logical reads 0, lob physical reads 0, lob READ-ahead reads 0. <\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/837-Missing_index_DMVs_clip_image012.jpg\" alt=\"837-Missing_index_DMVs_clip_image012.jpg\" \/><\/p>\n<p>You can see that besides the logical reads, the CPU and IO costs increased, so it looks like the query optimizer knows what it&#8217;s doing. Next I needed to try using a WHERE clause with the equality operator.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SELECT field1,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0field2,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0field3,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0field4\r\n\u00a0\u00a0\u00a0\u00a0FROM testdmv\r\n\u00a0\u00a0\u00a0\u00a0WHERE Codigo4 = 10001\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND Codigo3 = 30000 \r\nGO\r\nEXEC Util_MissingIndexes '','testdmv'<\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/837-Missing_index_DMVs_clip_image014.jpg\" alt=\"837-Missing_index_DMVs_clip_image014.jpg\" \/><\/p>\n<p>As before, there was only data in the inequality column because the WHERE clause only <em>had<\/em> equality conditions. And also as before, the columns suggested as indexes were in a different order from the WHERE clause. At this point, I decided to investigate from a different angle &#8211; I wanted to see the selectivity of these columns, using a script written by Nilton Pinheiro.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SELECT [Total Lines] = COUNT(*),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0[Distinct Lines] = COUNT(DISTINCT &lt;COLUMN&gt;),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0-- the closer to 1, the better\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0[selectivity] = COUNT(DISTINCT &lt;COLUMN&gt;)\/CAST( COUNT(*) AS DEC(10,2))\r\n\u00a0\u00a0\u00a0\u00a0FROM &lt;yourtable&gt; <\/pre>\n<p>So, what would the selectivity of the Codigo3 and Codigo4 columns be?<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SELECT [Total Lines] = COUNT(*),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0[Distinct Lines] = COUNT(DISTINCT Codigo3),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0-- the closer to 1, the better\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0[selectivity] = COUNT(DISTINCT Codigo3)\/CAST( COUNT(*) AS DEC(10,2))\r\n\u00a0\u00a0\u00a0\u00a0FROM testdmv\r\n\r\nSELECT [Total Lines] = COUNT(*),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0[Distinct Lines] = COUNT(DISTINCT Codigo4),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0-- the closer to 1, the better\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0[selectivity] = COUNT(DISTINCT Codigo4)\/CAST( COUNT(*) AS DEC(10,2))\r\n\u00a0\u00a0\u00a0\u00a0FROM testdmv\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Codigo3\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Total Lines DISTINCT Lines selectivity\r\n----------- -------------- ---------------------------------------\r\n1000000 \u00a0\u00a0\u00a0\u00a0969898 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a00.96989800000\r\n(1 row(s) affected)\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Codigo4\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Total Lines DISTINCT Lines selectivity\r\n----------- -------------- ---------------------------------------\r\n1000000\u00a0\u00a0\u00a0\u00a0\u00a010002\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 0.01000200000\r\n(1 row(s) affected) <\/pre>\n<p>It turns out that the Codigo3 column was much more selective than Codigo4 &#8211; Another goal for the optimizer! But now I wondered what would happen if I had one WHERE clause using both equality and inequality conditions?<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SELECT field1,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0field2,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0field3,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0field4\r\n\u00a0\u00a0\u00a0\u00a0FROM testdmv\r\n\u00a0\u00a0\u00a0\u00a0WHERE Codigo2 BETWEEN 100 \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND 100000\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND Codigo4 = 10001\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND Codigo3 = 30000 \r\nGO\r\nEXEC Util_MissingIndexes '','testdmv' <\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/837-Missing_index_DMVs_clip_image016.jpg\" alt=\"837-Missing_index_DMVs_clip_image016.jpg\" \/><\/p>\n<ul>\n<li>Equality_columns = [Codigo3],[Codigo4]<\/li>\n<li>Inequality_columns = [Codigo2]<\/li>\n<li>Included_columns = Null<\/li>\n<\/ul>\n<p>By now I was pretty convinced that the optimizer knew what it was doing. In this case, I would simply create the index, putting the equality columns first and inequality after them, as we can find in the best practices on BOL:<br \/>\n<em>Use the following guidelines for ordering columns in the CREATE INDEX statements you write from the missing indexes feature component output:<\/em><\/p>\n<ul>\n<li><em>List the equality columns first (leftmost in the column list).<\/em><\/li>\n<li><em>List the inequality columns after the equality columns (to the right of equality columns listed).<\/em><\/li>\n<li><em>List the include columns in the INCLUDE clause of the CREATE INDEX statement.<\/em><\/li>\n<\/ul>\n<p>&#8230;so the index should always in this order (equality first and inequality after).<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">CREATE INDEX Idx_test_02 ON testdmv(Codigo3,Codigo4,Codigo2)<\/pre>\n<p>So, my third question was now answered &#8211; The Optimizer does not use the ordering of the WHERE clause, but rather suggests the best indexes based in your statistics (By this point, I did not expect anything less from the optimization team).<\/p>\n<p>Finally, I wanted to see if the avg_total_user_cost value really was consistent with real performance:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">DBCC dropcleanbuffers\r\nGO\r\nSET STATISTICS time ON\r\nGO\r\nSELECT field1,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0field2,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0field3,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0field4\r\n\u00a0\u00a0\u00a0\u00a0FROM testdmv\r\n\u00a0\u00a0\u00a0\u00a0WHERE Codigo2 BETWEEN 100 \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND 1000000\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND Codigo4 = 10001\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND Codigo3 = 30000\r\n\u00a0\u00a0\u00a0\u00a0(30002 row(s) affected)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0SQL Server Execution Times:\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0CPU time = 423 ms, elapsed time = 4471 ms. <\/pre>\n<p>I created the index&#8230;<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">CREATE NONCLUSTERED INDEX Idx_test_03\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ON [dbo].[TestDmv] ([Codigo3],[Codigo4],[Codigo2]) <\/pre>\n<p>And ran the same query again, to see these results:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SQL Server Execution Times:\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0CPU time = 31 ms, elapsed time = 1308 ms. <\/pre>\n<p>Well, given that the predicted avg_user_impact was 98.71%, and I had 4471 ms brought down to1308 ms, the data from DMVs was pretty close. My last question was answered &#8211; The avg_total_user_cost is a &#8216;real&#8217; number, or at least is very, very near the mark.<\/p>\n<p>This is possibly one of the better features included in SQL Server. Obviously these indexes are only <em>suggested<\/em>, and we have to bear in mind a few other concerns too, like the fact that all INSERT, DELETE and UPDATE operations will be affected by these suggestions and may become very slow if we create too many indexes on the table. Nor can we forget that these indexes will also be using disk space. So analyze the potential impact before creating the them, but one thing I can tell you for sure is that we can trust the data of the &#8220;Missing indexes&#8221; DMVs. I hope this fantastic feature can help you like it helped me.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Laerte recently experienced an I\/O nightmare, which, as a happy accident, gave him an opportunity to test out the &#039;Missing Indexes&#039; DMVs and see if they were up to scratch. He solved his problems, ran a few tests to get a feel for how well the DMVs performed, and was very impressed. So much so, that he is sharing his findings with us.&hellip;<\/p>\n","protected":false},"author":221715,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143529],"tags":[4450,5064,5065,4206,4150],"coauthors":[6819],"class_list":["post-703","post","type-post","status-publish","format-standard","hentry","category-performance-sql-server","tag-dmv","tag-io","tag-indexes","tag-performance","tag-sql"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/703","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/221715"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=703"}],"version-history":[{"count":7,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/703\/revisions"}],"predecessor-version":[{"id":84269,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/703\/revisions\/84269"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=703"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=703"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=703"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=703"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}