<?xml version="1.0"?>
<rss version="2.0" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/" xmlns:yt="http://gdata.youtube.com/schemas/2007" xmlns:atom="http://www.w3.org/2005/Atom">
   <channel>
      <title>MySQL Optimizer Blogs</title>
      <description>Pipes Output</description>
      <link>http://pipes.yahoo.com/pipes/pipe.info?_id=0066154c1d41b9238be9323a4daa05a1</link>
      <atom:link rel="next" href="http://pipes.yahoo.com/pipes/pipe.run?_id=0066154c1d41b9238be9323a4daa05a1&amp;_render=rss&amp;page=2"/>
      <pubDate>Thu, 01 Oct 2015 23:05:39 +0000</pubDate>
      <generator>http://pipes.yahoo.com/pipes/</generator>
      <item>
         <title>Slides from Percona Live and airbnb Tech Talks</title>
         <link>http://oysteing.blogspot.com/2015/04/slides-from-percona-live-and-airbnb.html</link>
         <description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align:left;&quot;&gt;&lt;h2 style=&quot;text-align:left;&quot;&gt;&lt;/h2&gt;&lt;div style=&quot;text-align:left;&quot;&gt;Last week I presented my talk, &quot;How to Analyze and Tune SQL Queries for Better Performance&quot; both at Percona Live in Santa Clara and at airbnb Tech Talks in San Francisco.&amp;nbsp; The slides are available on &lt;a rel=&quot;nofollow&quot; class=&quot;notranslate&quot; target=&quot;_blank&quot; href=&quot;http://www.slideshare.net/oysteing/how-to-analyze-and-tune-sql-queries-for-better-performance-percona15&quot; title=&quot;How to analyze and tune sql queries for better performance percona15&quot;&gt;slideshare&lt;/a&gt;. A video recording from the airbnb talk should eventually be available the&amp;nbsp;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://nerds.airbnb.com/tech-talks/&quot;&gt;airbnb Tech Talks page&lt;/a&gt;. &lt;/div&gt;&lt;/div&gt;</description>
         <author>Øystein</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-1508669603650457962.post-2680510090598336621</guid>
         <pubDate>Tue, 21 Apr 2015 21:14:00 +0000</pubDate>
      </item>
      <item>
         <title>MySQL Webinar: Analyze &amp; Tune Queries for Better Performance</title>
         <link>http://oysteing.blogspot.com/2015/02/mysql-webinar-analyze-tune-queries-for_25.html</link>
         <description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align:left;&quot;&gt;&lt;div class=&quot;post-title entry-title&quot; style=&quot;text-align:left;&quot;&gt;Thanks to everyone who attended my webinar today, and thanks for all the positive feedback in the Q&amp;amp;A session.&amp;nbsp; Unfortunately, I was not able to respond to everyone during the 15 minutes available for Q&amp;amp;A.&amp;nbsp; If your question did not get answered, feel free to use the comments section of this blog post to ask your question.&amp;nbsp; You can also ask questions on the &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://forums.mysql.com/list.php?115&quot;&gt;MySQL Optimizer Forum&lt;/a&gt;.&lt;/div&gt;&lt;div class=&quot;post-title entry-title&quot; style=&quot;text-align:left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;post-title entry-title&quot; style=&quot;text-align:left;&quot;&gt;The slides from the presentation are available &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://www.slideshare.net/oysteing/how-to-analyze-and-tune-sql-queries-for-better-performance-webinar&quot;&gt;here&lt;/a&gt;.&amp;nbsp; In a few days, I expect it to be possible to access it as an &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://www.mysql.com/news-and-events/on-demand-webinars/&quot;&gt;MySQL On-Demand Webinar&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;</description>
         <author>Øystein</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-1508669603650457962.post-4057570537484875993</guid>
         <pubDate>Wed, 25 Feb 2015 19:49:00 +0000</pubDate>
      </item>
      <item>
         <title>MySQL Webinar: Analyze &amp; Tune Queries for Better Performance</title>
         <link>http://oysteing.blogspot.com/2015/02/mysql-webinar-analyze-tune-queries-for.html</link>
         <description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align:left;&quot;&gt;On Wednesday, February 25 at 18:00 CET (9 am Pacific Time), I will do webinar on how to analyze and tune MySQL queries for better performance. &lt;br /&gt;&lt;br /&gt;The webinar covers how the MySQL optimizer chooses a  specific plan to execute SQL queries. I will also show you how to use tools  such as EXPLAIN (including the new JSON-based output) and Optimizer  Trace to analyze query plans. We will also review how the Visual Explain  functionality available in MySQL Workbench helps us visualize these  plans. The webinar will also contain several examples of how to take  advantage of the query analysis to improve performance of MySQL queries.&lt;br /&gt;&lt;br /&gt;The presentation will be approximately 60 minutes long followed by Q&amp;amp;A.&lt;br /&gt;&lt;br /&gt;For details on how to register for the webinar visit&amp;nbsp;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://www.mysql.com/news-and-events/web-seminars/analyze-tune-queries-for-better-performance/&quot;&gt;http://www.mysql.com/news-and-events/web-seminars/analyze-tune-queries-for-better-performance/&lt;/a&gt; &lt;/div&gt;</description>
         <author>Øystein</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-1508669603650457962.post-7046110453950225731</guid>
         <pubDate>Wed, 18 Feb 2015 20:23:00 +0000</pubDate>
      </item>
      <item>
         <title>When ONLY_FULL_GROUP_BY Won’t See the Query Is Deterministic…</title>
         <link>http://guilhembichot.blogspot.com/2015/01/when-onlyfullgroupby-wont-see-query-is.html</link>
         <description>Hi! Just to say I wrote this new &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://mysqlserverteam.com/when-only_full_group_by-wont-see-the-query-is-deterministic/&quot;&gt;post&lt;/a&gt; about&lt;i&gt; only_full_group_by&lt;/i&gt; tricks, on the MySQL Server team's blog.</description>
         <author>Guilhem Bichot</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-9191231274999121063.post-8306094222831111900</guid>
         <pubDate>Mon, 19 Jan 2015 02:27:00 +0000</pubDate>
      </item>
      <item>
         <title>MySQL 5.7: only_full_group_by Improved, Recognizing Functional Dependencies, Enabled by Default!</title>
         <link>http://guilhembichot.blogspot.com/2014/12/mysql-57-onlyfullgroupby-improved.html</link>
         <description>I just posted on &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://mysqlserverteam.com/mysql-5-7-only_full_group_by-improved-recognizing-functional-dependencies-enabled-by-default/&quot;&gt;the Server team's blog&lt;/a&gt;, an account on my recent &lt;i&gt;only_full_group_by&lt;/i&gt; work. We have made significant improvements in 5.7.5, worth a look!</description>
         <author>Guilhem Bichot</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-9191231274999121063.post-4347346483631976151</guid>
         <pubDate>Wed, 10 Dec 2014 05:36:00 +0000</pubDate>
      </item>
      <item>
         <title>Speaking at MySQL Central @ Open World</title>
         <link>http://oysteing.blogspot.com/2014/09/speaking-at-mysql-central-open-world.html</link>
         <description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align:left;&quot;&gt;&lt;div class=&quot;separator&quot; style=&quot;clear:both;text-align:center;&quot;&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;https://www.blogger.com/blogger.g?blogID=1508669603650457962&quot; style=&quot;margin-left:1em;margin-right:1em;&quot;&gt;&lt;img alt=&quot;&quot; border=&quot;0&quot; src=&quot;data:image/png;base64,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GXED9kDsQjDBAABytGFsIl8irgBOM4x/jjghpxFyKxTdA+YvgTQBg5CiGDQHKHgawDcK8NDyiJSQG4kJBhBCYmCCAY2JEYNYYyAQg1gM9pACLPpy/a0vESdwnWFUQ6P2hQaYMTALWgM4QJaCAUbet8PL99yrbPdIENdau55bUVnx6aefbt261bIs27GFFFyIvGvMR6gzZ858/PHH77rrrj59+mz/bLtSyrbtzZ9uHjx4cP/+/d95553hw4ev37DedV0p5aZNm0aOHPnuu+/279+/b9++77333pYtW3zfX75i+dChQ5csWeK4Tn5L0L379n344YcHyw7W1NYOHDhww8YNjuNwzm3b/uSTT4YOHbp06VJta0qp67q7du1699139+zZk83ltn+2/a0eb/32t799/PHHp0+fLoTwfC+RSCxatGjQoEF9+vR5u+/bo0aN2rhxY/6SqqqqJkyYsHLlykNT/MK27erq6kmTJg0cOLBfv379+/efPXt2WXlZ/hZqWL/lB2bfAeK7IXb+IcSPIcbmI0A8L5RXAZxAAAiBJkWNYgSgEZB8XtyAIhMIXAawHQrQKMRDm6oxgBuMhoQ7gAGFAMQAKII8qYRsIo2DgtgXWyh+iTiQq4EwEhtkkphBwDAByBLTXACNwAAwIAbw8n33uY79TxG3HVtpNXDgwI4dO1577bVXX311586d129Yn8lm8qs45H1q3759mzRp0q5du86dO59yyinnnnvuli1bEXHAgAEAcMEFF1xxxRUnnXTSCSecMGbMmCiKevXqBQAXXnjhL3/5y8suu6xNmzZTp05FxCeeeAIALrnkkoqKCt/3oyiaXFxcEIutW7+uvKK8sLDw0Ucf9X1fKhlF0ZNPPgkA11xzjeM6SinP93r37l1YWLhq1ar9+/efd955J5100t133922bdumTZv26dPHT/uWZd14441Nmza9/PLLL7/88hYtWpxyyikLFizIZDIbN25s1arV448/bju20ppx5rrupk2bzjzzzKOPPvqKK6646KKLCgsLf/Ob3+zevdtxnLze+U8j/XX7ThAvOB9iDYiDsfkIhEoe8RUAxxkAhgGxxkcbZhEQiOWlOflJAXkAYAMhkRFDYjQszEmAEbjJyAsLs4jEfmeY/Yyi3qSwn2F0N4x3wag2YoH5rYjDr8FkhllL4GwAIACk8RNm0Shonk/pEIAX7u/s28r6Z/3ijusMGjTo7LPPHjRoUElJSUlJyb333nvVVVeVlpa6rmtZVjaX3bZtW4sWLW655RbGGSLOnTu3RYsWjz32WBiGAwcNAoAFCxYkEokNGza0bt36nHPOyWQz/fr1A4CPS0qqq6vLysv2799fV1+HiH/605+OPfbYZs2avffee57nIWJxcXGjRo1KSkocx+nQocNVV11VWVmZyWSSyWTHjh2PP/74Sy+9tKyszPM8yujtt93etm3bqqqq5557rkWLFjNnzUREIcRdd9119FFH79ixg3N+/fXXt2/ffvfu3XX1ddNnzDjhhBNuueUW3/e3b9/+85///Omnn/Z8jwuev2c2bdrUqlWrhx9++ODBg1VVVSNGjIjFYk888UQul8u7gP8w0d+wfxlxE3aT2Pkklkfza4hrIKeQPD9GM4OsOsR33iUvBTjOAEKICUVnkSZTSGy1CWsM+BQK95JGNiFokIhAmkAQg5wJaAIzoBPJ1zULWhJzCRA0CpCYDUlDMDMmhF9ZPPGrWrxdrChBitCABw0CMSCkyfnQ6BU4ihyKB567/05f83+MuO3o+nj9tddeO3HixH379vXs2bO4uHjWrFnt27cfPXq0n05zIVzPXbZsGQC8//5gRNSOzTm/4YYbLr30knQ6/beBAwFg2/Zt+T6xJ554onHjxikr1a9fP0JI6cFSRExn0mEYSikR8fHHH2/Tps0tt9zSpk2b2traPOJNmzUtKSkJw7B79+5HHXXUhg0bEHHturXnnntuly5dzjrrrJkzZ0aI+w/sP/bYY7t06ZLL5a644oqrr77acRylFSKOHz8eAJYsWSKV7NSp00033WTbDesstW3b9qKLLvJ8b+u2ra1aterydBdt20IKKaWf9vOIv/jii57vZTIZP+23bdu2TZs2nHMp/+sQjwiEhUYdkDYAUEgKCmJNgKzOl0JNQAOSpNGJRgwgBkCOipF1AEhIhpCcSdCA5QYcewivK8HkX86eEfzKHNqX22QCcICOecQJaVoAs6BhJcQvtocNDUjHSPbQ6V8ibkKHglgSYhgj80lRIQGDkMYkdhwpLDh0Dd1+e7/tf12o5D23amjGkrZjb9u27aqrrtq1a9dtt9120UUXnXrqqV2e7vLyyy+/+uqrSkmpldZ65syZhYWFI0aMCKMwXzy6/fbbL774Ys/zBg4cCABbt25FRMuyOnbseNxxxzmO07dv34KCgi1btyqtk8lkXX0dYwwRH3300WuvvXbW7FlNmzYdPXo0Ik6fMaN58+bLli1DxGnTpgHAlClTEHHMmDHnnXfesuXLTjvttN69eyPismXLmjZtOmbMmDCKLrjggg4dOniep5SKomjKlCkAMH/+fCnl9ddff+211x44cIALvmLFipNPPvnpp59OZzJbtmxp1apVly5dvtjy0/XczZs3n3HGGd26ddO2Vlo7jv3QQw9deOGF+/bvyyv4/zTSX7d/PWlI0qTgWsMAMIugEAyYYwASkjUAY7C9oFFLYgKJAZjHx4xaI4YAESFpACRfQ9yo/4fjlfmDEehgAEBBAZjHGTCfAJoQGQ3LNOcaAtkvl37+arjZzohRiKFJqqGoTcyEGBQAFILxRafLs/f+/lsRz/9C8nkPx3GWLFlyy823zJ8///Sfn75q9ao333zzpZdfeu+995555hmLWkIK13OnT59umuaQoUPDKBJCSCVvvvnmSy65xE/777//PgDcf//9Xbt2bdeunWmar776aoRRv3f6GYbRvn37m2+++eabb7777rvLy8sR8Y9//GO7dlfV1tbedtttv/rVrzzPmzV7drNmzVasWIGIW7ZsOe2007p3757JZF588cVOnTpVVFS0a9fuoYceCsPw7bffbtWq1fbt2x3XPf/88zt06OC6bv5Tnzx5ch5xrfWdd93VskWLdu3aXX/99aeccsodd9xRVVWVyWa3bNlyxhlndO3a1XEdIaWQ0vO9vEDv2rWrbdu26yitHnroofPPP3/X7l2u6/4AVzT/DvLiCI1uLYyBYRaRZmCQdwg05KoBFhjmccQAswkYheeaxCeNESAyjZCQbyJedwSIS4CbAMAsiJGCYwEW54EmRl6po0nCgr/bBferWvxKaCigRlD4stEYCBCA2JdNX/D8fX+wvW8JN/OM5tsMXdedN2/eHbffMXHSxEsuvWTHjh1PPfXU4MGDe/fu/eSTT1rUUkq5njtr1qxYLDZkyJBcLue4LuPs9ttvP//8813P/WDYMAC4/PJfXH311ffdd9+QIUNSqRQi9u7d2zTNBx94sEuXLv/zP//T7Zlu1dXVEUaPPPxI2yuvTGfS06dPb9y48dKlS2fPnt20adOlS5dGUcQ5v+WWWzp16lRWXnbddde98sorQRB069bt6quvjsfjN99yc/tr2ruuSy3roosuuvbaa33fz385TJo8CQBmzZollbjppptOP/30xx577Kmnnrr4kkt+cfkvNm/enM6kN2/e3KpVq6efftpxXSmlEML3/QYv/kw3rbWf9rngf/zjH1u3br17927btv8LEY8AEAreIiYUATELwSC/NGGb2cwzmh0gRXcUgkmISRpDkfnnmBFCk8g4tA7yv4J4YQGYBceaRX8zYgehyQGjZSlpVAmxfaTR9lhhxoh9ixYH0tY0y838bISxBgry+cpYQwMBAMALv/29dvTX2rC0rfNRVH4/Zdd1169ff911102dOvWss856/PHHzznnnKuvvvrKK68cNmxYOpOWUnqeN3fu3FgsNmLkyGw267gOpfS222679NJLPc8bOnQoAKxevVprnbJSmUxGa53L5Xr27Gma5s5dO8MotG1bKa21DqPw0T8/2qZNG8oopfTXv/71PffcPXXq1BYtWixdujSby4ZR+MILL5x++umrVq86p/U548ePR8SxY8eedtppK1asuPjii5/p9kw+vrz44os7duzo+74QIpPNFH9UnI96lVLXXnttp06dLEoRcfac2c2bNf/ra6/lgmDr1q15xG3HzuPi+/6mzZvyrl0d6qF/6KGHLr744n379vm+z+gPbk/nfxXxNIGcYewH45zG0JDIA7gG4B6DtDOgqFFDE99PDFhIAEkBGuSLnUn+3xC/FQBMYhjEiBlHGfBTAi2I2ZyQlgRagPFriEnSLDK+BfFfm1BmNixR64NxDzQEwl8g/tI9f7BtdTjE86lux3Uqqyqvuuqq4uLiF1544corr+za9ek777yzV69e9fF6IQUXXGtdUlICAIMGDUJEpZRFrY4dO7Zp0yaTzQwbPgwANn26OYxCbWvOeT7r0rt371gstnfv3iAIbMdRWufDzQcefODKtm0TyQQijhgx4vTTT+/Wrdtxxx23atUqP+3nglxxcfGpp576yiuvXHLJJWvWrEHENWvXnHTSSV27dr3ssstmz5ntZ/z/096Xx8lVVfnfV90JihCWwAjqiKwuozPoCIGMIhCSgDIw6swo6ojiRiQMiEZmlIBRIAmEBJKQTmfprKTTne6k925639NLet+7eqm96u37q/W98/vjvKp0SDqJjj+B0Cf305+uynuvXvX73nPPPcv38AKP81AQBEVRAGDX7t2EkKbm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&quot; style=&quot;cursor:move;&quot;/&gt;&lt;/a&gt;&lt;/div&gt;I am speaking at two sessions at Oracle Open World this week; one tutorial and one hands-on-lab:&lt;br /&gt;&lt;ul style=&quot;text-align:left;&quot;&gt;&lt;li&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=3157&quot;&gt;How to Analyze and Tune MySQL Queries for Better Performance &lt;/a&gt;&lt;br /&gt;Monday, Sep 29, 10:15 AM&lt;/li&gt;&lt;li&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=9232&quot;&gt;MySQL EXPLAIN in Practice&amp;nbsp;&lt;/a&gt;&lt;br /&gt;Wednesday, Oct 1, 10:15 AM &lt;/li&gt;&lt;/ul&gt;I can also recommend the other sessions by members of the MySQL Optimizer Team:&lt;br /&gt;&lt;ul style=&quot;text-align:left;&quot;&gt;&lt;li&gt;Manyi Lu: &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=2830&quot;&gt;MySQL 5.7: What’s New in the Parser and the Optimizer?&lt;/a&gt;&lt;br /&gt;Wednesday, Oct 1, 11:30 AM &lt;/li&gt;&lt;li&gt;Olav Sandstå: &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=3163&quot;&gt;MySQL Cost Model&amp;nbsp;&lt;/a&gt;&lt;br /&gt;Wednesday, Oct 1, 4:45 PM  &lt;/li&gt;&lt;/ul&gt;&lt;div style=&quot;text-align:left;&quot;&gt;Looking forward to meeting and talking with you at Oracle Open World!&lt;/div&gt;&lt;div style=&quot;text-align:left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align:left;&quot;&gt; &lt;/div&gt;&lt;/div&gt;</description>
         <author>Øystein</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-1508669603650457962.post-1854484350321049307</guid>
         <pubDate>Mon, 29 Sep 2014 03:58:00 +0000</pubDate>
      </item>
      <item>
         <title>MySQL Webinar: MySQL EXPLAIN, explained</title>
         <link>http://oysteing.blogspot.com/2014/07/mysql-webinar-mysql-explain-explained.html</link>
         <description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align:left;&quot;&gt;Some time ago, Matt Lord and I delivered a webinar on the MySQL EXPLAIN feature.&amp;nbsp; This webinar is available for on-demand access &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://www.mysql.com/news-and-events/web-seminars/mysql-explain-explained/&quot;&gt;here&lt;/a&gt;.&amp;nbsp; Based on the questions we got during the webinar, I want to emphasize that EXPLAIN does not execute the query, it only determines the query plan for the query.&amp;nbsp; Hence, EXPLAIN will not be able to evaluate how good the chosen query plan actually is.&lt;br /&gt;&lt;br /&gt;If you have questions on this topic after listening to this webinar, feel free to ask questions; either as comments on this blog or at the &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://forums.mysql.com/list.php?115&quot;&gt;MySQL Optimizer Forum&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;You can also access other &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://www.mysql.com/news-and-events/on-demand-webinars/&quot;&gt;webinars on MySQL&lt;/a&gt;. New webinars will be announced &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://www.mysql.com/news-and-events/web-seminars&quot;&gt;here&lt;/a&gt;.&lt;/div&gt;</description>
         <author>Øystein</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-1508669603650457962.post-1558949319231645110</guid>
         <pubDate>Fri, 18 Jul 2014 14:17:00 +0000</pubDate>
      </item>
      <item>
         <title>A Multi-Table Trick to Speed up Single-Table UPDATE/DELETE Statements</title>
         <link>http://oysteing.blogspot.com/2014/07/a-multi-table-trick-to-speed-up-single.html</link>
         <description>&lt;style type=&quot;text/css&quot;&gt;&lt;!--div.codebox {height:100%;width:100%;background-color:#EEEEEE;padding:4px;}--&gt;&lt;/style&gt;&lt;p&gt;&lt;i&gt;This post appeared first on &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://mysqlserverteam.com/multi-table-trick/&quot;&gt;mysqlserverteam.com&lt;/a&gt;&lt;/i&gt;&lt;/p&gt; In MySQL, query optimization of single-table UPDATE/DELETE statements is more limited than for SELECT statements. I guess the main reason for this is to limit the optimizer overhead for very simple statements. However, this also means that optimization opportunities are sometimes missed for more complex UPDATE/DELETE statements.  &lt;h1&gt;Example&lt;/h1&gt;Using the&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://sourceforge.net/apps/mediawiki/osdldbt/index.php?title=Main_Page#dbt3&quot;&gt; DBT-3 database&lt;/a&gt;, the following SQL statement will increase prices by 10% on parts from suppliers in the specified country: &lt;br /&gt;&lt;div class=&quot;codebox&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;UPDATE part&lt;br /&gt;SET p_retailprice = p_retailprice*1.10&lt;br /&gt;WHERE p_partkey IN&lt;br /&gt;     (SELECT ps_partkey&lt;br /&gt;      FROM partsupp JOIN supplier&lt;br /&gt;      ON ps_suppkey = s_suppkey&lt;br /&gt;      WHERE s_nationkey = 4);&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;Visual EXPLAIN in &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://www.mysql.com/products/workbench/&quot;&gt;MySQL Workbench&lt;/a&gt; shows that the optimizer will choose the following execution plan for this UPDATE statement:&lt;/p&gt;&lt;p style=&quot;text-align:center;&quot;&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://mysqlserverteam.com/wp-content/uploads/2014/05/update-subquery.png&quot;&gt;&lt;img class=&quot;aligncenter  wp-image-1275&quot; style=&quot;border:1px solid black;&quot; title=&quot;Execution Plan for UPDATE&quot; alt=&quot;update-subquery&quot; src=&quot;http://mysqlserverteam.com/wp-content/uploads/2014/05/update-subquery.png&quot; width=&quot;409&quot; height=&quot;295&quot;/&gt;&lt;/a&gt;&lt;/p&gt;That is, for every row in the &lt;tt&gt;part&lt;/tt&gt; table, MySQL will check if this part is supplied by a supplier of the requested nationality.  Consider the following similar SELECT statement: &lt;br /&gt;&lt;div class=&quot;codebox&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;SELECT * FROM part &lt;br /&gt;WHERE p_partkey IN &lt;br /&gt;     (SELECT ps_partkey &lt;br /&gt;      FROM partsupp JOIN supplier &lt;br /&gt;      ON ps_suppkey = s_suppkey &lt;br /&gt;      WHERE s_nationkey = 4);&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;In MySQL 5.6, the query optimizer will apply &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://roylyseng.blogspot.co.uk/2012/04/semi-join-in-mysql-56.html&quot;&gt;semi-join transformation&lt;/a&gt; to this query. Hence, the execution plan is quite different from the similar UPDATE statement:&lt;/p&gt;&lt;p style=&quot;text-align:center;&quot;&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://mysqlserverteam.com/wp-content/uploads/2014/05/select-semijoin.png&quot;&gt;&lt;img class=&quot;aligncenter  wp-image-1274&quot; style=&quot;border:1px solid black;&quot; title=&quot;Execution Plan for SELECT&quot; alt=&quot;select-semijoin&quot; src=&quot;http://mysqlserverteam.com/wp-content/uploads/2014/05/select-semijoin.png&quot; width=&quot;409&quot; height=&quot;295&quot;/&gt;&lt;/a&gt;&lt;/p&gt;As you can see, there is no sub-query in this plan. The query has been transformed into a three-way join. The great advantage of this semi-join transformation is that the optimizer is now free to re-arrange the order of the tables to be joined. Instead of having to go through all 179,000 parts, it will now start with the estimated 414 suppliers from the given country and find all parts supplied by them. This is obviously more efficient, and it would be good if MySQL would use the same approach for the UPDATE statement.  &lt;h1&gt;The Multi-Table Trick&lt;/h1&gt;Unlike single-table UPDATE statements, the MySQL Optimizer will use all available optimizations for multi-table &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.6/en/update.html&quot;&gt;UPDATE statements&lt;/a&gt;. This means that by rewriting the query as follows, the semi-join optimizations will apply: &lt;br /&gt;&lt;div class=&quot;codebox&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;UPDATE part, (SELECT 1) dummy &lt;br /&gt;SET p_retailprice = p_retailprice*1.10&lt;br /&gt;WHERE p_partkey IN &lt;br /&gt;     (SELECT ps_partkey &lt;br /&gt;      FROM partsupp JOIN supplier &lt;br /&gt;      ON ps_suppkey = s_suppkey &lt;br /&gt;      WHERE s_nationkey = 4);&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Notice the extra dummy table in the first line. Here is what happens when I execute the single-table and multi-table variants on a DBT-3 database (scale factor 1):&lt;/p&gt;&lt;div class=&quot;codebox&quot; style=&quot;overflow:auto;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;mysql&amp;gt; UPDATE part SET p_retailprice = p_retailprice*1.10 WHERE p_partkey IN (SELECT ps_partkey FROM partsupp JOIN supplier ON ps_suppkey = s_suppkey WHERE s_nationkey = 4);&lt;br /&gt;Query OK, 31097 rows affected, 28003 warnings (2.63 sec)&lt;br /&gt;Rows matched: 31097  Changed: 31097  Warnings: 28003&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; ROLLBACK;&lt;br /&gt;Query OK, 0 rows affected (0.20 sec)&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; UPDATE part, (SELECT 1) dummy SET p_retailprice = p_retailprice*1.10 WHERE p_partkey IN (SELECT ps_partkey FROM partsupp JOIN supplier ON ps_suppkey = s_suppkey WHERE s_nationkey = 4); &lt;br /&gt;Query OK, 31097 rows affected, 28003 warnings (0.40 sec)&lt;br /&gt;Rows matched: 31097  Changed: 31097  Warnings: 28003&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As you can see, execution time is reduced from 2.63 seconds to 0.40 seconds by using this trick. (I had executed both statements several times before, so the reported execution times are for a steady state with all accessed data in memory.)&lt;/p&gt; &lt;h1&gt;Multi-Table DELETE&lt;/h1&gt;The same trick can be used for &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.6/en/delete.html#idm47831925465680&quot;&gt;DELETE statements&lt;/a&gt;. Instead of the single-table variant, &lt;br /&gt;&lt;div class=&quot;codebox&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;DELETE FROM part&lt;br /&gt;WHERE p_partkey IN&lt;br /&gt;       (SELECT ps_partkey&lt;br /&gt;        FROM partsupp JOIN supplier&lt;br /&gt;        ON ps_suppkey = s_suppkey&lt;br /&gt;        WHERE s_nationkey = 4);&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;you can use the equivalent multi-table variant:&lt;/p&gt;&lt;div class=&quot;codebox&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;DELETE part FROM part&lt;br /&gt;WHERE p_partkey IN&lt;br /&gt;       (SELECT ps_partkey&lt;br /&gt;        FROM partsupp JOIN supplier&lt;br /&gt;        ON ps_suppkey = s_suppkey&lt;br /&gt;        WHERE s_nationkey = 4);&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This rewrite gives a similar performance improvement as reported for the above UPDATE statement.&lt;/p&gt;</description>
         <author>Øystein</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-1508669603650457962.post-1618007306330817068</guid>
         <pubDate>Mon, 14 Jul 2014 18:51:00 +0000</pubDate>
      </item>
      <item>
         <title>Re-factoring some internals of prepared statements in 5.7</title>
         <link>http://guilhembichot.blogspot.com/2014/05/re-factoring-some-internals-of-prepared.html</link>
         <description>[ &lt;i&gt;this is a re-posting of what I published on the&amp;nbsp;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://mysqlserverteam.com/re-factoring-some-internals-of-prepared-statements-in-5-7/&quot;&gt;MySQL server team blog&lt;/a&gt; a few days ago&lt;/i&gt; ]&lt;br /&gt;&lt;i&gt;&amp;nbsp;&lt;/i&gt; &lt;br /&gt;When the MySQL server receives a SELECT query, the query goes through several consecutive phases:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;parsing&lt;/b&gt;: SQL words are recognized, the query is  split into different parts following the SQL grammar rules: a list of  selected expressions, a list of tables to read, a WHERE condition, …&lt;/li&gt;&lt;li&gt;&lt;b&gt;resolution&lt;/b&gt;: the output of the parsing stage  contains names of columns and names of tables. Resolution is about  making sense out of this. For example, in “&lt;code&gt;WHERE foo=3&lt;/code&gt;“,  “foo” is a column name without a table name; by applying SQL name  resolution rules, we discover the table who contains “foo” (it can be  complicated if subqueries or outer joins are involved).&lt;/li&gt;&lt;li&gt;&lt;b&gt;optimization&lt;/b&gt;: finding the best way to read tables:  the best order of tables, and for each table, the best way to access it  (index lookup, index scan, …). The output is the so-called “plan”.&lt;/li&gt;&lt;li&gt;&lt;b&gt;execution&lt;/b&gt;: we read tables as dictated by the plan above, and send output rows to the client.&lt;/li&gt;&lt;/ul&gt;This design hasn’t changed since many many years. Originally, MySQL  didn’t have prepared statements. So it took a query, passed it through  the phases above, and then threw it away. This still happens nowadays  when using non-prepared statements.&lt;br /&gt;But, with a prepared statement, come more requirements. MySQL has to be  able to execute the query a second time (and a third time, and so on, at  the user’s will). No matter the used API (the &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.7/en/c-api-prepared-statement-function-overview.html&quot;&gt;C API&lt;/a&gt; or the &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.7/en/sql-syntax-prepared-statements.html&quot;&gt;SQL API&lt;/a&gt;), this usually looks like the following dialogue:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt; # Assume that &quot;t&quot; contains 3 rows with values 1, 2, 98 in a column&lt;br /&gt; user: PREPARE stmt FROM 'SELECT * FROM t';&lt;br /&gt; MySQL: okay&lt;br /&gt; user: EXECUTE stmt;&lt;br /&gt; MYSQL: 1,2,98&lt;br /&gt; user: INSERT INTO t VALUES (0);&lt;br /&gt; user: EXECUTE stmt;&lt;br /&gt; MYSQL: 1,2,98,0&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;SQL mandates that syntax and semantic errors be reported by PREPARE,  without postponing them to EXECUTE. So, PREPARE needs to do at least  parsing and resolution, to spot any such error.&lt;br /&gt;Then there is an implementation question: when it receives &lt;code&gt;&quot;EXECUTE stmt&quot;&lt;/code&gt;, how can MySQL remember what “stmt” is?&lt;br /&gt;The simplest, most obvious solution would be, during PREPARE, to simply store &lt;code&gt;&quot;stmt&quot; =&amp;gt; &quot;SELECT * FROM t&quot;&lt;/code&gt; in some session-local key/value dictionary. When &lt;code&gt;&quot;EXECUTE stmt&quot;&lt;/code&gt; comes, MySQL would find the query string in the dictionary, and go  through all stages (parsing, resolution, optimization, execution). And  when the next &lt;code&gt;&quot;EXECUTE stmt&quot;&lt;/code&gt; comes, it would do the same again.&lt;br /&gt;Of course, that would not be efficient. At least parsing and resolution  theoretically don’t need to be re-done every time: query’s text doesn’t  change, so only one parsing is necessary; the structure of tables (names  and type of columns) doesn’t change, so only one resolution is  necessary (but see footnote 1). So, what PREPARE actually stores in the key/value dictionary, is &lt;code&gt;&quot;stmt&quot; =&amp;gt; tree&lt;/code&gt;,  where “tree” is the output of the resolution phase; it’s an in-memory  tree of C++ objects, each object representing an expression, a column of  a table, a table, a left join, a boolean condition, …&lt;br /&gt;With this design, only optimization and execution are repeated by each &lt;code&gt;&quot;EXECUTE stmt&quot;&lt;/code&gt; (but see footnote 2).  Repeating optimization is sometimes a smart thing to do; imagine that  between the first and second EXECUTE, the table’s data distribution has  significantly changed (perhaps due to other clients), then the plan of  the first EXECUTE is maybe not the best one anymore, and searching for a  new plan makes sense. In other cases, repeating optimization is a  waste, because the old plan would be good enough. As of today, MySQL  always repeats optimization; making this more flexible would be an  interesting enhancement for the future, but today’s situation is: we  repeat optimization.&lt;br /&gt;&lt;br /&gt;Optimization does everything it can to speed up the query. Imagine  the query references 10 tables in its FROM clause. To find the best  order to access those 10 tables, we need to consider 10! possible  orders, roughly 3,000,000. It takes time to consider that many plans.&amp;nbsp;  The optimizer has techniques to cut this down, let me show one. If one  table is referenced like this in the WHERE clause:&lt;br /&gt;&lt;code&gt;WHERE t.pk=39 AND ...&lt;/code&gt;&lt;br /&gt;then, because “t.pk” is a primary key, I know that at most one row of  this table will participate in the result, and it makes sense to read it  with a primary key lookup. And this read has to be done only once in  this execution. So let’s put “t” first in the plan, unconditionally.  This technique, known as “constant tables”, divides the number of  possible orders by a factor of 10 in our example (only 9 tables left to  order). Now, imagine that the WHERE clause looks like this:&lt;br /&gt;&lt;code&gt;WHERE t.pk=39 AND t.c1&amp;gt;t2.c2&lt;/code&gt;&lt;br /&gt;As soon as I read the row of “t” with a primary key lookup (looking up  39), I get access to the value of t.c1; say it is 100. My condition can  thus be simplified to:&lt;br /&gt;&lt;code&gt;WHERE 100&amp;gt;t2.c2&lt;/code&gt;&lt;br /&gt;Notice how the AND has disappeared.&lt;br /&gt;The inequality predicate “100&amp;gt;t2.c2″ is interesting: assuming that  t2.c2 is indexed, it means that a range scan is a possibility for  reading t2.&lt;br /&gt;This little example was meant to demonstrate that MySQL, in the  optimization phase, does data-based transformations to the query:  transformations which depend on the content of tables, and which apply  for one single execution; indeed, when the next execution comes, it may  be that the row of “t” with t.pk=39 now has t.c1=1200, so our condition  simplifications don’t hold anymore. Another way to say this, is:  data-based transformations must be non-permanent.&lt;br /&gt;So the AND operator which was killed above during the optimization of  the first EXECUTE, must be resurrected for the optimization of the  second EXECUTE.&lt;br /&gt;To achieve this, at the end of resolution we rename &lt;i&gt;tree&lt;/i&gt; to &lt;i&gt;permanent_tree&lt;/i&gt;, then we make a copy of it, which we name &lt;i&gt;tree&lt;/i&gt;. The optimization phase has access to &lt;i&gt;tree&lt;/i&gt;, and no access to &lt;i&gt;permanent_tree&lt;/i&gt;. The optimization operates on &lt;i&gt;tree&lt;/i&gt;, does data-based transformations on it. When the second EXECUTE starts, &lt;i&gt;permanent_tree&lt;/i&gt; is fetched, copied into a new &lt;i&gt;tree&lt;/i&gt;, on which the optimization operates. For example, the AND operator always exists in &lt;i&gt;permanent_tree&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;What about permanent transformations? For example, consider this query:&lt;br /&gt;&lt;code&gt;SELECT * FROM t1 JOIN (t2 JOIN t3 ON t2.a=t3.a) ON t2.b+t3.b&amp;gt;t1.b&lt;br /&gt;WHERE t1.b*2&amp;gt;15;&lt;/code&gt;&lt;br /&gt;The result of this query is defined by the SQL standard as: get the  result of the most inner join (t2,t3), filter with t2.a=t3.a, then join  the result with t1, filter with t2.b+t3.b&amp;gt;t1.b, then filter with  t1.b*2&amp;gt;15. With this definition, the order of tables’ reading and the  order of application of conditions are constrained. For example,  reading t2 then t1 then t3 is not possible. But if we notice that the  query is equivalent to:&lt;br /&gt;&lt;code&gt;SELECT * FROM t1, t2, t3 WHERE t2.a=t3.a AND t2.b+t3.b&amp;gt;t1.b&lt;br /&gt;AND t1.b*2&amp;gt;15;&lt;/code&gt;&lt;br /&gt;then we have 3! = 6 possible orders for tables. More plans to examine,  but more potential for finding a good plan – it is very possible that  the best plan, the one yielding the fastest execution, is not among the  ones suggested by the SQL standard’s definition.&lt;br /&gt;The equivalence between both queries is semantics-based, not data-based.  Thus, the transformation from the first to the second query can be  permanent: it can be done once for all, not at every EXECUTE.&lt;br /&gt;Permanent transformations include:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;JOIN-to-WHERE, like in the example above&lt;/li&gt;&lt;li&gt;outer-join-to-inner-join, when there is something in the WHERE  clause which allows to deduce that NULL-complemented rows will actually  not participate in the result&lt;/li&gt;&lt;li&gt;semi-join, more or less merging an “IN (subquery)” predicate into the parent query&lt;/li&gt;&lt;li&gt;IN-to-EXISTS, rewriting “x IN (subquery)” to “EXISTS (modified subquery)”.&lt;/li&gt;&lt;/ul&gt;After giving all this background (phew…), I’m now ready to explain  one re-factoring which I did in MySQL 5.7.4. The situation of permanent  transformations in MySQL 5.6 is the following:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;IN-to-EXISTS : done in the resolution phase.&lt;/li&gt;&lt;li&gt;semi-join, JOIN-to-WHERE, outer-join-to-inner-join: done in the optimization phase.&lt;/li&gt;&lt;/ol&gt;Doing permanent transformations during optimization (step (2) above)  is surprising, as optimization is re-done at every EXECUTE. Fortunately,  we can internally know if an optimization is the first or not; if it’s  not, we skip step (2).&lt;br /&gt;So in the end, efficiency is guaranteed – permanent transformations are not re-done. Though this design agreeably looks strange.&lt;br /&gt;&lt;br /&gt;Now, putting pieces together, on the way to more strangeness: you remember that after the resolution phase, we produce &lt;i&gt;permanent_tree&lt;/i&gt; and save it for all future EXECUTEs. It thus contains permanent  transformations done in resolution, good. But, it does not contain those  done in optimization (semijoin …), as optimization runs after &lt;i&gt;permanent_tree&lt;/i&gt; has been produced. Still we do want semi-join and friends to be reused by all future EXECUTEs, so they must be put in &lt;i&gt;permanent_tree&lt;/i&gt;! So we get an even more strange design:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;resolution &lt;ol&gt;&lt;li&gt;do some permanent transformations (IN-to-EXISTS) on &lt;i&gt;tree&lt;/i&gt;&lt;/li&gt;&lt;li&gt;rename &lt;i&gt;tree&lt;/i&gt; to &lt;i&gt;permanent_tree&lt;/i&gt;&lt;/li&gt;&lt;li&gt;copy &lt;i&gt;permanent_tree&lt;/i&gt; to a new &lt;i&gt;tree&lt;/i&gt; (for optimization to do nonpermanent transformations on it)&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;optimization &lt;ol&gt;&lt;li&gt;if first optimization: &lt;ol&gt;&lt;li&gt;do some more permanent transformations (semijoin, etc) on &lt;i&gt;tree&lt;/i&gt;&lt;/li&gt;&lt;li&gt;throw &lt;i&gt;permanent_tree&lt;/i&gt; away&lt;/li&gt;&lt;li&gt;copy &lt;i&gt;tree&lt;/i&gt; to a new &lt;i&gt;permanent_tree&lt;/i&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;do nonpermanent transformations on &lt;i&gt;tree&lt;/i&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;execution &lt;ol&gt;&lt;li&gt;read tables and send rows&lt;/li&gt;&lt;li&gt;throw &lt;i&gt;tree&lt;/i&gt; away&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;/ol&gt;This has a few nasty effects:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;permanent transformations are scattered: the boundary between phases  is blurred, which is a code readability problem, and an impediment for  future improvements in the Optimizer in general&lt;/li&gt;&lt;li&gt;efficiency loss: copying a tree in resolution phase takes some time and memory; in the first optimization we throw&amp;nbsp;&lt;i&gt;permanent_tree&lt;/i&gt; away and do the copying again… Bad.&lt;/li&gt;&lt;li&gt;real bugs. Yes, because what the resolution phase thinks of &lt;i&gt;permanent_tree&lt;/i&gt; is not true anymore: this object has been deleted and replaced by  another one, in the first optimization… so the next EXECUTE gets  confused…&lt;/li&gt;&lt;/ul&gt;In MySQL 5.7.4, I have moved all permanent transformations to where they belong, so now we have the more straightforward design:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;resolution &lt;ol&gt;&lt;li&gt;do &lt;b&gt;all&lt;/b&gt; permanent transformations on &lt;i&gt;tree&lt;/i&gt;&lt;/li&gt;&lt;li&gt;rename &lt;i&gt;tree&lt;/i&gt; to &lt;i&gt;permanent_tree&lt;/i&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;optimization &lt;ol&gt;&lt;li&gt;copy &lt;i&gt;permanent_tree&lt;/i&gt; to a new &lt;i&gt;tree&lt;/i&gt; (for optimization to do nonpermanent transformations on it)&lt;/li&gt;&lt;li&gt;do nonpermanent transformations on &lt;i&gt;tree&lt;/i&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;execution &lt;ol&gt;&lt;li&gt;read tables and send rows&lt;/li&gt;&lt;li&gt;throw &lt;i&gt;tree&lt;/i&gt; away&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;/ol&gt;If we had done this re-factoring earlier, we would have saved us some  headaches. But, better late than never. It’s at least comforting that  nowadays we have time to do re-factoring, even if it means spending  several man-months on a task like that. It really took that much time:  the idea may look simple, but the devil was in the details as usual, and  if you add, on top of coding time, review time by two reviewers, and QA  time to verify that I didn’t break anything… But in my team we are  convinced that this is a long-term investment which will pay. Moreover,  when we do such re-factoring work, it gives us the occasion to remove  the little hacks which accumulated over time to work around the root  problem (which the re-factoring finally addresses). And that leads to  even more code simplification.&lt;br /&gt;&lt;br /&gt;There are a few more details in &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/worklog/task/?id=7082&quot;&gt;the Worklog page&lt;/a&gt; if you are interested (note the multiple tabs there).&lt;br /&gt;&lt;br /&gt;That’s all for today. If you reached this line, congratulations :-)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b id=&quot;footnote&quot;&gt;Footnotes:&lt;/b&gt;&lt;br /&gt;&lt;b&gt;1&lt;/b&gt;. I hear you say: “what if an ALTER TABLE is done  between PREPARE and EXECUTE?! This may change names and types of  columns!”. Yes, you are right, but there exists, somewhere in the  prepared statement subsystem, a detector for this; it runs when EXECUTE  starts, and if it spots a table structure change since the statement was  prepared, it throws away the prepared statement (as “out-of-date”),  silently re-prepares it and then docks into EXECUTE; the user does not  notice, except if she looks at com_stmt_reprepare in &lt;code&gt;SHOW STATUS&lt;/code&gt;.  This detector is external to the Optimizer and runs before it; so if  the Optimizer is involved in EXECUTE, it can safely assume that nothing  has changed since PREPARE.&lt;br /&gt;&lt;b&gt;2&lt;/b&gt;. Ok, a part of resolution is repeated too. It would be good to avoid that, in the future.</description>
         <author>Guilhem Bichot</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-9191231274999121063.post-4544487552307710906</guid>
         <pubDate>Wed, 21 May 2014 08:07:00 +0000</pubDate>
      </item>
      <item>
         <title>The range access method and why you should use EXPLAIN JSON</title>
         <link>http://jorgenloland.blogspot.com/2014/03/the-range-access-method-and-why-you.html</link>
         <description>I got an interesting question about &lt;span style=&quot;font-size:small;&quot;&gt;&lt;span style=&quot;&quot;&gt;EXPLAIN&lt;/span&gt;&lt;/span&gt; and the range access method recently. The person had a query that could be written either with a BETWEEN predicate or an IN predicate, something similar to this:  &lt;br&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;line-height:100%;overflow:auto;padding:10px;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:xx-small;&quot;&gt;mysql&amp;gt; EXPLAIN SELECT * &lt;br&gt;    -&amp;gt; FROM orders WHERE customer_id BETWEEN 7 AND 10 AND value &amp;gt; 500;&lt;br&gt;+----+-------------+--------+-------+----------+----------+------+------&lt;br&gt;| id | select_type | table  | type  | key      | key_len  | rows | Extra&lt;br&gt;+----+-------------+--------+-------+----------+----------+------+------&lt;br&gt;|  1 | SIMPLE      | orders | range | cust_val | 10       |   91 |  ...&lt;br&gt;+----+-------------+--------+-------+----------+----------+------+------&lt;br&gt;&lt;br&gt;mysql&amp;gt; EXPLAIN SELECT * &lt;br&gt;    -&amp;gt; FROM orders WHERE customer_id IN (7,8,9,10) AND value &amp;gt; 500;&lt;br&gt;+----+-------------+--------+-------+----------+----------+------+------&lt;br&gt;| id | select_type | table  | type  | key      | key_len  | rows | Extra&lt;br&gt;+----+-------------+--------+-------+----------+----------+------+------&lt;br&gt;|  1 | SIMPLE      | orders | range | cust_val | 10       |   44 |  ...&lt;br&gt;+----+-------------+--------+-------+----------+----------+------+------&lt;br&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;The table was:&lt;br&gt;&lt;br&gt;&lt;span style=&quot;font-family:Verdana, sans-serif;font-size:xx-small;&quot;&gt;CREATE TABLE orders (&lt;/span&gt;&lt;br&gt;&lt;div style=&quot;font-family:Verdana, sans-serif;&quot;&gt;&lt;span style=&quot;font-size:xx-small;&quot;&gt;   order_id INT NOT NULL PRIMARY KEY,&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family:Verdana, sans-serif;&quot;&gt;&lt;span style=&quot;font-size:xx-small;&quot;&gt;   customer_id INT,&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family:Verdana, sans-serif;&quot;&gt;&lt;span style=&quot;font-size:xx-small;&quot;&gt;   value INT,&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family:Verdana, sans-serif;&quot;&gt;&lt;span style=&quot;font-size:xx-small;&quot;&gt;   order_date DATE,&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family:Verdana, sans-serif;&quot;&gt;&lt;span style=&quot;font-size:xx-small;&quot;&gt;   KEY custid_value (customer_id, value)&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family:Verdana, sans-serif;&quot;&gt;&lt;span style=&quot;font-size:xx-small;&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;br&gt;Given that &lt;span style=&quot;&quot;&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;customer_id&lt;/span&gt;&lt;/span&gt; is an integer value, these queries should be equivalent. And that&amp;#39;s what &lt;span style=&quot;&quot;&gt;EXPLAIN&lt;/span&gt; seems to tell us too: same access method, same key, same key_length etc. &lt;br&gt;&lt;br&gt;You may have guessed the question by now: If the queries are equivalent, why isn&amp;#39;t the rows estimate identical? Is one of the numbers wrong or do these queries have different execution plans after all?&lt;br&gt;&lt;br&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://jorgenloland.blogspot.com/2014/03/the-range-access-method-and-why-you.html#more&quot;&gt;Read more »&lt;/a&gt;</description>
         <author>Jørgen Løland</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-7171477457129187873.post-3779630589759509586</guid>
         <pubDate>Fri, 28 Mar 2014 13:59:00 +0000</pubDate>
      </item>
      <item>
         <title>Range access: now in an IN predicate near you.</title>
         <link>http://optimize-this.blogspot.com/2013/12/range-access-now-in-in-predicate-near.html</link>
         <description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align:left;&quot;&gt;Several users have reported that certain queries with &lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;IN&lt;/span&gt; predicates can't use index scans even though all the columns in the query are indexed. What's worse, if you reformulate your query without &lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;IN&lt;/span&gt;, the indexes are used. Let's take some example query. Suppose we have a table with two indexed columns:&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;CREATE TABLE t1 (&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&amp;nbsp; col1 INTEGER,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&amp;nbsp; col2 INTEGER,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&amp;nbsp; ...&lt;/span&gt;&lt;br /&gt;&lt;div&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&amp;nbsp; KEY key1( col1, col2 )&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Let's take a look at some queries that could take advantage of the &lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;key1&lt;/span&gt; index to read rows without accessing the table.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ol style=&quot;text-align:left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;SELECT col1, col2 FROM t1 WHERE col1 = 100;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;SELECT col1, col2 FROM t1 WHERE col1 &amp;gt; 100 AND col1 &amp;lt; 200;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;SELECT col1, col2 FROM t1&lt;/span&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;WHERE col1 &amp;gt; 100 AND col1 &amp;lt; 200 OR col1 &amp;gt; 300 AND col1 &amp;lt; 400;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;SELECT col1, col2 FROM t1 WHERE col1 = 100 AND col2 &amp;gt; 100 AND cold2 &amp;lt; 200;&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;These queries will use what MySQL calls Index Range Scans. (although the first query could also use Ref Scan). This access method will fetch rows from the index trees given a start and end value. It's also possible to read multiple intervals, each with a start and end value, as we saw in query 3 above.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A special case of intervals is when the endpoints are the same value. Range scans can be used for conditions such as &lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;col1 = 100 &lt;/span&gt;because it's equivalent to the interval &lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;100 &amp;lt;= col1 &amp;lt;= 100&lt;/span&gt;. This way we can use range scans for a broader class of queries.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Armed with multiple-interval scans, a.k.a. multi-range reads, or MRR for short, we can use the range access for queries such as&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align:left;&quot;&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;SELECT col1, col2 FROM t1&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align:left;&quot;&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;WHERE col1 = 100 or col1 = 200 or col1 = 300;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align:left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align:left;&quot;&gt;We can use all columns in the index of course:&lt;/div&gt;&lt;div style=&quot;text-align:left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align:left;&quot;&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;SELECT col1, col2 FROM t1&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;WHERE col1 = 100 AND col2 = 100&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp;OR col1 = 200&lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;AND col2 = 200&lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp;OR col1 = 300 AND col2 = 300;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;At some point, this syntax becomes unwieldy. And this isn't just aesthetics, for really big queries, we get a combinatorial blowup which can cause parsing to take a long time. This is the reason why SQL has &lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;IN&lt;/span&gt; predicates to say the same thing:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;SELECT col1, col2 FROM t1&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;WHERE col1 = 100 OR col2 = 200&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;OR&lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&amp;nbsp;col2 = 300;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;means the same as&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;SELECT col1, col2 FROM t1&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;WHERE col1 IN (100, 200, 300);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;And for rows it gets even more convenient:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;SELECT col1, col2 FROM t1&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;WHERE col1 = 100 AND col2 = 100&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp;OR col1 = 200&lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;AND col2 = 200&lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp;OR col1 = 300 AND col2 = 300;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;can be written as&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;SELECT col1, col2 FROM t1&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;WHERE (col1, col2) IN ((100, 100), (200, 200), (300, 300));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The problem that users saw is that suddenly MySQL doesn't use MRR any more, and resorts to scanning the entire index. This is because the &lt;i&gt;range optimizer&lt;/i&gt; ignored IN conditions over rows. The range optimizer is the sub-optimizer that analyzes conditions and translates them into a multi-range structure that can be handed to the storage engine to fetch the rows from the index. It handled &lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;IN&lt;/span&gt; predicates as long as they were over scalars or just a single row, but completely ignored lists of rows.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;As of 5.7.3 this hole in the net is stitched up. The range optimizer gladly opens the door for queries with &lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;IN&lt;/span&gt; predicates as long as&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align:left;&quot;&gt;&lt;li&gt;The predicate is only &lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;IN&lt;/span&gt;, not &lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;NOT IN&lt;/span&gt;.&lt;/li&gt;&lt;li&gt;The row on the predicate's left-hand side is only indexed column references, in the same index.&lt;/li&gt;&lt;li&gt;The rows contain only constants or come from a previously read table in nested-loops join.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Note that 'constants' is a pretty broad category. It consists of pre-evaluated expressions, even some sub-queries, SQL variables and similar beings.&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;text-align:left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;</description>
         <author>Martin</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-4934478127523643312.post-1737934090123637966</guid>
         <pubDate>Tue, 10 Dec 2013 07:42:00 +0000</pubDate>
      </item>
      <item>
         <title>WITH RECURSIVE and MySQL</title>
         <link>http://guilhembichot.blogspot.com/2013/11/with-recursive-and-mysql.html</link>
         <description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align:left;&quot;&gt;If you have been using certain DBMSs, or reading recent versions of the SQL standard, you are probably aware of the so-called &lt;i&gt;&quot;WITH clause&quot;&lt;/i&gt; of SQL.&lt;br /&gt;Some call it &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_10002.htm#i2161315&quot;&gt;Subquery Factoring&lt;/a&gt;. Others call it&amp;nbsp;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://msdn.microsoft.com/en-us/library/ms190766%28v=sql.105%29.aspx&quot;&gt;Common Table Expression&lt;/a&gt;.&lt;br /&gt;In its simplest form, this feature is a kind of &quot;boosted derived table&quot;.&lt;br /&gt;&lt;br /&gt;Assume that a table T1 has three columns: &lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;CREATE TABLE T1(&lt;br /&gt;YEAR INT, # 2000, 2001, 2002 ...&lt;br /&gt;MONTH INT, # January, February, ...&lt;br /&gt;SALES INT # how much we sold on that month of that year&lt;br /&gt;);&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;Now I want to know the sales trend (increase/decrease), year after year:&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;SELECT D1.YEAR, (CASE WHEN D1.S&amp;gt;D2.S THEN 'INCREASE' ELSE 'DECREASE' END) AS TREND&lt;br /&gt;FROM&lt;br /&gt;  (SELECT YEAR, SUM(SALES) AS S FROM T1 GROUP BY YEAR) AS D1,&lt;br /&gt;  (SELECT YEAR, SUM(SALES) AS S FROM T1 GROUP BY YEAR) AS D2&lt;br /&gt;WHERE D1.YEAR = D2.YEAR-1;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;Both derived tables are based on the same subquery text, but usually a DBMS is not smart enough to recognize it. Thus, it will evaluate &quot;SELECT YEAR, SUM(SALES)... GROUP BY YEAR&quot; twice! A first time to fill D1, a second time to fill D2. This limitation is sometimes stated as &lt;i&gt;&quot;it's not possible to refer to a derived table twice in the same query&quot;&lt;/i&gt;.&lt;br /&gt;Such double evaluation can lead to a serious performance problem. Using WITH, this limitation does not exist, and the following statement evaluates the subquery only once:&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;WITH D AS (SELECT YEAR, SUM(SALES) AS S FROM T1 GROUP BY YEAR)&lt;br /&gt;SELECT D1.YEAR, (CASE WHEN D1.S&amp;gt;D2.S THEN 'INCREASE' ELSE 'DECREASE' END) AS TREND&lt;br /&gt;FROM&lt;br /&gt; D AS D1,&lt;br /&gt; D AS D2&lt;br /&gt;WHERE D1.YEAR = D2.YEAR-1;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;This already demonstrates one benefit of WITH.&lt;br /&gt;In MySQL, WITH is not yet supported. But it can be emulated with a view:&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;CREATE VIEW D AS (SELECT YEAR, SUM(SALES) AS S FROM T1 GROUP BY YEAR);&lt;br /&gt;SELECT D1.YEAR, (CASE WHEN D1.S&amp;gt;D2.S THEN 'INCREASE' ELSE 'DECREASE' END) AS TREND&lt;br /&gt;FROM&lt;br /&gt; D AS D1,&lt;br /&gt; D AS D2&lt;br /&gt;WHERE D1.YEAR = D2.YEAR-1;&lt;br /&gt;DROP VIEW D;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;Instead of a view, I could as well create D as a normal table. But not as a temporary table, because in MySQL a temporary table cannot be referred twice in the same query, as mentioned in &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.7/en/temporary-table-problems.html&quot;&gt;the manual&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;After this short introduction, showing the simplest form of WITH, I would like to turn to the &lt;i&gt;more complex form of WITH: the RECURSIVE form&lt;/i&gt;.&lt;br /&gt;According to the SQL standard, to use the recursive form, you should write WITH RECURSIVE. However, looking at some other DBMSs, they seem to not require the RECURSIVE word.&lt;br /&gt;WITH RECURSIVE is a powerful construct. For example, it can do the same job as Oracle's CONNECT BY clause (you can check out &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://rajeshwaranbtech.blogspot.co.uk/2012/12/recursive-with-clause-to-implement.html&quot;&gt;some example conversions&lt;/a&gt; between both constructs).&lt;br /&gt;Let's walk through an example, to understand what WITH RECURSIVE does. &lt;br /&gt;&lt;br /&gt;Assume you have a table of employees (this is a &lt;i&gt;very&lt;/i&gt; classical example of WITH RECURSIVE):&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;CREATE TABLE EMPLOYEES (&lt;br /&gt;ID INT PRIMARY KEY,&lt;br /&gt;NAME VARCHAR(100),&lt;br /&gt;MANAGER_ID INT,&lt;br /&gt;INDEX (MANAGER_ID),&lt;br /&gt;FOREIGN KEY (MANAGER_ID) REFERENCES EMPLOYEES(ID)&lt;br /&gt;);&lt;br /&gt;INSERT INTO EMPLOYEES VALUES&lt;br /&gt;(333, &quot;Yasmina&quot;, NULL),&lt;br /&gt;(198, &quot;John&quot;, 333),&lt;br /&gt;(29, &quot;Pedro&quot;, 198),&lt;br /&gt;(4610, &quot;Sarah&quot;, 29),&lt;br /&gt;(72, &quot;Pierre&quot;, 29),&lt;br /&gt;(692, &quot;Tarek&quot;, 333);&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;In other words, Yasmina is CEO, John and Tarek report to her. Pedro reports to John, Sarah and Pierre report to Pedro.&lt;br /&gt;In a big company, they would be thousands of rows in this table.&lt;br /&gt;&lt;br /&gt;Now, let's say that you would like to know, for each employee: &quot;how many people are, directly and indirectly, reporting to him/her&quot;? Here is how I would do it. First, I would make a list of people who are not managers: with a subquery I get the list of all managers, and using &lt;i&gt;NOT IN (subquery)&lt;/i&gt; I get the list of all non-managers:&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;SELECT ID, NAME, MANAGER_ID, 0 AS REPORTS&lt;br /&gt;FROM EMPLOYEES&lt;br /&gt;WHERE ID NOT IN (SELECT MANAGER_ID FROM EMPLOYEES WHERE MANAGER_ID IS NOT NULL);&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;Then I would insert the results into a new table named EMPLOYEES_EXTENDED; EXTENDED stands for &quot;extended with more information&quot;, the new information being the fourth column named REPORTS: it is a count of people who are reporting directly or indirectly to the employee. Because&amp;nbsp; we have listed people who are not managers, they have a value of 0 in the REPORTS column.&lt;br /&gt;Then, we can produce the rows for &quot;first level&quot; managers (the direct managers of non-managers):&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;SELECT M.ID, M.NAME, M.MANAGER_ID, SUM(1+E.REPORTS) AS REPORTS&lt;br /&gt;FROM EMPLOYEES M JOIN EMPLOYEES_EXTENDED E ON M.ID=E.MANAGER_ID&lt;br /&gt;GROUP BY M.ID, M.NAME, M.MANAGER_ID;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;Explanation: for a row of M (that is, for an employee), the JOIN will produce zero or more rows, one per non-manager directly reporting to the employee.&lt;br /&gt;Each such non-manager contributes to the value of REPORTS for his manager, through two numbers: 1 (the non-manager himself), and the number of direct/indirect reports of the non-manager (i.e. the value of REPORTS for the non-manager).&lt;br /&gt;Then I would empty EMPLOYEES_EXTENDED, and fill it with the rows produced just above, which describe the first level managers.&lt;br /&gt;Then the same query should be run again, and it would produce information about the &quot;second level&quot; managers. And so on.&lt;br /&gt;Finally, at one point Yasmina will be the only row of EMPLOYEES_EXTENDED, and when we run the above SELECT again, the JOIN will produce no rows, because E.MANAGER_ID will be NULL (she's the CEO). We are done.&lt;br /&gt;&lt;br /&gt;It's time for a recap: EMPLOYEES_EXTENDED has been a kind of &quot;temporary buffer&quot;, which has &lt;i&gt;successively &lt;/i&gt;held non-managers, first level managers, second level managers, etc. We have used &lt;i&gt;recursion&lt;/i&gt;. The answer to the original problem is: the &lt;i&gt;union&lt;/i&gt; of all the successive content of EMPLOYEES_EXTENDED.&lt;br /&gt;Non-managers have been the start of the recursion, which is usually called &quot;the anchor member&quot; or &quot;the seed&quot;. The SELECT query which moves from one step of&amp;nbsp; recursion to the next one, is the &quot;recursive member&quot;. The complete statement looks like this:&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;WITH RECURSIVE&lt;br /&gt;# The temporary buffer, also used as UNION result:&lt;br /&gt;EMPLOYEES_EXTENDED&lt;br /&gt;AS&lt;br /&gt;(&lt;br /&gt;  # The seed:&lt;br /&gt;  SELECT ID, NAME, MANAGER_ID, 0 AS REPORTS&lt;br /&gt;  FROM EMPLOYEES&lt;br /&gt;  WHERE ID NOT IN (SELECT MANAGER_ID FROM EMPLOYEES WHERE MANAGER_ID IS NOT NULL)&lt;br /&gt;UNION ALL&lt;br /&gt;  # The recursive member:&lt;br /&gt;  SELECT M.ID, M.NAME, M.MANAGER_ID, SUM(1+E.REPORTS) AS REPORTS&lt;br /&gt;  FROM EMPLOYEES M JOIN EMPLOYEES_EXTENDED E ON M.ID=E.MANAGER_ID&lt;br /&gt;  GROUP BY M.ID, M.NAME, M.MANAGER_ID&lt;br /&gt;)&lt;br /&gt;# what we want to do with the complete result (the UNION):&lt;br /&gt;SELECT * FROM EMPLOYEES_EXTENDED;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;MySQL does not yet support WITH RECURSIVE, but it is possible to code a generic stored procedure which can easily emulate it. Here is how you would call it:&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;CALL WITH_EMULATOR(&lt;br /&gt;&quot;EMPLOYEES_EXTENDED&quot;,&lt;br /&gt;&quot;&lt;br /&gt;  SELECT ID, NAME, MANAGER_ID, 0 AS REPORTS&lt;br /&gt;  FROM EMPLOYEES&lt;br /&gt;  WHERE ID NOT IN (SELECT MANAGER_ID FROM EMPLOYEES WHERE MANAGER_ID IS NOT NULL)&lt;br /&gt;&quot;,&lt;br /&gt;&quot;&lt;br /&gt;  SELECT M.ID, M.NAME, M.MANAGER_ID, SUM(1+E.REPORTS) AS REPORTS&lt;br /&gt;  FROM EMPLOYEES M JOIN EMPLOYEES_EXTENDED E ON M.ID=E.MANAGER_ID&lt;br /&gt;  GROUP BY M.ID, M.NAME, M.MANAGER_ID&lt;br /&gt;&quot;,&lt;br /&gt;&quot;SELECT * FROM EMPLOYEES_EXTENDED&quot;,&lt;br /&gt;0,&lt;br /&gt;&quot;&quot;&lt;br /&gt;);&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;You can recognize, as arguments of the stored procedure, every member of the WITH standard syntax: name of the temporary buffer, query for the seed, query for the recursive member, and what to do with the complete result. The last two arguments - 0 and the empty string - are details which you can ignore for now.&lt;br /&gt;&lt;br /&gt;Here is the result returned by this stored procedure:&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;+------+---------+------------+---------+&lt;br /&gt;| ID   | NAME    | MANAGER_ID | REPORTS |&lt;br /&gt;+------+---------+------------+---------+&lt;br /&gt;|   72 | Pierre  |         29 |       0 |&lt;br /&gt;|  692 | Tarek   |        333 |       0 |&lt;br /&gt;| 4610 | Sarah   |         29 |       0 |&lt;br /&gt;|   29 | Pedro   |        198 |       2 |&lt;br /&gt;|  333 | Yasmina |       NULL |       1 |&lt;br /&gt;|  198 | John    |        333 |       3 |&lt;br /&gt;|  333 | Yasmina |       NULL |       4 |&lt;br /&gt;+------+---------+------------+---------+&lt;br /&gt;7 rows in set&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;Notice how Pierre, Tarek and Sarah have zero reports, Pedro has two, which looks correct... However, Yasmina appears in two rows! Odd? Yes and no. Our algorithm starts from non-managers, the &quot;leaves&quot; of the tree (Yasmina being the root of the tree). Then our algorithm looks at first level managers, the direct parents of leaves. Then at second level managers. But Yasmina is both a first level manager (of the nonmanager Tarek) and a third level manager (of the nonmanagers Pierre, Tarek and Sarah). That's why she appears twice in the final result: once for the &quot;tree branch&quot; which ends at leaf Tarek, once for the tree branch which ends at leaves Pierre, Tarek and Sarah. The first tree branch contributes 1 direct/indirect report. The second tree branch contributes 4. The right number, which we want, is the sum of the two: 5. Thus we just need to change the final query, in the CALL:&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;CALL WITH_EMULATOR(&lt;br /&gt;&quot;EMPLOYEES_EXTENDED&quot;,&lt;br /&gt;&quot;&lt;br /&gt;  SELECT ID, NAME, MANAGER_ID, 0 AS REPORTS&lt;br /&gt;  FROM EMPLOYEES&lt;br /&gt;  WHERE ID NOT IN (SELECT MANAGER_ID FROM EMPLOYEES WHERE MANAGER_ID IS NOT NULL)&lt;br /&gt;&quot;,&lt;br /&gt;&quot;&lt;br /&gt;  SELECT M.ID, M.NAME, M.MANAGER_ID, SUM(1+E.REPORTS) AS REPORTS&lt;br /&gt;  FROM EMPLOYEES M JOIN EMPLOYEES_EXTENDED E ON M.ID=E.MANAGER_ID&lt;br /&gt;  GROUP BY M.ID, M.NAME, M.MANAGER_ID&lt;br /&gt;&quot;,&lt;br /&gt;&quot;&lt;br /&gt;  SELECT ID, NAME, MANAGER_ID, SUM(REPORTS)&lt;br /&gt;  FROM EMPLOYEES_EXTENDED&lt;br /&gt;  GROUP BY ID&lt;/span&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;span style=&quot;font-size:small;&quot;&gt;, NAME, MANAGER_ID&lt;/span&gt;&lt;br /&gt;&quot;,&lt;br /&gt;0,&lt;br /&gt;&quot;&quot;&lt;br /&gt;);&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;And here is finally the proper result:&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;+------+---------+------------+--------------+&lt;br /&gt;| ID   | NAME    | MANAGER_ID | SUM(REPORTS) |&lt;br /&gt;+------+---------+------------+--------------+&lt;br /&gt;|   29 | Pedro   |        198 |            2 |&lt;br /&gt;|   72 | Pierre  |         29 |            0 |&lt;br /&gt;|  198 | John    |        333 |            3 |&lt;br /&gt;|  333 | Yasmina |       NULL |            5 |&lt;br /&gt;|  692 | Tarek   |        333 |            0 |&lt;br /&gt;| 4610 | Sarah   |         29 |            0 |&lt;br /&gt;+------+---------+------------+--------------+&lt;br /&gt;6 rows in set&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;Let's finish by showing the body of the stored procedure. You will notice that it does heavy use of dynamic SQL, thanks to prepared statements. Its body does not depend on the particular problem to solve, it's reusable as-is for other WITH RECURSIVE use cases. I have added comments inside the body, so it should be self-explanatory. If it's not, feel free to drop a comment on this post, and I will explain further. &lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;# Usage: the standard syntax:&lt;br /&gt;#   WITH RECURSIVE recursive_table AS&lt;br /&gt;#    (initial_SELECT&lt;br /&gt;#     UNION ALL&lt;br /&gt;#     recursive_SELECT)&lt;br /&gt;#   final_SELECT;&lt;br /&gt;# should be translated by you to &lt;br /&gt;# CALL WITH_EMULATOR(recursive_table, initial_SELECT, recursive_SELECT,&lt;br /&gt;#                    final_SELECT, 0, &quot;&quot;).&lt;br /&gt;&lt;br /&gt;# ALGORITHM:&lt;br /&gt;# 1) we have an initial table T0 (actual name is an argument&lt;br /&gt;# &quot;recursive_table&quot;), we fill it with result of initial_SELECT.&lt;br /&gt;# 2) We have a union table U, initially empty.&lt;br /&gt;# 3) Loop:&lt;br /&gt;#   add rows of T0 to U,&lt;br /&gt;#   run recursive_SELECT based on T0 and put result into table T1,&lt;br /&gt;#   if T1 is empty&lt;br /&gt;#      then leave loop,&lt;br /&gt;#      else swap T0 and T1 (renaming) and empty T1&lt;br /&gt;# 4) Drop T0, T1&lt;br /&gt;# 5) Rename U to T0&lt;br /&gt;# 6) run final select, send relult to client&lt;br /&gt;&lt;br /&gt;# This is for *one* recursive table.&lt;br /&gt;# It would be possible to write a SP creating multiple recursive tables.&lt;br /&gt;&lt;br /&gt;delimiter |&lt;br /&gt;&lt;br /&gt;CREATE PROCEDURE WITH_EMULATOR(&lt;br /&gt;recursive_table varchar(100), # name of recursive table&lt;br /&gt;initial_SELECT varchar(65530), # seed a.k.a. anchor&lt;br /&gt;recursive_SELECT varchar(65530), # recursive member&lt;br /&gt;final_SELECT varchar(65530), # final SELECT on UNION result&lt;br /&gt;max_recursion int unsigned, # safety against infinite loop, use 0 for default&lt;br /&gt;create_table_options varchar(65530) # you can add CREATE-TABLE-time options&lt;br /&gt;# to your recursive_table, to speed up initial/recursive/final SELECTs; example:&lt;br /&gt;# &quot;(KEY(some_column)) ENGINE=MEMORY&quot;&lt;br /&gt;)&lt;br /&gt;&lt;br /&gt;BEGIN&lt;br /&gt;  declare new_rows int unsigned;&lt;br /&gt;  declare show_progress int default 0; # set to 1 to trace/debug execution&lt;br /&gt;  declare recursive_table_next varchar(120);&lt;br /&gt;  declare recursive_table_union varchar(120);&lt;br /&gt;  declare recursive_table_tmp varchar(120);&lt;br /&gt;  set recursive_table_next  = concat(recursive_table, &quot;_next&quot;);&lt;br /&gt;  set recursive_table_union = concat(recursive_table, &quot;_union&quot;);&lt;br /&gt;  set recursive_table_tmp   = concat(recursive_table, &quot;_tmp&quot;);&lt;br /&gt;  # If you need to reference recursive_table more than&lt;br /&gt;  # once in recursive_SELECT, remove the TEMPORARY word.&lt;br /&gt;  SET @str = # create and fill T0&lt;br /&gt;    CONCAT(&quot;CREATE TEMPORARY TABLE &quot;, recursive_table, &quot; &quot;,&lt;br /&gt;    create_table_options, &quot; AS &quot;, initial_SELECT);&lt;br /&gt;  PREPARE stmt FROM @str;&lt;br /&gt;  EXECUTE stmt;&lt;br /&gt;  SET @str = # create U&lt;br /&gt;    CONCAT(&quot;CREATE TEMPORARY TABLE &quot;, recursive_table_union, &quot; LIKE &quot;, recursive_table);&lt;br /&gt;  PREPARE stmt FROM @str;&lt;br /&gt;  EXECUTE stmt;&lt;br /&gt;  SET @str = # create T1&lt;br /&gt;    CONCAT(&quot;CREATE TEMPORARY TABLE &quot;, recursive_table_next, &quot; LIKE &quot;, recursive_table);&lt;br /&gt;  PREPARE stmt FROM @str;&lt;br /&gt;  EXECUTE stmt;&lt;br /&gt;  if max_recursion = 0 then&lt;br /&gt;    set max_recursion = 100; # a default to protect the innocent&lt;br /&gt;  end if;&lt;br /&gt;  recursion: repeat&lt;br /&gt;    # add T0 to U (this is always UNION ALL)&lt;br /&gt;    SET @str =&lt;br /&gt;      CONCAT(&quot;INSERT INTO &quot;, recursive_table_union, &quot; SELECT * FROM &quot;, recursive_table);&lt;br /&gt;    PREPARE stmt FROM @str;&lt;br /&gt;    EXECUTE stmt;&lt;br /&gt;    # we are done if max depth reached&lt;br /&gt;    set max_recursion = max_recursion - 1;&lt;br /&gt;    if not max_recursion then&lt;br /&gt;      if show_progress then&lt;br /&gt;        select concat(&quot;max recursion exceeded&quot;);&lt;br /&gt;      end if;&lt;br /&gt;      leave recursion;&lt;br /&gt;    end if;&lt;br /&gt;    # fill T1 by applying the recursive SELECT on T0&lt;br /&gt;    SET @str =&lt;br /&gt;      CONCAT(&quot;INSERT INTO &quot;, recursive_table_next, &quot; &quot;, recursive_SELECT);&lt;br /&gt;    PREPARE stmt FROM @str;&lt;br /&gt;    EXECUTE stmt;&lt;br /&gt;    # we are done if no rows in T1&lt;br /&gt;    select row_count() into new_rows;&lt;br /&gt;    if show_progress then&lt;br /&gt;      select concat(new_rows, &quot; new rows found&quot;);&lt;br /&gt;    end if;&lt;br /&gt;    if not new_rows then&lt;br /&gt;      leave recursion;&lt;br /&gt;    end if;&lt;br /&gt;    # Prepare next iteration:&lt;br /&gt;    # T1 becomes T0, to be the source of next run of recursive_SELECT,&lt;br /&gt;    # T0 is recycled to be T1.&lt;br /&gt;    SET @str =&lt;br /&gt;      CONCAT(&quot;ALTER TABLE &quot;, recursive_table, &quot; RENAME &quot;, recursive_table_tmp);&lt;br /&gt;    PREPARE stmt FROM @str;&lt;br /&gt;    EXECUTE stmt;&lt;br /&gt;    # we use ALTER TABLE RENAME because RENAME TABLE does not support temp tables&lt;br /&gt;    SET @str =&lt;br /&gt;      CONCAT(&quot;ALTER TABLE &quot;, recursive_table_next, &quot; RENAME &quot;, recursive_table);&lt;br /&gt;    PREPARE stmt FROM @str;&lt;br /&gt;    EXECUTE stmt;&lt;br /&gt;    SET @str =&lt;br /&gt;      CONCAT(&quot;ALTER TABLE &quot;, recursive_table_tmp, &quot; RENAME &quot;, recursive_table_next);&lt;br /&gt;    PREPARE stmt FROM @str;&lt;br /&gt;    EXECUTE stmt;&lt;br /&gt;    # empty T1&lt;br /&gt;    SET @str =&lt;br /&gt;      CONCAT(&quot;TRUNCATE TABLE &quot;, recursive_table_next);&lt;br /&gt;    PREPARE stmt FROM @str;&lt;br /&gt;    EXECUTE stmt;&lt;br /&gt;  until 0 end repeat;&lt;br /&gt;  # eliminate T0 and T1&lt;br /&gt;  SET @str =&lt;br /&gt;    CONCAT(&quot;DROP TEMPORARY TABLE &quot;, recursive_table_next, &quot;, &quot;, recursive_table);&lt;br /&gt;  PREPARE stmt FROM @str;&lt;br /&gt;  EXECUTE stmt;&lt;br /&gt;  # Final (output) SELECT uses recursive_table name&lt;br /&gt;  SET @str =&lt;br /&gt;    CONCAT(&quot;ALTER TABLE &quot;, recursive_table_union, &quot; RENAME &quot;, recursive_table);&lt;br /&gt;  PREPARE stmt FROM @str;&lt;br /&gt;  EXECUTE stmt;&lt;br /&gt;  # Run final SELECT on UNION&lt;br /&gt;  SET @str = final_SELECT;&lt;br /&gt;  PREPARE stmt FROM @str;&lt;br /&gt;  EXECUTE stmt;&lt;br /&gt;  # No temporary tables may survive:&lt;br /&gt;  SET @str =&lt;br /&gt;    CONCAT(&quot;DROP TEMPORARY TABLE &quot;, recursive_table);&lt;br /&gt;  PREPARE stmt FROM @str;&lt;br /&gt;  EXECUTE stmt;&lt;br /&gt;  # We are done :-)&lt;br /&gt;END|&lt;br /&gt;&lt;br /&gt;delimiter ;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;In the SQL Standard, WITH RECURSIVE allows some nice additional tweaks (depth-first or breadth-first ordering, cycle detection). In future posts I will show how to emulate them too.&lt;/div&gt;</description>
         <author>Guilhem Bichot</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-9191231274999121063.post-5334856650064524845</guid>
         <pubDate>Mon, 18 Nov 2013 06:17:00 +0000</pubDate>
      </item>
      <item>
         <title>FAQ: InnoDB extended secondary keys</title>
         <link>http://jorgenloland.blogspot.com/2013/10/faq-innodb-extended-secondary-indexes.html</link>
         <description>MySQL 5.6 introduced a new feature called &lt;i&gt;extended secondary keys&lt;/i&gt;. We get a lot of questions about it and find that most of them come from a few incorrect assumption. In this post I&amp;#39;ll try to get rid of the confusion once and for all. Famous last words... here goes:&lt;br&gt;&lt;b&gt;&lt;br&gt;&lt;/b&gt;&lt;b&gt;Q1: Do I need to do anything to enable extended secondary keys?&lt;/b&gt;&lt;br&gt;&lt;br&gt;No, nothing at all. It&amp;#39;s on by default and I can&amp;#39;t see any sensible reason why you would want to disable it. However, it is possible to disable it by tuning the optimizer_switch: &lt;span style=&quot;&quot;&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;SET optimizer_switch=&amp;#39;use_index_extensions={on|off}&amp;#39;.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;Q2: Does extended secondary keys only work with InnoDB?&lt;/b&gt;&lt;br&gt;&lt;br&gt;No, it should work with any storage engine that uses the primary key columns as reference to the row, which means most storage engines with clustered primary keys. I say &amp;quot;should&amp;quot; because it requires a minimum of work from the storage engine provider; it must announce to MySQL that it is supported.&lt;br&gt;&lt;br&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://jorgenloland.blogspot.com/2013/10/faq-innodb-extended-secondary-indexes.html#more&quot;&gt;Read more »&lt;/a&gt;</description>
         <author>Jørgen Løland</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-7171477457129187873.post-5031148408259442918</guid>
         <pubDate>Mon, 21 Oct 2013 13:11:00 +0000</pubDate>
      </item>
      <item>
         <title>The second MySQL seminar: a summary</title>
         <link>http://jorgenloland.blogspot.com/2013/10/the-second-mysql-seminar-summary.html</link>
         <description>Once again, Geir Høydalsvik and I had the pleasure of hosting a MySQL mini-seminar in Trondheim. 25+ attendants from at least 7 different companies and a few professors from the &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://www.idi.ntnu.no//&quot;&gt;computer science dept.&lt;/a&gt; at &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://www.ntnu.edu/&quot;&gt;NTNU&lt;/a&gt; showed up on yesterdays event. I recognized many of these from the first seminar but there were some new faces as well.&lt;br /&gt;&lt;br /&gt;This time, &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://www.markleith.co.uk/&quot;&gt;Mark Leith&lt;/a&gt; came on a visit from the UK. He gave an introduction to Performance Schema and ps_helper. ps_helper is a really nice tool to make sense of the overwhelming amount of data collected by PS. He also gave a very convincing demo of MySQL Enterprise Monitor (MEM). More than a few attendants now plan to give MEM a try in their environment. You can too - there's a 30 day trial, which should be more than enough to decide if you need it :-)&lt;br /&gt;&lt;br /&gt;Like last time, we had great discussions, pizza and a very good time. I'm looking forward to the next seminar already.&lt;br /&gt;&lt;br /&gt;If you're interested, Mark's PS presentation can be found on &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://www.slideshare.net/Leithal/performance-schema-andpshelper&quot;&gt;slideshare&lt;/a&gt;. ps_helper is both on &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://github.com/MarkLeith/dbahelper&quot;&gt;github&lt;/a&gt; and on his &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://www.markleith.co.uk/ps_helper/&quot;&gt;blog&lt;/a&gt;. ps_helper comes highly recommended!</description>
         <author>Jørgen Løland</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-7171477457129187873.post-2140314768164038700</guid>
         <pubDate>Fri, 18 Oct 2013 13:02:00 +0000</pubDate>
      </item>
      <item>
         <title>Next MySQL mini-seminar in Trondheim October 17</title>
         <link>http://jorgenloland.blogspot.com/2013/09/next-mysql-mini-seminar-in-trondheim.html</link>
         <description>We have the pleasure to announce that the next MySQL mini-seminar in Trondheim will be held on October 17 15:00-18:00. This time, MySQL Monitoring and performance schema evangelist Mark Leith will visit us. The agenda will be similar to last mini-seminar:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Presentation: &lt;span class=&quot;bodycopy&quot;&gt;&lt;span class=&quot;bodycopy&quot;&gt;MySQL monitoring and performance Schema,&lt;/span&gt;&lt;/span&gt; by Mark Leith&lt;/li&gt;&lt;li&gt;Q&amp;amp;A, discussions&lt;/li&gt;&lt;li&gt;Suggestions for presentations on future seminars&lt;/li&gt;&lt;li&gt;Food and mingling&lt;/li&gt;&lt;/ul&gt;For more info, see the &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://www.oracle.com/webapps/events/ns/EventsDetail.jsp?p_eventId=174804&amp;amp;src=7866894&amp;amp;src=7866894&amp;amp;Act=75&quot;&gt;registration page&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;Don't miss this opportunity to meet experts, developers and other MySQL users.</description>
         <author>Jørgen Løland</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-7171477457129187873.post-4799241006161065636</guid>
         <pubDate>Fri, 13 Sep 2013 14:21:00 +0000</pubDate>
      </item>
      <item>
         <title>The first MySQL mini-seminar in Trondheim was a huge success!</title>
         <link>http://jorgenloland.blogspot.com/2013/06/the-first-mysql-mini-seminar-in.html</link>
         <description>Yesterday, Geir Høydalsvik and I had the pleasure of hosting a MySQL mini-seminar in Trondheim, Norway.&lt;br /&gt;&lt;br /&gt;The topic of the day was a presentation by yours truly on how the MySQL optimizer works. Geir briefly explained how the MySQL teams are organized in Oracle, and that our focus is on delivering high quality on time.&lt;br /&gt;&lt;br /&gt;We had lots of interesting questions and discussions (and pizza) afterwards. Of particular interest was:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;How the MySQL code base can be modularized to make it maintainable and testable. The takeaway was that MySQL has invested a lot on refactoring the last couple of years to improve in this area and will continue to do so in both the near and far future.&lt;/li&gt;&lt;li&gt;How testing is done in MySQL. The answer was that the QA teams have been significantly ramped up since Sun acquired MySQL. In addition to much more resources to the QA teams, developers are now expected to write unit tests using the Google unit test framework for new functionality whenever it makes sense. Refactoring and modularization of the code makes this a lot easier to do now.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Close to 20 MySQL users attended in addition to a handful of MySQL hackers located in the Trondheim office. The users had very different backgrounds: from start-ups to solid established companies, and deployment on MySQL 5.1, 5.5 and 5.6. All agreed that the mini-seminar was a great initiative and that it should not be a one-time event. Yesterday's event will therefore not be &lt;i&gt;the&lt;/i&gt; mini-seminar, but rather &lt;i&gt;the first in a series&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;We'll get back to you with agenda and date for the next mini-seminar once the details have been decided.</description>
         <author>Jørgen Løland</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-7171477457129187873.post-7251165161073780726</guid>
         <pubDate>Thu, 20 Jun 2013 11:16:00 +0000</pubDate>
      </item>
      <item>
         <title>The Outer Join to Inner Join Coversion</title>
         <link>http://optimize-this.blogspot.com/2013/05/the-outer-join-to-inner-join-coversion.html</link>
         <description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align:left;&quot;&gt;&lt;div dir=&quot;ltr&quot; style=&quot;text-align:left;&quot;&gt;&lt;br /&gt;It is a central part of the MySQL philisophy to try and help you as much as you can. There are many occasions when it &lt;i&gt;could&lt;/i&gt; tell you that what you are asking for is utterly stupid or give you a bad execution plan because &quot;you asked for it&quot;. But we're friendly here. We don't do that. One of those cases is when you run a query with an outer join but you really meant an inner join, or you don't care.&lt;br /&gt;&lt;br /&gt;Inner joins are almost always cheaper to execute than outer joins and that is why MySQL rewrites them to inner joins whenever it can.&lt;br /&gt;&lt;br /&gt;An outer join may have&amp;nbsp;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;WHERE&lt;/span&gt;&amp;nbsp;conditions on any of the columns in the result set. In fact, it is a common trick to find non-matching rows using the&amp;nbsp;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;IS NULL&lt;/span&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt;&amp;nbsp;predicate. Here's an example:&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;TABLE person&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;name &amp;nbsp;&lt;u&gt;id&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;Tom &amp;nbsp; 1&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;Dick &amp;nbsp;2&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;Harry 3&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;TABLE car&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;brand owner&lt;i&gt;_&lt;/i&gt;id mfg_year&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;Chevy 1 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;2007&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;Volvo 3 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;2008&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt;The query&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;SELECT name&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;FROM person LEFT OUTER JOIN car ON id = owner&lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;id&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;WHERE brand IS NULL&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt;would give you the names of people that don't have a car.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt;The &lt;/span&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;IS NULL&lt;/span&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt; predicate is&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;TRUE&lt;/span&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt;&amp;nbsp;for an&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;UNKNOWN&lt;/span&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt;&amp;nbsp;value. Nearly all other predicates are what we refer to as&amp;nbsp;&lt;i&gt;null-rejecting&lt;/i&gt;. A null-rejecting predicate is one that is&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;UNKNOWN&lt;/span&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt; if any of its arguments is &lt;/span&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;UNKNOWN&lt;/span&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt;. This goes for most SQL predicates: &amp;gt;, &amp;lt;, =.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt;On the top-level of the where clause, there is an implicit &lt;/span&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;IS TRUE&lt;/span&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt; predicate. This way you never get to see rows where it is unknown if they match your search condition or not. So the where clause in our query above is equivalent to&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;WHERE brand IS NULL IS TRUE&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;We could also query&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;SELECT name&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;FROM person LEFT OUTER JOIN car ON id = owner&lt;u&gt;_&lt;/u&gt;id&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;WHERE mfg_year = 2008 IS UNKNOWN&lt;/span&gt;&lt;br /&gt;&lt;div&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt;The result of which would be &lt;/span&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&quot;Dick&quot;&lt;/span&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt;, because it is indeed unknown if Dick's car was made in 2008 if he doesn't have a car. Computation wise, the condition is evaluated bottom-up: &lt;/span&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;UNKNOWN&lt;/span&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt; = 2008 is &lt;/span&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;UNKNOWN&lt;/span&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt;.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt;We are now ready to turn this notion into a rule-of-thumb: If there are predicates in the where clause that are not nested inside predicates that turn &lt;/span&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;UNKNOWN&lt;/span&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt; into &lt;/span&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;TRUE&lt;/span&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt;, for example&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;IS NULL&lt;/span&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt; or &lt;/span&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;IS UNKNOWN&lt;/span&gt;&lt;span style=&quot;font-family:Times, Times New Roman, serif;&quot;&gt; (i.e. null-rejecting), then we can turn the outer join into an inner join before we optimize, because the result set is the same.&amp;nbsp;&lt;/span&gt;Unfortunately this translation does not show up in &lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;EXPLAIN&lt;/span&gt;, but it does in the optimizer trace. Here's how to see it.&lt;/div&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;SET optimizer_trace=&quot;enabled=on&quot;;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;SELECT name&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;FROM person LEFT OUTER JOIN car ON id = owner_id&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;WHERE mfg_year = 2008;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;+-------+&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;| name &amp;nbsp;|&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;+-------+&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;| Harry |&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;+-------+&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;SELECT trace FROM information_schema.optimizer_trace;&lt;/span&gt;&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp; {&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;join_optimization&quot;: {&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;select#&quot;: 1,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;steps&quot;: [&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;transformations_to_nested_joins&quot;: {&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;transformations&quot;: [&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;&lt;span style=&quot;color:red;&quot;&gt;outer_join_to_inner_join&lt;/span&gt;&quot;,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;JOIN_condition_to_WHERE&quot;,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;parenthesis_removal&quot;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ],&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family:Courier New, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;expanded_query&quot;: &quot;/* select#1 */ select `person`.`name` AS `name` from `person` join `car` where ((`car`.`mfg_year` = 2008) and (`person`.`id` = `car`.`owner_id`))&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;...&lt;/div&gt;</description>
         <author>Martin</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-4934478127523643312.post-2087765769502606641</guid>
         <pubDate>Wed, 15 May 2013 05:41:00 +0000</pubDate>
      </item>
      <item>
         <title>When is a Subquery Executed?</title>
         <link>http://oysteing.blogspot.com/2013/05/when-is-subquery-executed.html</link>
         <description>&lt;style type=&quot;text/css&quot;&gt;&lt;!--div.codebox {height:100%;width:100%;border:1px solid;background-color:#EEEEEE;padding:4px;}--&gt;&lt;/style&gt;&lt;p&gt;In an earlier &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://oysteing.blogspot.com/2012/07/from-months-to-seconds-with-subquery.html&quot;&gt;blog post&lt;/a&gt;, I managed to confuse myself as to when a subquery was executed.  It is not very clear from the output of EXPLAIN where the execution of a subquery takes place.  Let's take a look at the following example query (Query 17 in the DBT-3 benchmark): &lt;br /&gt;&lt;div class=&quot;codebox&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;select sum(l_extendedprice) / 7.0 as avg_yearly&lt;br /&gt;from lineitem, part&lt;br /&gt;where p_partkey = l_partkey&lt;br /&gt;  and p_brand = 'Brand#33' and p_container = 'LG CAN'&lt;br /&gt;  and l_quantity &amp;lt; (&lt;br /&gt; select 0.2 * avg(l_quantity)&lt;br /&gt; from lineitem&lt;br /&gt; where l_partkey = p_partkey&lt;br /&gt;);&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you run EXPLAIN on this query, you will see the following execution plan: &lt;br /&gt;&lt;div class=&quot;codebox&quot; style=&quot;overflow:auto;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;+----+--------------------+----------+------+---------------------------------+---------------------+---------+---------------------+---------+-------------+&lt;br /&gt;| id | select_type        | table    | type | possible_keys                   | key                 | key_len | ref                 | rows    | Extra       |&lt;br /&gt;+----+--------------------+----------+------+---------------------------------+---------------------+---------+---------------------+---------+-------------+&lt;br /&gt;|  1 | PRIMARY            | part     | ALL  | PRIMARY                         | NULL                | NULL    | NULL                | 2000000 | Using where |&lt;br /&gt;|  1 | PRIMARY            | lineitem | ref  | i_l_suppkey_partkey,i_l_partkey | i_l_suppkey_partkey | 5       | dbt3.part.p_partkey |      14 | Using where |&lt;br /&gt;|  2 | DEPENDENT SUBQUERY | lineitem | ref  | i_l_suppkey_partkey,i_l_partkey | i_l_suppkey_partkey | 5       | dbt3.part.p_partkey |      14 | NULL        |&lt;br /&gt;+----+--------------------+----------+------+---------------------------------+---------------------+---------+---------------------+---------+-------------+&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It is not straightforward to determine from this plan when the subquery will be executed.  We see that both tables of the join has &quot;Using where&quot; in the Extra column.  In other words, the original WHERE clause has been split between the two tables.  The question is: Which part contains the subquery? &lt;/p&gt;&lt;p&gt;In this specific case, the answer is pretty evident when looking closer at the query.  The subquery is part of an expression which refers to a column of the &lt;tt&gt;lineitem&lt;/tt&gt; table, &lt;tt&gt;l_quantity&lt;/tt&gt;, and within the subquery, there is also also a reference to &lt;tt&gt;p_partkey&lt;/tt&gt; of the &lt;tt&gt;part&lt;/tt&gt; table.  In other words, columns from both tables of the join is needed when executing the subqquery.  Hence, the subquery will be part of the where clause attached to the last table of the join.   &lt;/p&gt;&lt;p&gt;This means that the above query will, for each row in the &lt;tt&gt;part&lt;/tt&gt; table that satisfies the condition attached to the &lt;tt&gt;part&lt;/tt&gt; table, look up matching rows in the &lt;tt&gt;lineitem&lt;/tt&gt; table, and, for every matching row, execute the subquery.  As you probably realize, one will have to know quite a bit about how the optimizer handles WHERE conditions in order to deduce this.  But don't despair!  An extension to EXPLAIN in MySQL 5.6 make things much clearer. &lt;/p&gt; &lt;h1&gt;Structured EXPLAIN&lt;/h1&gt;&lt;p&gt;With MySQL 5.6, you can get &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://glebshchepa.blogspot.com/2012/04/optimizer-new-explain-formatjson.html&quot;&gt;a structured version of the query plan in JSON format&lt;/a&gt;.  All you need to do is to write &quot;EXPLAIN FORMAT=JSON&quot; instead of just EXPLAIN.  Let's take a look at what the JSON EXPLAIN output for the query discussed above looks like: &lt;br /&gt;&lt;div class=&quot;codebox&quot; style=&quot;overflow:auto;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;{&lt;br /&gt;  &quot;query_block&quot;: {&lt;br /&gt;    &quot;select_id&quot;: 1,&lt;br /&gt;    &quot;nested_loop&quot;: [&lt;br /&gt;      {&lt;br /&gt;        &quot;table&quot;: {&lt;br /&gt;          &quot;table_name&quot;: &quot;part&quot;,&lt;br /&gt;          &quot;access_type&quot;: &quot;ALL&quot;,&lt;br /&gt;          &quot;possible_keys&quot;: [&lt;br /&gt;            &quot;PRIMARY&quot;&lt;br /&gt;          ] /* possible_keys */,&lt;br /&gt;          &quot;rows&quot;: 2000000,&lt;br /&gt;          &quot;filtered&quot;: 100,&lt;br /&gt;          &quot;attached_condition&quot;: &quot;((`dbt3`.`part`.`p_container` = 'LG CAN') and (`dbt3`.`part`.`p_brand` = 'Brand#33'))&quot;&lt;br /&gt;        } /* table */&lt;br /&gt;      },&lt;br /&gt;      {&lt;br /&gt;        &quot;table&quot;: {&lt;br /&gt;          &quot;table_name&quot;: &quot;lineitem&quot;,&lt;br /&gt;          &quot;access_type&quot;: &quot;ref&quot;,&lt;br /&gt;          &quot;possible_keys&quot;: [&lt;br /&gt;            &quot;i_l_suppkey_partkey&quot;,&lt;br /&gt;            &quot;i_l_partkey&quot;&lt;br /&gt;          ] /* possible_keys */,&lt;br /&gt;          &quot;key&quot;: &quot;i_l_suppkey_partkey&quot;,&lt;br /&gt;          &quot;used_key_parts&quot;: [&lt;br /&gt;            &quot;l_partkey&quot;&lt;br /&gt;          ] /* used_key_parts */,&lt;br /&gt;          &quot;key_length&quot;: &quot;5&quot;,&lt;br /&gt;          &quot;ref&quot;: [&lt;br /&gt;            &quot;dbt3.part.p_partkey&quot;&lt;br /&gt;          ] /* ref */,&lt;br /&gt;          &quot;rows&quot;: 14,&lt;br /&gt;          &quot;filtered&quot;: 100,&lt;br /&gt;          &quot;attached_condition&quot;: &quot;(`dbt3`.`lineitem`.`l_quantity` &amp;lt; (/* select#2 */ select (0.2 * avg(`dbt3`.`lineitem`.`l_quantity`)) from `dbt3`.`lineitem` where (`dbt3`.`lineitem`.`l_partkey` = `dbt3`.`part`.`p_partkey`)))&quot;,&lt;br /&gt;          &quot;attached_subqueries&quot;: [&lt;br /&gt;            {&lt;br /&gt;              &quot;dependent&quot;: true,&lt;br /&gt;              &quot;cacheable&quot;: false,&lt;br /&gt;              &quot;query_block&quot;: {&lt;br /&gt;                &quot;select_id&quot;: 2,&lt;br /&gt;                &quot;table&quot;: {&lt;br /&gt;                  &quot;table_name&quot;: &quot;lineitem&quot;,&lt;br /&gt;                  &quot;access_type&quot;: &quot;ref&quot;,&lt;br /&gt;                  &quot;possible_keys&quot;: [&lt;br /&gt;                    &quot;i_l_suppkey_partkey&quot;,&lt;br /&gt;                    &quot;i_l_partkey&quot;&lt;br /&gt;                  ] /* possible_keys */,&lt;br /&gt;                  &quot;key&quot;: &quot;i_l_suppkey_partkey&quot;,&lt;br /&gt;                  &quot;used_key_parts&quot;: [&lt;br /&gt;                    &quot;l_partkey&quot;&lt;br /&gt;                  ] /* used_key_parts */,&lt;br /&gt;                  &quot;key_length&quot;: &quot;5&quot;,&lt;br /&gt;                  &quot;ref&quot;: [&lt;br /&gt;                    &quot;dbt3.part.p_partkey&quot;&lt;br /&gt;                  ] /* ref */,&lt;br /&gt;                  &quot;rows&quot;: 14,&lt;br /&gt;                  &quot;filtered&quot;: 100&lt;br /&gt;                } /* table */&lt;br /&gt;              } /* query_block */&lt;br /&gt;            }&lt;br /&gt;          ] /* attached_subqueries */&lt;br /&gt;        } /* table */&lt;br /&gt;      }&lt;br /&gt;    ] /* nested_loop */&lt;br /&gt;  } /* query_block */&lt;br /&gt;}&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You probably recognize much of the information from traditional EXPLAIN, but there is also some information that traditional EXPLAIN do not show.  The &quot;attached_condition&quot; field shows which parts of the WHERE condition is evaluated at which stage of the query execution.  That is, the condition attached to a table will be evaluated for each row that is read from this table.  In this specific example, it shows that the subquery is part of the condition attached to the lineitem table, and the &quot;attached_subqueries&quot; field for that table contains the execution plan for the subquery. &lt;/p&gt; &lt;h1&gt;Visual EXPLAIN&lt;/h1&gt;&lt;p&gt;Structured EXPLAIN, or EXPLAIN in JSON format, is also much more suitable for tools that want to process and display query plans.  &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://www.mysql.com/products/workbench/&quot;&gt;MySQL Workbench 5.2&lt;/a&gt; uses structural EXPLAIN to visualize MySQL query plans.  Below is shown the result of Visual EXPLAIN for the example query used in this blog post:  &lt;br /&gt;&lt;div class=&quot;separator&quot; style=&quot;clear:both;text-align:center;&quot;&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://4.bp.blogspot.com/-ihACtNde50g/UYJT2kYO7ZI/AAAAAAAAAE0/1VzwsmeNMG4/s1600/explain-q17.png&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://4.bp.blogspot.com/-ihACtNde50g/UYJT2kYO7ZI/AAAAAAAAAE0/1VzwsmeNMG4/s640/explain-q17.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;Note that MySQL Workbench colors the table boxes based on which access method is used. A &lt;span style=&quot;color:#990000;&quot;&gt;red&lt;/span&gt; box indicates a tables scan, while a &lt;span style=&quot;color:#38761d;&quot;&gt;green&lt;/span&gt; box is used for index look-ups (&lt;i&gt;ref access&lt;/i&gt;).&lt;br /&gt;&lt;/p&gt;&lt;p&gt;As we all know: &quot;A picture is worth a thousand words&quot;, and from the  above picture it should be pretty clear when the subquery is executed. &lt;/p&gt;</description>
         <author>Øystein</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-1508669603650457962.post-4580765254747632489</guid>
         <pubDate>Thu, 02 May 2013 14:17:00 +0000</pubDate>
         <media:thumbnail height="72" url="http://4.bp.blogspot.com/-ihACtNde50g/UYJT2kYO7ZI/AAAAAAAAAE0/1VzwsmeNMG4/s72-c/explain-q17.png" width="72" xmlns:media="http://search.yahoo.com/mrss/"/>
      </item>
      <item>
         <title>When and How to Take Advantage of New Optimizer Features in MySQL 5.6</title>
         <link>http://oysteing.blogspot.com/2013/02/when-and-how-to-take-advantage-of-new.html</link>
         <description>A few weeks ago, I made a presentation with the above title at &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;https://fosdem.org/2013/&quot;&gt;FOSDEM&lt;/a&gt; in Brussels.  My slides can be found &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;https://fosdem.org/2013/schedule/event/new_opt_mysql/attachments/slides/288/export/events/attachments/new_opt_mysql/slides/288/fosdem2013.pdf&quot;&gt;here&lt;/a&gt;.  The &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;https://fosdem.org/2013/schedule/track/mysql_and_friends/&quot;&gt;MySQL and Friends devroom&lt;/a&gt; had many interesting presentations and the room was full for most of them.</description>
         <author>Øystein</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-1508669603650457962.post-4376249402434441334</guid>
         <pubDate>Fri, 22 Feb 2013 19:08:00 +0000</pubDate>
      </item>
      <item>
         <title>Fixing awkward TIMESTAMP behaviors...</title>
         <link>http://guilhembichot.blogspot.com/2013/02/fixing-awkward-timestamp-behaviors.html</link>
         <description>There are great features in MySQL 5.6. But not only that. We also tried to correct some old behaviors and limitations which, over the years, have shown to irritate our Community. The behavior of TIMESTAMP columns is one of them.&lt;br /&gt;&lt;br /&gt;My colleague Martin Hansson did most of the work and summarized it well in his &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://optimize-this.blogspot.co.uk/2012/04/datetime-default-now-finally-available.html&quot;&gt;blog&lt;/a&gt;. Thanks to him, since MySQL 5.6.5, it's possible to declare more than one TIMESTAMP column with the DEFAULT CURRENT_TIMESTAMP or ON UPDATE CURRENT_TIMESTAMP attributes. And it's possible to have DATETIME columns with such attributes. Two limitations lifted!&lt;br /&gt;&lt;br /&gt;But that is not the end of the story. TIMESTAMP was still special. Unlike other datatypes, if not declared with the NULL or NOT NULL attributes, it would automatically get NOT NULL. And the first TIMESTAMP column of the table would automatically get DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP. Many people found these behaviors odd. For them, my colleague Gopal Shankar added a new option to the server, documented &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_explicit_defaults_for_timestamp&quot;&gt;here&lt;/a&gt;. The old behavior, now depreciated, is still available if the option is not used.&lt;br /&gt;&lt;br /&gt;But that is still not the end of the story. &quot;CREATE TABLE ... SELECT&quot; dealt strangely with columns having DEFAULT CURRENT_TIMESTAMP. Just look at this simple example, which shows results with version 5.6.9: &lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;CREATE TABLE t1 (&lt;br /&gt;t1a TIMESTAMP DEFAULT CURRENT_TIMESTAMP,&lt;br /&gt;t1b TIMESTAMP DEFAULT '2000-01-01 01:00:00'&lt;br /&gt;);&lt;br /&gt;INSERT INTO t1 VALUES ();&lt;br /&gt;SELECT * FROM t1;&lt;br /&gt;t1a t1b&lt;br /&gt;2013-02-19 18:12:41 2000-01-01 01:00:00&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;The content of t1 is as expected (remember that &quot;()&quot; in INSERT means &quot;insert columns' defaults&quot;).&lt;br /&gt;Now let's create a second table, which should have four columns: first, two extra columns t2a and t2b, then two columns filled with values selected from t1: &lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;CREATE TABLE t2 (&lt;br /&gt;t2a TIMESTAMP DEFAULT CURRENT_TIMESTAMP,&lt;br /&gt;t2b TIMESTAMP DEFAULT '2000-01-01 02:00:00'&lt;br /&gt;) SELECT * FROM t1;&lt;br /&gt;SHOW CREATE TABLE t2;&lt;br /&gt;Table Create Table&lt;br /&gt;t2 CREATE TABLE `t2` (&lt;br /&gt;  `t2a` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,&lt;br /&gt;  `t2b` timestamp NOT NULL DEFAULT '2000-01-01 02:00:00',&lt;br /&gt;  `t1a` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',&lt;br /&gt;  `t1b` timestamp NOT NULL DEFAULT '2000-01-01 01:00:00'&lt;br /&gt;) ENGINE=InnoDB DEFAULT CHARSET=latin1&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;See how t2.t1b inherited the DEFAULT attribute of its source column t1.t1b: &lt;i&gt;DEFAULT '2000-01-01 01:00:00'&lt;/i&gt;, as expected, and as the documentation says. But! t2.t1a did NOT inherit the DEFAULT attribute of its source column t1.t1a: it rather got the strange &lt;i&gt;DEFAULT '0000-00-00 00:00:00'&lt;/i&gt; (year zero...). That's one first problem: constant defaults are properly inherited, but function defaults are not.&lt;br /&gt;Now let's look at the content of t2: &lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;SELECT * FROM t2;&lt;br /&gt;t2a t2b t1a t1b&lt;br /&gt;0000-00-00 00:00:00 2000-01-01 02:00:00 2013-02-19 18:12:41 2000-01-01 01:00:00&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;The last two columns, which have their source in t1, have the same value as their source column, which is correct.&lt;br /&gt;The two first (extra) columns did not have their values specified, so their default should have been inserted. That's what nicely happened for t2b. But not for t2a: year zero again! That's one second problem: an extra column is not filled with its default if it is a function default.&lt;br /&gt;&lt;br /&gt;I grouped those two problems under the name of Bug#16163936, and fixed them. Here are the results in 5.6.10: &lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;SHOW CREATE TABLE t2;&lt;br /&gt;Table Create Table&lt;br /&gt;t2 CREATE TABLE `t2` (&lt;br /&gt;  `t2a` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,&lt;br /&gt;  `t2b` timestamp NOT NULL DEFAULT '2000-01-01 02:00:00',&lt;br /&gt;  `t1a` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,&lt;br /&gt;  `t1b` timestamp NOT NULL DEFAULT '2000-01-01 01:00:00'&lt;br /&gt;) ENGINE=InnoDB DEFAULT CHARSET=latin1&lt;br /&gt;SELECT * FROM t2;&lt;br /&gt;t2a t2b t1a t1b&lt;br /&gt;2013-02-19 18:27:39 2000-01-01 02:00:00 2013-02-19 18:27:39 2000-01-01 01:00:00&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;All correct!</description>
         <author>Guilhem Bichot</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-9191231274999121063.post-4692471887967877271</guid>
         <pubDate>Tue, 19 Feb 2013 08:12:00 +0000</pubDate>
      </item>
      <item>
         <title>DBT-3 Q3: 6 x performance in MySQL 5.6.10</title>
         <link>http://jorgenloland.blogspot.com/2013/02/dbt-3-q3-6-x-performance-in-mysql-5610.html</link>
         <description>When MySQL gets a query, it is the job of the optimizer to find the cheapest way to execute that query. Decisions include access method (range access, table scan, index lookup etc), join order, sorting strategy etc. If we simplify a bit,  the optimizer first identifies the different ways to access each table and calculate their cost. After that, the join order is decided.&lt;br&gt;&lt;br&gt;However, some access methods can only be considered after the join order has been decided and therefore gets special treatment in the MySQL optimizer. For join conditions, e.g. &lt;span style=&quot;&quot;&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;&amp;quot;WHERE table1.col1 = table2.col2&amp;quot;&lt;/span&gt;&lt;/span&gt;,  index lookup can only be used in table2 if table1 is earlier in the join sequence. Another class of access methods is only meaningful for tables that are first in the join order. An example is queries with &lt;span style=&quot;&quot;&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;ORDER BY ... LIMIT&lt;/span&gt;&lt;/span&gt;. Prior to MySQL 5.6.10 there was a bug in MySQL that made the optimizer choose inefficient execution plans for this query type. That&amp;#39;s the topic I discuss below.&lt;br&gt;&lt;br&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://jorgenloland.blogspot.com/2013/02/dbt-3-q3-6-x-performance-in-mysql-5610.html#more&quot;&gt;Read more »&lt;/a&gt;</description>
         <author>Jørgen Løland</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-7171477457129187873.post-4259885788151545839</guid>
         <pubDate>Wed, 13 Feb 2013 09:18:00 +0000</pubDate>
         <media:thumbnail height="72" url="http://2.bp.blogspot.com/-QULXhZ0pFvc/URtLf4Mjc9I/AAAAAAAAAFE/u8GlHHS9LEA/s72-c/limit_resptime3.jpg" width="72" xmlns:media="http://search.yahoo.com/mrss/"/>
      </item>
      <item>
         <title>Gluh on InnoDB extended secondary keys</title>
         <link>http://jorgenloland.blogspot.com/2012/12/gluh-on-innodb-extended-secondary-keys.html</link>
         <description>&lt;div class=&quot;post-title entry-title&quot;&gt;Sergey Glukhov (Gluh) recently wrote an interesting blog about InnoDB secondary key improvements in MySQL 5.6. His blog isn't aggregated to planet.mysql.com but certainly deserves some attention.&amp;nbsp;&lt;/div&gt;&lt;div class=&quot;post-title entry-title&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;post-title entry-title&quot;&gt;Here it is: &lt;span style=&quot;font-size:small;&quot;&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://glukhsv.blogspot.co.uk/2012/12/innodb-extended-secondary-keys.html&quot;&gt;InnoDB, extended secondary keys&lt;/a&gt;. &lt;/span&gt;&lt;/div&gt;</description>
         <author>Jørgen Løland</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-7171477457129187873.post-5451854446963667448</guid>
         <pubDate>Thu, 20 Dec 2012 08:38:00 +0000</pubDate>
      </item>
      <item>
         <title>InnoDB, extended secondary keys.</title>
         <link>http://glukhsv.blogspot.com/2012/12/innodb-extended-secondary-keys.html</link>
         <description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align:left;&quot;&gt;It's a well-know fact that InnoDB secondary keys contain both user defined columns and the primary key columns. For example, if a table has PRIMARY KEY(pk) and secondary key k1(f1), then index k1 is internally stored as k1(f1,pk). &lt;br /&gt;&lt;br /&gt;Prior to version 5.6.9, the MySQL optimizer could only partially use these extended primary key columns: they could be used for sorting and to provide index only access. Starting from MySQL 5.6.9, the optimizer makes full use of the extended columns. This means that 'ref' access, range access, MIN/MAX optimizations, index_merge, loose index scan etc all works as if you had created the index with all primary key columns in all secondary keys. The new feature is turned on and off by optimizer switch 'use_index_extensions' and is on by default.&lt;br /&gt;&lt;br /&gt;Consider we have following table:&lt;br /&gt;&lt;br /&gt;CREATE TABLE t1&lt;br /&gt;(&lt;br /&gt;&amp;nbsp; f1 INT NOT NULL DEFAULT '0',&lt;br /&gt;&amp;nbsp; f2 INT NOT NULL DEFAULT '0',&lt;br /&gt;&amp;nbsp; f3 DATE DEFAULT NULL,&lt;br /&gt;&amp;nbsp; PRIMARY KEY (f1, f2),&lt;br /&gt;&amp;nbsp; KEY k_f3 (f3)&lt;br /&gt;) ENGINE = InnoDB;&lt;br /&gt;&lt;br /&gt;INSERT INTO t1 VALUES (1, 1, '1998-01-01'), (1, 2, '1999-01-01'),&lt;br /&gt;(1, 3, '2000-01-01'), (1, 4, '2001-01-01'), (1, 5, '2002-01-01'),&lt;br /&gt;(2, 1, '1998-01-01'), (2, 2, '1999-01-01'), (2, 3, '2000-01-01'),&lt;br /&gt;(2, 4, '2001-01-01'), (2, 5, '2002-01-01'), (3, 1, '1998-01-01'),&lt;br /&gt;(3, 2, '1999-01-01'), (3, 3, '2000-01-01'), (3, 4, '2001-01-01'),&lt;br /&gt;(3, 5, '2002-01-01'), (4, 1, '1998-01-01'), (4, 2, '1999-01-01'),&lt;br /&gt;(4, 3, '2000-01-01'), (4, 4, '2001-01-01'), (4, 5, '2002-01-01'),&lt;br /&gt;(5, 1, '1998-01-01'), (5, 2, '1999-01-01'), (5, 3, '2000-01-01'),&lt;br /&gt;(5, 4, '2001-01-01'), (5, 5, '2002-01-01');&lt;br /&gt;&lt;br /&gt;Lets take a look at the difference in results with &quot;use_index_extensions=off|on&quot;:&lt;br /&gt;&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;line-height:100%;overflow:auto;padding:10px;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:xx-small;&quot;&gt;&lt;br /&gt;mysql&amp;gt; set optimizer_switch= &quot;use_index_extensions=off&quot;;&lt;br /&gt;Query OK, 0 rows affected (0.00 sec)&lt;br /&gt;mysql&amp;gt; FLUSH STATUS;&lt;br /&gt;Query OK, 0 rows affected (0.00 sec)&lt;br /&gt;mysql&amp;gt; SELECT COUNT(*) FROM t1 WHERE f3 = '2000-01-01' AND f1 BETWEEN 1 AND 3;&lt;br /&gt;+----------+&lt;br /&gt;| COUNT(*) |&lt;br /&gt;+----------+&lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3 |&lt;br /&gt;+----------+&lt;br /&gt;1 row in set (0.01 sec)&lt;br /&gt;mysql&amp;gt; SHOW STATUS LIKE 'handler_read_next';&lt;br /&gt;+-------------------+-------+&lt;br /&gt;| Variable_name&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | Value |&lt;br /&gt;+-------------------+-------+&lt;br /&gt;| Handler_read_next | 5&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;br /&gt;+-------------------+-------+&lt;br /&gt;1 row in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; set optimizer_switch= &quot;use_index_extensions=on&quot;;&lt;br /&gt;Query OK, 0 rows affected (0.00 sec)&lt;br /&gt;mysql&amp;gt; FLUSH STATUS;&lt;br /&gt;Query OK, 0 rows affected (0.00 sec)&lt;br /&gt;mysql&amp;gt; SELECT COUNT(*) FROM t1 WHERE f3 = '2000-01-01' AND f1 BETWEEN 1 AND 3;&lt;br /&gt;+----------+&lt;br /&gt;| COUNT(*) |&lt;br /&gt;+----------+&lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3 |&lt;br /&gt;+----------+&lt;br /&gt;1 row in set (0.00 sec)&lt;br /&gt;mysql&amp;gt; SHOW STATUS LIKE 'handler_read_next';&lt;br /&gt;+-------------------+-------+&lt;br /&gt;| Variable_name&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | Value |&lt;br /&gt;+-------------------+-------+&lt;br /&gt;| Handler_read_next | 3&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;br /&gt;+-------------------+-------+&lt;br /&gt;1 row in set (0.00 sec)&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;Number of handler_read_next is decreased with use_index_extensions=on. It happens because optimizer uses 'f3, f1' pair for range access. This behaviour change is indirectly can be visible is explain:&lt;br /&gt;&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;line-height:100%;overflow:auto;padding:10px;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:xx-small;&quot;&gt;&lt;br /&gt;mysql&amp;gt; set optimizer_switch= &quot;use_index_extensions=off&quot;;&lt;br /&gt;Query OK, 0 rows affected (0.00 sec)&lt;br /&gt;mysql&amp;gt; EXPLAIN SELECT COUNT(*) FROM t1 WHERE f3 = '2000-01-01' AND f1 BETWEEN 1 AND 3;&lt;br /&gt;+----+-------------+-------+------+---------------+------+---------+-------+------+--------------------------+&lt;br /&gt;| id | select_type | table | type | possible_keys | key&amp;nbsp; | key_len | ref&amp;nbsp;&amp;nbsp; | rows | Extra&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;br /&gt;+----+-------------+-------+------+---------------+------+---------+-------+------+--------------------------+&lt;br /&gt;|&amp;nbsp; 1 | SIMPLE&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | t1&amp;nbsp;&amp;nbsp;&amp;nbsp; | ref&amp;nbsp; | PRIMARY,k_f3&amp;nbsp; | k_f3 | 4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | const |&amp;nbsp;&amp;nbsp;&amp;nbsp; 5 | Using where; Using index |&lt;br /&gt;+----+-------------+-------+------+---------------+------+---------+-------+------+--------------------------+&lt;br /&gt;1 row in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; set optimizer_switch= &quot;use_index_extensions=on&quot;;&lt;br /&gt;Query OK, 0 rows affected (0.00 sec)&lt;br /&gt;mysql&amp;gt; EXPLAIN SELECT COUNT(*) FROM t1 WHERE f3 = '2000-01-01' AND f1 BETWEEN 1 AND 3;&lt;br /&gt;+----+-------------+-------+-------+---------------+------+---------+------+------+--------------------------+&lt;br /&gt;| id | select_type | table | type&amp;nbsp; | possible_keys | key&amp;nbsp; | key_len | ref&amp;nbsp; | rows | Extra&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;br /&gt;+----+-------------+-------+-------+---------------+------+---------+------+------+--------------------------+&lt;br /&gt;|&amp;nbsp; 1 | SIMPLE&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | t1&amp;nbsp;&amp;nbsp;&amp;nbsp; | range | PRIMARY,k_f3&amp;nbsp; | k_f3 | 8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | NULL |&amp;nbsp;&amp;nbsp;&amp;nbsp; 3 | Using where; Using index |&lt;br /&gt;+----+-------------+-------+-------+---------------+------+---------+------+------+--------------------------+&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;As you can see key_len field is changed  (4 with &quot;use_index_extensions=off&quot; and 8 with &quot;use_index_extensions=on&quot;)  which means that 'f3, f1' pair is used with enabled feature.&lt;br /&gt;&lt;br /&gt;Another example shows the improvement when using JOIN(the same table with 12800 records):&lt;br /&gt;&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;line-height:100%;overflow:auto;padding:10px;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:xx-small;&quot;&gt;&lt;br /&gt;mysql&amp;gt; SELECT COUNT(*) FROM t1;&lt;br /&gt;+----------+&lt;br /&gt;| COUNT(*) |&lt;br /&gt;+----------+&lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp; 12800 |&lt;br /&gt;+----------+&lt;br /&gt;1 row in set (0.07 sec)&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; set optimizer_switch= &quot;use_index_extensions=off&quot;;&lt;br /&gt;Query OK, 0 rows affected (0.00 sec)&lt;br /&gt;mysql&amp;gt; SELECT count(*) FROM t1 AS t1 JOIN t1 AS t2 ON t2.f1 = t1.f1&lt;br /&gt;WHERE t1.f3 = '2000-01-01' AND t2.f3 = '2002-01-01';&lt;br /&gt;+----------+&lt;br /&gt;| count(*) |&lt;br /&gt;+----------+&lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp; 10240 |&lt;br /&gt;+----------+&lt;br /&gt;1 row in set (0.47 sec)&lt;br /&gt;&lt;br /&gt;set optimizer_switch= &quot;use_index_extensions=on&quot;;&lt;br /&gt;Query OK, 0 rows affected (0.00 sec)&lt;br /&gt;mysql&amp;gt; SELECT count(*) FROM t1 AS t1 JOIN t1 AS t2 ON t2.f1 = t1.f1&lt;br /&gt;WHERE t1.f3 = '2000-01-01' AND t2.f3 = '2002-01-01';&lt;br /&gt;+----------+&lt;br /&gt;| count(*) |&lt;br /&gt;+----------+&lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp; 10240 |&lt;br /&gt;+----------+&lt;br /&gt;1 row in set (0.18 sec)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;As you can see execution of the query is roughly 2.5 better with  enabled &quot;use_index_extensions&quot; switch.&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;</description>
         <author>Sergey Glukhov</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-64018782879169547.post-2792550376571389336</guid>
         <pubDate>Wed, 19 Dec 2012 11:30:00 +0000</pubDate>
      </item>
      <item>
         <title>Favorite MySQL 5.6 features: an optimizer perspective</title>
         <link>http://jorgenloland.blogspot.com/2012/12/favorite-mysql-56-features-optimizer.html</link>
         <description>There are so many exciting new features in MySQL 5.6 that I almost don&amp;#39;t know where to start. To mention a few, MySQL&amp;#39;s &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dimitrik.free.fr/blog/archives/2012/09/mysql-performance-welcome-56-rc-the-best-mysql-ever.html&quot;&gt;multi-core scalability&lt;/a&gt; has been significantly improved to meet modern hardware, InnoDB has better &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://blogs.innodb.com/wp/2011/04/innodb-persistent-statistics-at-last/&quot;&gt;index statistics&lt;/a&gt;, much &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.6/en/innodb-performance.html&quot;&gt;better performance&lt;/a&gt;, and &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;https://blogs.oracle.com/mysqlinnodb/entry/online_alter_table_in_mysql&quot;&gt;online ALTER&lt;/a&gt;, replication has &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://d2-systems.blogspot.co.uk/2011/10/multi-threaded-slave-its-in.html&quot;&gt;multi-threaded slaves&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://d2-systems.blogspot.co.uk/2012/04/global-transaction-identifiers-are-in.html&quot;&gt;global transaction identifiers&lt;/a&gt;, &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://www.markleith.co.uk/2012/07/13/monitoring-processes-with-performance-schema-in-mysql-5-6/&quot;&gt;performance schema&lt;/a&gt; has added capabilities to provide a much more detailed view of what&amp;#39;s bogging a server down, and more... much more.&lt;br&gt;&lt;br&gt;However, my prime interest is the optimizer, which is why I&amp;#39;ve compiled a list of my favorite new optimizer features in 5.6. Here goes:&lt;br&gt;&lt;br&gt;&lt;b&gt;New ways to understand query plans:&lt;/b&gt;&lt;br&gt;The most common requests from DBAs is to get more information to understand how and why MySQL behaves like it does. You can come a very long way at squeezing performance out of MySQL if you can answer questions like &amp;quot;What Query Execution Plan (QEP) did the server decide to use?&amp;quot; and &amp;quot;Why was this QEP picked?&amp;quot;. In this department, MySQL 5.6 delivers a bunch of new features.&lt;br&gt;&lt;br&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://jorgenloland.blogspot.com/2012/12/favorite-mysql-56-features-optimizer.html#more&quot;&gt;Read more »&lt;/a&gt;</description>
         <author>Jørgen Løland</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-7171477457129187873.post-914225671849789259</guid>
         <pubDate>Mon, 17 Dec 2012 13:55:00 +0000</pubDate>
      </item>
      <item>
         <title>Cost-based choice between subquery materialization and EXISTS</title>
         <link>http://guilhembichot.blogspot.com/2012/10/cost-based-choice-between-subquery.html</link>
         <description>In &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://guilhembichot.blogspot.co.uk/2012/04/faster-subqueries-with-materialization.html&quot;&gt;a previous post&lt;/a&gt;, I had demonstrated how  &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.6/en/subquery-materialization.html&quot;&gt;&lt;i&gt;subquery materialization&lt;/i&gt;&lt;/a&gt;, introduced in MySQL 5.6.5, improves the performance of certain queries, like query Q16 of DBT3. Such improvement was easily explained:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Subquery materialization has a high start up cost (it needs to create and fill the temporary table).&lt;/li&gt;&lt;li&gt;But afterwards it has fast lookups (temporary table has a hash index, no duplicates, and is in memory).&lt;/li&gt;&lt;li&gt;In other words, compared to EXISTS, the first evaluation of the IN predicate is slow (high start up cost) and all following evaluations are fast (just a hash lookup).&lt;/li&gt;&lt;li&gt;In the DBT 3 setup, one outer table (named &quot;part&quot;) has 200,000 rows, so there are 200,000 evaluations of IN, thus subquery materialization wins over EXISTS because the time it loses in the first evaluation is more than compensated by the many faster following evaluations.&lt;/li&gt;&lt;/ul&gt;However, if there were only few outer rows, then subquery materialization should logically be &lt;i&gt;slower&lt;/i&gt; than EXISTS (the compensation would not happen anymore)... MySQL 5.6.5, by blindly always choosing subquery materialization, takes the risk of making certain queries slower. There needs to be a cost-based choice between the two strategies, to pick the best, depending on the situation! That is what I have implemented in MySQL 5.6.7.&lt;br /&gt;&lt;br /&gt;To show it in action, I will use query Q16 again. First I will run it with the normal &quot;part&quot; table which has 200,000 rows. Then I will reduce this table to only 200 rows, and run the query again. Each time, I will run EXPLAIN to see what subquery strategy is chosen by the optimizer. I will also, by tweaking the optimizer_switch variable, force the optimizer to use the other strategy which it didn't like, in order to verify that it is indeed worse.&lt;br /&gt;&lt;br /&gt;For brevity, let me jump directly to the results, obtained with a release build of MySQL 5.6.7 on my machine:&lt;br /&gt;&lt;br /&gt;&lt;table border=&quot;1&quot;&gt;  &lt;tbody&gt;&lt;tr&gt;   &lt;td&gt;&lt;b&gt;Rows&lt;/b&gt; &lt;b&gt;in&lt;/b&gt; &lt;b&gt;&lt;i&gt;part&lt;/i&gt;&lt;/b&gt;&lt;/td&gt;   &lt;td&gt;&lt;b&gt;Optimizer&lt;/b&gt; &lt;b&gt;chooses&lt;/b&gt;&lt;/td&gt;   &lt;td&gt;&lt;b&gt;Execution&lt;/b&gt; &lt;b&gt;time&lt;/b&gt;&lt;/td&gt;   &lt;td&gt;&lt;b&gt;If&lt;/b&gt; &lt;b&gt;I&lt;/b&gt; &lt;b&gt;force&lt;/b&gt; &lt;b&gt;alternative&lt;/b&gt;&lt;/td&gt;  &lt;/tr&gt;&lt;tr&gt;   &lt;td&gt;200,000&lt;/td&gt;   &lt;td&gt;Materialization&lt;/td&gt;   &lt;td&gt;550 ms&lt;/td&gt;   &lt;td&gt;830 ms&lt;/td&gt;  &lt;/tr&gt;&lt;tr&gt;   &lt;td&gt;200&lt;/td&gt;   &lt;td&gt;EXISTS&lt;/td&gt;   &lt;td&gt;1 ms&lt;/td&gt;   &lt;td&gt;10 ms &lt;/td&gt;  &lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;We can see that in both cases the optimizer has made the right choice!</description>
         <author>Guilhem Bichot</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-9191231274999121063.post-6219388629066120986</guid>
         <pubDate>Fri, 12 Oct 2012 03:02:00 +0000</pubDate>
      </item>
      <item>
         <title>Index merge annoyances fixed in MySQL 5.6</title>
         <link>http://jorgenloland.blogspot.com/2012/10/index-merge-annoyances-fixed-in-mysql-56.html</link>
         <description>While the &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.6/en/index-merge-optimization.html&quot;&gt;index merge access types&lt;/a&gt; certainly are useful for a number of queries, there has been some frustration expressed both from customers and the community about how it...&lt;br&gt;&lt;ol&gt;&lt;li&gt;is not used when it should have been&lt;/li&gt;&lt;li&gt;is used when ref access is obviously better&lt;/li&gt;&lt;li&gt;merges suboptimal indexes&lt;/li&gt;&lt;li&gt;is too restricted in which conditions can be used&lt;/li&gt;&lt;/ol&gt;I could come up with numerous examples of related bugs and feature requests dating back more than six years. To list a few: &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://bugs.mysql.com/bug.php?id=17673&quot;&gt;17673&lt;/a&gt;, &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://bugs.mysql.com/bug.php?id=30151&quot;&gt;30151&lt;/a&gt;, &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://bugs.mysql.com/bug.php?id=23322&quot;&gt;23322&lt;/a&gt;, &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://bugs.mysql.com/bug.php?id=65274&quot;&gt;65274&lt;/a&gt;, &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://bugs.mysql.com/bug.php?id=65359&quot;&gt;65359&lt;/a&gt;.&lt;br&gt;&lt;br&gt;The good news is that we have fixed all these issues in the latest released MySQL 5.6 server.&lt;br&gt;&lt;br&gt;Consider the problem in &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://bugs.mysql.com/bug.php?id=65274&quot;&gt;BUG#65274&lt;/a&gt; (simplified a bit):&lt;span style=&quot;font-size:x-small;&quot;&gt;&lt;span style=&quot;&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;&lt;span style=&quot;&quot;&gt;CREATE TABLE phpbb_posts (&lt;br&gt;  ...&lt;br&gt;  KEY `topic_id` (`topic_id`),&lt;br&gt;  KEY `forum_id` (`forum_id`)&lt;br&gt;  KEY `tid_fid` (`topic_id`,`forum_id`),&lt;br&gt;);&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;&quot;&gt;SELECT count(*) FROM phpbb_posts WHERE topic_id = 110126 AND forum_id = 19;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://jorgenloland.blogspot.com/2012/10/index-merge-annoyances-fixed-in-mysql-56.html#more&quot;&gt;Read more »&lt;/a&gt;</description>
         <author>Jørgen Løland</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-7171477457129187873.post-8905058823337656817</guid>
         <pubDate>Tue, 02 Oct 2012 10:32:00 +0000</pubDate>
      </item>
      <item>
         <title>Speaking at MySQL Connect This Week-end</title>
         <link>http://oysteing.blogspot.com/2012/09/speaking-at-mysql-connect-this-week-end.html</link>
         <description>I will give a talk at MySQL Connect where I present examples of how MySQL 5.6 improves the performance of many of the queries in the DBT-3 benchmark. I will also be giving a brief description of the relevant new optimization techniques and examples of the types of queries that will benefit from these techniques.  My presentation is on Sunday (September 30) at 1.15pm.&lt;br /&gt;&lt;br /&gt;I will also like to point you to the other presentations made by member of the MySQL Optimizer team:&lt;br /&gt;&lt;br /&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://olavsandstaa.blogspot.com/&quot;&gt;Olav Sandstå&lt;/a&gt;: &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://www.oracle.com/goto/newsletters/qtr/mysql/0912/oracleus_activeevents_9169.html?msgid=3-7037630764&quot;&gt;MySQL Optimizer Overview&lt;/a&gt; (Saturday at 11:30am)&lt;br /&gt;Manyi Lu: &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;https://oracleus.activeevents.com/connect/sessionDetail.ww?SESSION_ID=8979&quot;&gt;Overview of New Optimizer Features in MySQL 5.6&lt;/a&gt; (Saturday at 1:00pm)&lt;br /&gt;Evgeny Potemkin:&lt;span style=&quot;font-weight:normal;&quot;&gt; &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;https://oracleus.activeevents.com/connect/sessionDetail.ww?SESSION_ID=8562&quot;&gt;Powerful EXPLAIN in MySQL 5.6&lt;/a&gt;&lt;/span&gt; (Sunday at 4:15pm)&lt;br /&gt;&lt;br /&gt;We will also be having a BOF on Saturday evening (7:00 am) where we like people to come and give us some input on which query optimizations they would like us to work on for future releases.&lt;br /&gt;&lt;br /&gt;</description>
         <author>Øystein</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-1508669603650457962.post-2914105847853454298</guid>
         <pubDate>Thu, 27 Sep 2012 13:02:00 +0000</pubDate>
      </item>
      <item>
         <title>From Months to Seconds with Subquery Materialization</title>
         <link>http://oysteing.blogspot.com/2012/07/from-months-to-seconds-with-subquery.html</link>
         <description>&lt;style type=&quot;text/css&quot;&gt;&lt;!--div.codebox {height:100%;width:100%;border:1px solid;background-color:#EEEEEE;padding:4px;}--&gt;&lt;/style&gt;&lt;br /&gt;In an earlier &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://oysteing.blogspot.com/2012/04/improved-dbt-3-results-with-mysql-565.html&quot;&gt;blog post&lt;/a&gt;, I showed how optimizer improvements in MySQL 5.6 gave better performance for several of the queries in the DBT-3 benchmark. &lt;br /&gt;However, for one of the queries, Query 18, I was not able to give exact numbers for the improvement since the query took very long in MySQL 5.5.  I decided to try to find out exactly how long the query would take, but when the query had run for one month, I gave up.  How can a query take so long?  Especially, when I had set up InnoDB with a buffer pool that should be large enough to hold the entire database. Let's have a look at the query: &lt;br /&gt;&lt;br /&gt;&lt;div class=&quot;codebox&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;select c_name, c_custkey, o_orderkey, o_orderdate, o_totalprice, sum(l_quantity)&lt;br /&gt;from customer, orders, lineitem&lt;br /&gt;where o_orderkey in (&lt;br /&gt;                select l_orderkey&lt;br /&gt;                from lineitem&lt;br /&gt;                group by l_orderkey&lt;br /&gt;                having sum(l_quantity) &amp;gt; 313&lt;br /&gt;  )&lt;br /&gt;  and c_custkey = o_custkey&lt;br /&gt;  and o_orderkey = l_orderkey&lt;br /&gt;group by c_name, c_custkey, o_orderkey, o_orderdate, o_totalprice&lt;br /&gt;order by o_totalprice desc, o_orderdate&lt;br /&gt;LIMIT 100;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt; This query will find the orders from customers that have placed big orders.  The reason that this takes so long in MySQL 5.5, is that the subquery in the WHERE clause will be executed for each processed row of the table for which this subquery is part of the WHERE predicate. Let's look at the EXPLAIN output for this query: &lt;div class=&quot;codebox&quot; style=&quot;overflow:auto;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;+----+--------------------+----------+-------+--------------------------------------------+-----------------------+---------+-------------------------+---------+---------------------------------+&lt;br /&gt;| id | select_type        | table    | type  | possible_keys                              | key                   | key_len | ref                     | rows    | Extra                           |&lt;br /&gt;+----+--------------------+----------+-------+--------------------------------------------+-----------------------+---------+-------------------------+---------+---------------------------------+&lt;br /&gt;|  1 | PRIMARY            | customer | ALL   | PRIMARY                                    | NULL                  | NULL    | NULL                    |  150000 | Using temporary; Using filesort | &lt;br /&gt;|  1 | PRIMARY            | orders   | ref   | PRIMARY,i_o_custkey                        | i_o_custkey           | 5       | dbt3.customer.c_custkey |       7 | Using where                     | &lt;br /&gt;|  1 | PRIMARY            | lineitem | ref   | PRIMARY,i_l_orderkey,i_l_orderkey_quantity | i_l_orderkey_quantity | 4       | dbt3.orders.o_orderkey  |       2 | Using index                     | &lt;br /&gt;|  2 | DEPENDENT SUBQUERY | lineitem | index | NULL                                       | PRIMARY               | 8       | NULL                    | 6001215 | NULL                            | &lt;br /&gt;+----+--------------------+----------+-------+--------------------------------------------+-----------------------+---------+-------------------------+---------+---------------------------------+&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;The &lt;tt&gt;select_type&lt;/tt&gt; for the subquery is &lt;tt&gt;DEPENDENT SUBQUERY&lt;/tt&gt;.  Since the left hand side of the IN-expression is a field from the &lt;tt&gt;orders&lt;/tt&gt; table, the subquery will be executed for each row processed from this table. This implies that the index scan of the &lt;tt&gt;lineitem&lt;/tt&gt; table will be performed more than one million times given the rows estimates in the EXPLAIN output (&lt;tt&gt;150000*7&lt;/tt&gt;).  I have measured that one execution of the sub-query takes about 3.5 seconds when all the data is in memory.  Hence, it will take more than 40 days to execute it one million times. &lt;p&gt;In MySQL 5.6, Subquery Materialization may be used to avoid the repeated execution of subqueries.  This implies that the subquery is executed once and the result stored (materialized) in a temporary table.  Then, for each row where the subquery was earlier executed, a hash-based look-up into the temporary table will be made instead to check whether there is a match.  The EXPLAIN output for Query 18, looks like this in MySQL 5.6: &lt;/p&gt; &lt;div class=&quot;codebox&quot; style=&quot;overflow:auto;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;+----+-------------+----------+-------+--------------------------------------------+-----------------------+---------+-------------------------+---------+---------------------------------+&lt;br /&gt;| id | select_type | table    | type  | possible_keys                              | key                   | key_len | ref                     | rows    | Extra                           |&lt;br /&gt;+----+-------------+----------+-------+--------------------------------------------+-----------------------+---------+-------------------------+---------+---------------------------------+&lt;br /&gt;|  1 | PRIMARY     | customer | ALL   | PRIMARY                                    | NULL                  | NULL    | NULL                    |  150000 | Using temporary; Using filesort | &lt;br /&gt;|  1 | PRIMARY     | orders   | ref   | PRIMARY,i_o_custkey                        | i_o_custkey           | 5       | dbt3.customer.c_custkey |       7 | Using where                     | &lt;br /&gt;|  1 | PRIMARY     | lineitem | ref   | PRIMARY,i_l_orderkey,i_l_orderkey_quantity | i_l_orderkey_quantity | 4       | dbt3.orders.o_orderkey  |       2 | Using index                     | &lt;br /&gt;|  2 | SUBQUERY    | lineitem | index | NULL                                       | PRIMARY               | 8       | NULL                    | 6001215 | NULL                            | &lt;br /&gt;+----+-------------+----------+-------+--------------------------------------------+-----------------------+---------+-------------------------+---------+---------------------------------+&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;The only difference is that &lt;tt&gt;select_type&lt;/tt&gt; of the subquery now is &lt;tt&gt;SUBQUERY&lt;/tt&gt;.  In other words, it is no longer dependent on the preceding tables, and can be executed only once.  In my run of DBT-3 with MySQL 5.6, Query 18 takes only 6.8 seconds.  Hence, we have gone from more than a month to just a few seconds! My colleague Guilhem has earlier written about &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://guilhembichot.blogspot.com/2012/04/faster-subqueries-with-materialization.html&quot;&gt;another DBT-3 query that is improved with Subquery Materialization&lt;/a&gt;. &lt;br /&gt;</description>
         <author>Øystein</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-1508669603650457962.post-5687086978185523569</guid>
         <pubDate>Sat, 07 Jul 2012 14:11:00 +0000</pubDate>
      </item>
      <item>
         <title>Performance improvements for big INFORMATION_SCHEMA tables</title>
         <link>http://jorgenloland.blogspot.com/2012/05/performance-improvements-for-big.html</link>
         <description>A short while after &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://jorgenloland.blogspot.co.uk/2012/04/copying-unused-bytes-is-bad-duh.html&quot;&gt;I fixed the legacy bug&lt;/a&gt; that prevented temporary  MyISAM tables from using the dynamic record format, I got an email from Davi  Arnaut @ Twitter. It turned out that Twitter needed to fix the very same  problem, but for the case when INFORMATION_SCHEMA temporary tables use  MyISAM. &lt;br&gt;&lt;br&gt;In short, INFORMATION_SCHEMA tables provide access to database metadata.  Despite their name, they are more like views than tables: when you query  them, relevant data is gathered from the dictionary and other server  internals, not from tables. The gathered data is stored in a temporary table (memory or MyISAM depending on size) and then returned to the user. &lt;br&gt;&lt;br&gt;The reason Davi emailed me was to let me know that he had further  improved the fix for temporary MyISAM tables to also enable the use of  dynamic record format for INFORMATION_SCHEMA tables. I usually don&amp;#39;t  have huge databases on my development box so the problem of querying  metadata had gone unnoticed. But it turns out that Davi and his  colleagues at Twitter &lt;i&gt;do&lt;/i&gt; deal with massive amounts of data :-) &lt;br&gt;&lt;br&gt;This was one of the queries that caused problems: &lt;br&gt;&lt;br&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;SELECT pool_id FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE limit 1; &lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://jorgenloland.blogspot.com/2012/05/performance-improvements-for-big.html#more&quot;&gt;Read more »&lt;/a&gt;</description>
         <author>Jørgen Løland</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-7171477457129187873.post-3138776743673222894</guid>
         <pubDate>Fri, 04 May 2012 08:27:00 +0000</pubDate>
      </item>
      <item>
         <title>Copying unused bytes is bad (duh!)</title>
         <link>http://jorgenloland.blogspot.com/2012/04/copying-unused-bytes-is-bad-duh.html</link>
         <description>Last summer my colleague Marko Mäkelä committed this seemingly innocent performance fix for InnoDB in MySQL 5.6:&lt;br&gt;&lt;br&gt;&lt;blockquote class=&quot;tr_bq&quot;&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;3581 Marko Makela    2011-08-10&lt;br&gt;Bug#12835650 VARCHAR maximum length performance impact&lt;br&gt;&lt;br&gt;row_sel_field_store_in_mysql_format(): Do not pad the unused part of&lt;br&gt;the buffer reserved for a True VARCHAR column (introduced in 5.0.3).&lt;br&gt;Add Valgrind instrumentation ensuring that the unused part will be&lt;br&gt;flagged uninitialized.&lt;/span&gt;&lt;/blockquote&gt;&lt;br&gt;Before this, buffers which were used to send VARCHARs from InnoDB to the MySQL server were padded with 0s if the string was shorter than specified by the column. If, e.g., the string &amp;quot;foo&amp;quot; was stored in a VARCHAR(8), InnoDB used to write &amp;quot;3foo00000&amp;quot; to the buffer (the first character - 3 - determines the actual length of the string). However, even though these trailing bytes are not used anywhere, writing 0s to the buffer certainly has a cost. Hence the fix which stops InnoDB from writing them.&lt;br&gt;&lt;br&gt;Oh my were we up for a ride! All of a sudden Valgrind started barking like this (and similar) for a number of tests:&lt;br&gt;&lt;blockquote class=&quot;tr_bq&quot; style=&quot;&quot;&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;Thread 19:&lt;br&gt;Syscall param pwrite64(buf) points to uninitialised byte(s)&lt;br&gt;at 0x381C60EEE3: ??? (in /lib64/libpthread-2.12.so)&lt;br&gt;by 0x84B703: my_pwrite (my_pread.c:162) by 0x86FE6F: mi_nommap_pwrite (mysql_file.h:1203)&lt;br&gt;   by 0x88621B: _mi_write_static_record (mi_statrec.c:65)&lt;br&gt;   by 0x889592: mi_write (mi_write.c:142)&lt;br&gt;   by 0x532085: handler::ha_write_row(unsigned char*) (handler.cc:6043)&lt;br&gt;   by 0x69B7F7: end_write(JOIN*, st_join_table*, bool) (sql_select.cc:20237)&lt;br&gt;   by 0x69A43C: evaluate_join_record(JOIN*, st_join_table*, int)&lt;br&gt;(sql_select.cc:19187)&lt;br&gt;   by 0x69A8E0: sub_select(JOIN*, st_join_table*, bool) (sql_select.cc:19294)&lt;/span&gt;&lt;/blockquote&gt;The modifications we did to the server to remedy the problems can be divided into two categories as described below. In the end, the result was significantly improved performance.&lt;br&gt;&lt;br&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://jorgenloland.blogspot.com/2012/04/copying-unused-bytes-is-bad-duh.html#more&quot;&gt;Read more »&lt;/a&gt;</description>
         <author>Jørgen Løland</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-7171477457129187873.post-4450481856444397522</guid>
         <pubDate>Wed, 18 Apr 2012 10:59:00 +0000</pubDate>
      </item>
      <item>
         <title>New MySQL optimizer team page launched</title>
         <link>http://jorgenloland.blogspot.com/2012/04/new-mysql-optimizer-team-page-launched.html</link>
         <description>FYI: A MySQL &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://mysqloptimizerteam.blogspot.com/&quot;&gt;Optimizer Team blog&lt;/a&gt; aggregate page has been launched. It will be continuously updated with the latest blogs published by the MySQL optimizer developers. Now you can easily stay updated ;-)</description>
         <author>Jørgen Løland</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-7171477457129187873.post-7717189260866708909</guid>
         <pubDate>Fri, 13 Apr 2012 15:07:00 +0000</pubDate>
      </item>
      <item>
         <title>Improved DBT-3 Results with MySQL 5.6.5</title>
         <link>http://oysteing.blogspot.com/2012/04/improved-dbt-3-results-with-mysql-565.html</link>
         <description>&lt;p&gt;In addition to the Optimizer features added in earlier 5.6 Development Milestone Releases, the newly released &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;https://blogs.oracle.com/MySQL/entry/new_replication_optimizer_and_high&quot;&gt;MySQL 5.6.5 Development Milestone Release&lt;/a&gt; adds a few more.  I think this is a good time to check how all these new optimizer features impact the performance of the  &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://osdldbt.sourceforge.net/#dbt3&quot;&gt;DBT-3 benchmark&lt;/a&gt; queries.  In this blog post, I will compare the performance of the DBT-3 queries in MySQL 5.5 and MySQL 5.6.5. &lt;/p&gt;&lt;h1&gt;Test Setup&lt;/h1&gt;&lt;p&gt;I used a DBT-3 scale 1 database (InnoDB tables) that was stored on a traditional hard disk, and the InnoDB database size was a bit more than 2.5 GB. The DBT-3 queries were run in two settings: a &lt;i&gt;disk-bound&lt;/i&gt; setting with a very small InnoDB buffer pool (50 MB), and &lt;i&gt;CPU-bound&lt;/i&gt; with a 3 GB InnoDB buffer pool (all data in memory). The query cache was disabled, and by setting &lt;tt&gt;--innodb_flush_method=O_DIRECT&lt;/tt&gt;, the file buffering in the file system was also out of the way.  If not stated otherwise, default settings were used for all other MySQL system variables. &lt;/p&gt;&lt;p&gt;For the disk-bound scenario, each query were executed 10 times in a row, and the results presented are the average of the last 8 runs.  Since the buffer pool was very small, and there was no buffering in the file system, there were very little difference between the first and the tenth execution of the queries.  The only exceptions were Query 16 and Query 22 where the working data sets were so small that subsequent executions were not disk-bound.  Due to this, I have not included the result for these queries in the presentation of the disk-bound scenario. &lt;/p&gt;&lt;p&gt;For the CPU-bound scenario, each query were executed 20 times in a row, but the results presented are still the average of the last 8 runs.  (The reason for this, is that I observed that for several of the queries, the 8th or so execution of the query took significantly longer than the other executions.) &lt;/p&gt;&lt;p&gt;The order in which the different queries were run were randomized, but the same order was used in all experiments. &lt;/p&gt;&lt;p&gt;In order to get stable execution plans, I enabled InnoDB Persistent Statistics and recorded exact statistics in the statistics tables as discussed &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://oysteing.blogspot.com/2011/05/innodb-persistent-statistics-save-day.html&quot;&gt;here&lt;/a&gt;. Since Persistent Statistics is not available in 5.5, I used optimizer hints to force a similar join order and index usage as for 5.6 where necessary.  &lt;/p&gt;&lt;p&gt;For MySQL 5.6.5, the DBT-3 queries were run both with default settings for the &lt;tt&gt;optimizer_switch&lt;/tt&gt; variable, and with setting &lt;tt&gt;optimizer_switch='mrr_cost_based=off,batched_key_access=on'&lt;/tt&gt; in order to activate the Disk-Sweep Multi-Range Read (DS-MRR)and Batched Key Access (BKA) features. &lt;/p&gt;&lt;p&gt;OK, enough talking, let's move on to the results. &lt;/p&gt;&lt;h1&gt;Disk-Bound Workload&lt;/h1&gt;&lt;p&gt;The below chart shows the execution times for the DBT-3 queries in a disk-bound setting.  The executions times in MySQL 5.5, MySQL 5.6.5 with default &lt;tt&gt;optimizer_switch&lt;/tt&gt; settings, and MySQL 5.6.5 with DS-MRR and BKA activated are compared.  The execution times for MySQL 5.6.5 default is set to 1, and the relative execution times are shown for the other two variants. &lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear:both;text-align:center;&quot;&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://1.bp.blogspot.com/-OhWCRHnfE4o/T4R4EkOcq-I/AAAAAAAAADQ/YN1TLDfiDkY/s1600/5.5vc5.6.5off-disk.jpg&quot; style=&quot;margin-left:1em;margin-right:1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://1.bp.blogspot.com/-OhWCRHnfE4o/T4R4EkOcq-I/AAAAAAAAADQ/YN1TLDfiDkY/s480/5.5vc5.6.5off-disk.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;Except for Query 18, there are no significant differences between MySQL 5.5 and MySQL 5.6.5 with default settings.  However, for Query 18 the improvement is dramatic.  While it takes &lt;i&gt;days&lt;/i&gt; to execute this query in MySQL 5.5, it takes less 45 minutes in MySQL 5.6.5.  This improvement is due to &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.6/en/subquery-materialization.html&quot;&gt;Subquery Materialization&lt;/a&gt;, and I will discuss this further in a later blog post.  &lt;/p&gt;&lt;p&gt;As discussed in an &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://oysteing.blogspot.com/2011/10/bacthed-key-access-speeds-up-disk-bound.html&quot;&gt;earlier blog post&lt;/a&gt;, BKA can give very big improvements when the workload is disk-bound.  This is illustrated by the improvements of queries 2, 5, 8, 9, 13, 18, 19, and 21.  Note also that there are some queries that does not benefit from BKA.  As discussed in the aforementioned blog, Query 17 gets better locality for the disk accesses without BKA, and turning on BKA makes it take 16 times longer to execute.  We also see that Query 11 performs worse with BKA.  I plan to investigate further the reason for this. &lt;/p&gt;&lt;p&gt;Queries 3, 4, 10, 14, and 15, are improved by using the &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.6/en/mrr-optimization.html&quot;&gt;DS-MRR algorithm&lt;/a&gt; when doing range access.  That is, the rows in the base table are not accessed in the order given by the index used for range access, but in sequential order as viewed from the clustered primary key index. &lt;/p&gt;&lt;h1&gt;CPU-Bound Workload&lt;/h1&gt;&lt;p&gt;The below chart shows the execution times for the DBT-3 queries in a CPU-bound setting.  As above the executions times in MySQL 5.5, MySQL 5.6.5 with default &lt;tt&gt;optimizer_switch&lt;/tt&gt; settings, and MySQL 5.6.5 with DS-MRR and BKA activated are compared. &lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear:both;text-align:center;&quot;&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://1.bp.blogspot.com/-_cGaeZzskdA/T4R4arfc5bI/AAAAAAAAADc/V_mrfIVnshY/s1600/5.5vc5.6.5off-cpu.jpg&quot; style=&quot;margin-left:1em;margin-right:1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://1.bp.blogspot.com/-_cGaeZzskdA/T4R4arfc5bI/AAAAAAAAADc/V_mrfIVnshY/s480/5.5vc5.6.5off-cpu.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt; &lt;p&gt;For MySQL 5.6.5 with default settings, the most significant improvement is for Query 18 also with a CPU-bound workload.  In addition, there is a 10% improvement for Query 16.  This is also due to Subquery Materialization, and my colleague Guilhem &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://guilhembichot.blogspot.com/2012/04/faster-subqueries-with-materialization.html&quot;&gt;discusses this improvement in more detail&lt;/a&gt;. &lt;/p&gt;&lt;p&gt;As discussed in &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://oysteing.blogspot.com/2011/10/bacthed-key-access-speeds-up-disk-bound.html&quot;&gt;my previous blog post on BKA&lt;/a&gt;, several queries perform worse with BKA in a CPU-bound setting (e.g., queries 2, 11, 13, and 17).  Only query 18 performs better with BKA in this setting. &lt;/p&gt;&lt;p&gt;On the other hand, many of the queries that benefited from DS-MRR with a disk-bound workload, still show some improvement with a CPU-bound workload. &lt;/p&gt; &lt;h1&gt;Conclusions&lt;/h1&gt;&lt;p&gt;The results from comparing the new MySQL 5.6.5 release with MySQL 5.5, show that Subquery Materialization have significant effects on the execution time of the few DBT-3 queries where it applies.&lt;/p&gt;&lt;p&gt;Also, as shown earlier, BKA has a good effect for disk-bound workloads, while it in many cases will cause worse performance for CPU-bound workloads. &lt;/p&gt;&lt;p&gt;Disk-sweep MRR has a good effect on the performace of range scans in for disk-bound workloads, and it also shows a small improvement for many queries with CPU-bound workloads. &lt;/p&gt;</description>
         <author>Øystein</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-1508669603650457962.post-7526127235641551966</guid>
         <pubDate>Wed, 11 Apr 2012 01:01:00 +0000</pubDate>
         <media:thumbnail height="72" url="http://1.bp.blogspot.com/-OhWCRHnfE4o/T4R4EkOcq-I/AAAAAAAAADQ/YN1TLDfiDkY/s72-c/5.5vc5.6.5off-disk.jpg" width="72" xmlns:media="http://search.yahoo.com/mrss/"/>
      </item>
      <item>
         <title>Improvements for many-table joins in MySQL 5.6</title>
         <link>http://jorgenloland.blogspot.com/2012/04/improvements-for-many-table-joins-in.html</link>
         <description>A lot has happened in MySQL 5.6 for queries joining many tables. For the most common use cases we have drastically reduced the cost of finding the execution plan. We have also improved the heuristics and removed bugs so that the final plan is often better than it used to be. Read on if you are one of those people who do 15 way joins!&lt;br&gt;&lt;br&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;b&gt;Finding a query execution plan&lt;/b&gt;&lt;/span&gt;&lt;br&gt;First some background. You can skip this part if you know how MySQL picks the table join order in 5.5.&lt;br&gt;&lt;br&gt;When presented with a query, MySQL will try to find the best order to join tables by employing a greedy search algorithm. The outcome is what we call a query execution plan, QEP. When you join just a few tables, there&amp;#39;s no problem calculating the cost of all join order combinations and then pick the best plan. However, since there are (#tables)! possible combinations, the cost of calculating them all soon becomes too high: for five tables, e.g., there are 120 combinations which is no problem to compute. For 10 tables there are 3.6 million combinations and for 15 tables there are 1307 billion. You get the picture. For this reason, MySQL makes a trade off: use heuristics to only explore promising plans. This is supposed to significantly reduce the number of plans MySQL needs to calculate, but at the same time you risk not finding the best one.&lt;br&gt;&lt;br&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://jorgenloland.blogspot.com/2012/04/improvements-for-many-table-joins-in.html#more&quot;&gt;Read more »&lt;/a&gt;</description>
         <author>Jørgen Løland</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-7171477457129187873.post-4227940940132711122</guid>
         <pubDate>Tue, 10 Apr 2012 22:00:00 +0000</pubDate>
         <media:thumbnail height="72" url="http://3.bp.blogspot.com/-mPI7y9MwbXs/T03lghrlBGI/AAAAAAAAACE/1aaQor1w3sM/s72-c/response_time.jpg" width="72" xmlns:media="http://search.yahoo.com/mrss/"/>
      </item>
      <item>
         <title>On queries with many values in the IN clause</title>
         <link>http://jorgenloland.blogspot.com/2012/04/on-queries-with-many-values-in-in.html</link>
         <description>A few customers with rather extreme needs have contacted us about a performance issue with the range optimizer. Our solution to the problem is to introduce a new variable in MySQL 5.6, &lt;span style=&quot;font-size:x-small;&quot;&gt;eq_range_index_dive_limit&lt;span style=&quot;font-size:small;&quot;&gt;, which can be used to control whether or not the range optimizer will a) do index dives, or b) use index statistics when estimating the number of rows in the ranges of the query. The former method gives a far more accurate estimate while the latter costs a lot less to compute.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;&lt;span style=&quot;font-size:small;&quot;&gt;This is what the help text has to tell about the variable:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;blockquote class=&quot;tr_bq&quot;&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;The optimizer will use existing index statistics instead of doing index dives for equality ranges if the number of equality ranges for the index is larger than or equal to [the value of variable]. If set to 0, index dives are always used.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;&amp;quot;Equality range&amp;quot; means predicates using operators &lt;span style=&quot;font-size:x-small;&quot;&gt;IN()&lt;span style=&quot;font-size:small;&quot;&gt; or &lt;span style=&quot;font-size:x-small;&quot;&gt;=&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;, and it&amp;#39;s important to notice that the number of such ranges is counted on a per index basis. Furthermore, index dives are not used on unique/primary indexes, so only non-unique indexes are affected.&lt;b&gt; &lt;/b&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;The two ways row estimates can be computed&lt;/b&gt; &lt;br&gt;&lt;br&gt;For as long as there have been a range access method in MySQL, the number of rows in a range has been estimated by diving down the index to find the start and end of the range and use these to count the number of rows between them. This technique is accurate, and is therefore a good basis to make the best possible execution plan (QEP) for the query. Unfortunately, it involves two index dives for each range. That&amp;#39;s not a big deal if you run normal queries with a few equality ranges like&lt;br&gt;&lt;br&gt;&lt;blockquote class=&quot;tr_bq&quot; style=&quot;&quot;&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;SELECT title FROM albums WHERE artist IN (10, 12)&lt;/span&gt;&lt;/blockquote&gt;But some MySQL users have queries with hundreds of values in the IN clause. For such queries it may take considerably longer to optimize the query than to execute it. This is when it makes sense to use the less accurate index statistics. Consider this example:&lt;br&gt;&lt;br&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://jorgenloland.blogspot.com/2012/04/on-queries-with-many-values-in-in.html#more&quot;&gt;Read more »&lt;/a&gt;</description>
         <author>Jørgen Løland</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-7171477457129187873.post-8410393202770232059</guid>
         <pubDate>Tue, 10 Apr 2012 15:00:00 +0000</pubDate>
      </item>
      <item>
         <title>Faster subqueries with materialization</title>
         <link>http://guilhembichot.blogspot.com/2012/04/faster-subqueries-with-materialization.html</link>
         <description>In &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://guilhembichot.blogspot.com/2011/11/understanding-uniquesubquery.html&quot;&gt;a previous post&lt;/a&gt;, I analyzed how a query of the famous DBT3 benchmark was&lt;br /&gt;optimized by MySQL. It was this query, named &quot;Q16&quot; in the DBT3 jargon:&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;select&lt;br /&gt; p_brand,&lt;br /&gt; p_type,&lt;br /&gt; p_size,&lt;br /&gt; count(distinct ps_suppkey) as supplier_cnt&lt;br /&gt;from&lt;br /&gt; partsupp,&lt;br /&gt; part&lt;br /&gt;where&lt;br /&gt; p_partkey = ps_partkey&lt;br /&gt; and p_brand &amp;lt;&amp;gt; 'Brand#23'&lt;br /&gt; and p_type not like 'LARGE PLATED%'&lt;br /&gt; and p_size in (43, 1, 25, 5, 35, 12, 42, 40)&lt;br /&gt; and &lt;span style=&quot;color:red;&quot;&gt;ps_suppkey not in (&lt;br /&gt;  select&lt;br /&gt;   s_suppkey&lt;br /&gt;  from&lt;br /&gt;   supplier&lt;br /&gt;  where&lt;br /&gt;   s_comment like '%Customer%Complaints%'&lt;br /&gt; )&lt;/span&gt;&lt;br /&gt;group by&lt;br /&gt; p_brand,&lt;br /&gt; p_type,&lt;br /&gt; p_size&lt;br /&gt;order by&lt;br /&gt; supplier_cnt desc,&lt;br /&gt; p_brand,&lt;br /&gt; p_type,&lt;br /&gt; p_size;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;Here is a brief recap of conclusions I had drawn:&lt;br /&gt;&lt;ul&gt;&lt;li style=&quot;text-align:justify;&quot;&gt;for this query, MySQL tranforms the IN condition to EXISTS and then&lt;br /&gt;evaluates it with the &quot;unique_subquery&quot; technique, which does an index&lt;br /&gt;lookup into the subquery's table.&lt;/li&gt;&lt;li style=&quot;text-align:justify;&quot;&gt;IN is evaluated 120,000 times (once per combined row of the outer tables).&lt;/li&gt;&lt;li style=&quot;text-align:justify;&quot;&gt;The total execution time of query Q16 is 0.65 seconds.&lt;/li&gt;&lt;/ul&gt;If you look at the original subquery, before IN becomes EXISTS, you will see that it's &lt;i&gt;not correlated&lt;/i&gt;, which means that it does not mention columns of tables of the top query's FROM clause (&lt;i&gt;'partsupp'&lt;/i&gt; and '&lt;i&gt;part'&lt;/i&gt;). Thus, its resultset is constant throughout execution of the entire top query; here it is:&lt;br /&gt;&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;mysql&amp;gt; select&lt;br /&gt;    -&amp;gt; s_suppkey&lt;br /&gt;    -&amp;gt; from&lt;br /&gt;    -&amp;gt; supplier&lt;br /&gt;    -&amp;gt; where&lt;br /&gt;    -&amp;gt; s_comment like '%Customer%Complaints%';&lt;br /&gt;+-----------+&lt;br /&gt;| s_suppkey |&lt;br /&gt;+-----------+&lt;br /&gt;|       358 |&lt;br /&gt;|      2820 |&lt;br /&gt;|      3804 |&lt;br /&gt;|      9504 |&lt;br /&gt;+-----------+&lt;br /&gt;4 rows in set (0.00 sec)&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;The transformation to EXISTS, because it injects equalities like&lt;br /&gt;&lt;i&gt;&amp;nbsp; `partsupp`.`ps_suppkey` = supplier`.`s_suppkey`&lt;/i&gt;&lt;br /&gt;into the subquery's WHERE clause, makes the subquery correlated: it thus has to be executed 120,000 times, so we do 120,000 times this:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;an index lookup in &lt;i&gt;'supplier'&lt;/i&gt; (which has 10,000 rows)&lt;/li&gt;&lt;li&gt;a test of the found row(s) against the LIKE condition.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Intuitively, determining the 4-row resultset once for all, and injecting it into the top query should yield better performance - it is fast to evaluate&lt;br /&gt;&lt;i&gt;&amp;nbsp; ps_suppkey not in (358, 2820, 3804, 9504) .&lt;/i&gt;&lt;br /&gt;Starting from the just released MySQL 5.6.5, this transformation is automatically done by the Optimizer, and is called &lt;b&gt;subquery materialization&lt;/b&gt;. The subquery's resultset is determined once for all and stored into an in-memory temporary table. If the temporary table has only 4 rows as in our example, searching for a match in it can be done with a scan; but if it had more rows, a hash index would help greatly. So a hash index is always created on the temporary table's columns. Last, this index is unique: there is no point in storing duplicates, and they would make the table bigger. After this one-time setup has been completed, each evaluation of IN simply does a hash index lookup into the 4-row temporary table.&lt;br /&gt;&lt;br /&gt;My former colleague Timour Katchaounov started developing this feature years ago, when working for MySQL/Sun. In the last months, after a round of intensive QA, we have fixed some last bugs in it, in preparation for releasing in 5.6.5. But the feature still had one limitation: it was applicable only if IN was placed at certain positions in the query. For example, it couldn't be used with NOT IN. And query Q16 has a NOT IN! so the Optimizer could not apply subquery materialization to it, and was thus stuck with using EXISTS. Sad!&lt;br /&gt;&lt;br /&gt;Why it could not work with NOT IN, is not very easy to explain. It has to do with NULL values, because they sometimes prevent using the hash index. To give an idea, look at this:&lt;br /&gt;&amp;nbsp; &lt;i&gt;(NULL, 1) NOT IN (SELECT ...)&lt;/i&gt;&lt;br /&gt;Per the SQL standard, if the subquery's resultset contains at least one row of the form (x,1) where x is any number (or NULL), then the IN condition is neither TRUE, nor FALSE, it is &lt;b&gt;UNKNOWN&lt;/b&gt;. So is the NOT IN condition, because it is the negation of IN, and NOT(UNKNOWN) is UNKNOWN.&lt;br /&gt;Here are example of such rows: (NULL,1), (421,1), (236,1), (5329,1), ad infinitam.&lt;br /&gt;We can see that those rows will not be found by a lookup in the hash index: this index is defined on the two columns, it has a usual &quot;prefix-only&quot; behaviour, which means that it cannot be used to search for &quot;any value in first column, then 1 in second column&quot;. As long as the sentence starts with &quot;any value in first column&quot; a table scan is necessary; we should read each row of the temporary table and compare its second column with 1 until we find a matching row. And that:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;will drag subquery materialization's performance down&lt;/li&gt;&lt;li&gt;will drag subquery materialization's code complexity up.&lt;/li&gt;&lt;/ul&gt;And I have even not covered all problems here: there can be more than two columns, there can be more than one NULL in the left argument of IN, there can also be NULLs inside the subquery's resultset.&lt;br /&gt;&lt;br /&gt;In some lucky cases, the scan can be avoided, for example:&lt;br /&gt;&lt;i&gt;&amp;nbsp; SELECT * FROM table1 WHERE (a,b) IN (SELECT ...)&lt;/i&gt;&lt;br /&gt;If (a,b) is (NULL,1), the IN will be UNKNOWN or FALSE. It will be UNKNOWN if the subquery's resultset contains one (x,1) as seen above; otherwise it will be FALSE. No matter what, it will not be TRUE, and this is all that WHERE wants to know - (a,b) can thus be rejected without doing a scan.&lt;br /&gt;Now, for&lt;br /&gt;&lt;i&gt;&amp;nbsp; SELECT * FROM table1 WHERE (a,b) NOT IN (SELECT ...)&lt;/i&gt;&lt;br /&gt;i.e.&lt;br /&gt;&lt;i&gt;&amp;nbsp; SELECT * FROM table1 WHERE NOT ((a,b) IN (SELECT ...))&lt;/i&gt;&lt;br /&gt;things are different: if (a,b) is (NULL,1), the IN will be UNKNOWN or FALSE, as we said. So NOT IN will be UNKNOWN or TRUE. &quot;Hum, can you be more specific?? I need to know if it's TRUE&quot;, will the WHERE evaluation code ask. Then we have to do the scan...&lt;br /&gt;&lt;br /&gt;So now you understand why subquery materialization was restricted to certain placement of IN.&lt;br /&gt;&lt;br /&gt;What I have done recently is to lift this restriction in two simple, however common, cases:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;If all outer and inner expressions are not nullable, then no NULL can get in the way, so there is no problem.&lt;/li&gt;&lt;li&gt;If there is only one outer expression (and thus there is only one inner expression), figuring out the correct TRUE/FALSE/UNKNOWN answer is immediate. Understanding why... is left as an exercise to the reader :-)&lt;/li&gt;&lt;/ol&gt;Those two cases are independent: as long as one is satisfied, subquery materialization can apply to IN, no matter where it is placed (NOT IN, etc).&lt;br /&gt;&lt;br /&gt;It turned out to be very easy to code this: I had a working prototype in an afternoon.&lt;br /&gt;&lt;br /&gt;Q16 happens to meet the criteria of both cases: columns &lt;i&gt;'ps_suppkey&lt;/i&gt;' and &lt;i&gt;'s_suppkey'&lt;/i&gt; are declared NOT NULL (first case), and the subquery has only one outer and one inner expression (second case).&lt;br /&gt;&lt;br /&gt;So nowadays MySQL can, and does, use subquery materialization for query Q16; thanks to it, the execution time is down from 0.65 seconds to 0.47 seconds, which is a 25% improvement!&lt;br /&gt;&lt;br /&gt;The new technique is visible in EXPLAIN. I want to first show how EXPLAIN was with the EXISTS transformation, so I temporarily disable subquery materialization, then run EXPLAIN, enable&amp;nbsp; subquery materialization, run EXPLAIN:&lt;br /&gt;&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;mysql&amp;gt; set optimizer_switch='materialization=off';&lt;br /&gt;mysql&amp;gt; explain ...&lt;br /&gt;&lt;br /&gt;+----+--------------------+----------+-----------------+----------------------+--------------+---------+---------------------+--------+----------------------------------------------+&lt;br /&gt;| id | select_type        | table    | type            | possible_keys        | key          | key_len | ref                 | rows   | Extra                                        |&lt;br /&gt;+----+--------------------+----------+-----------------+----------------------+--------------+---------+---------------------+--------+----------------------------------------------+&lt;br /&gt;|  1 | PRIMARY            | part     | ALL             | PRIMARY              | NULL         | NULL    | NULL                | 199742 | Using where; Using temporary; Using filesort |&lt;br /&gt;|  1 | PRIMARY            | partsupp | ref             | PRIMARY,i_ps_partkey | i_ps_partkey | 4       | dbt3.part.p_partkey |      2 | Using where; Using index                     |&lt;br /&gt;|  2 | &lt;span style=&quot;color:red;&quot;&gt;DEPENDENT SUBQUERY&lt;/span&gt; | supplier | unique_subquery | PRIMARY              | PRIMARY      | 4       | func                |      1 | Using where                                  |&lt;br /&gt;+----+--------------------+----------+-----------------+----------------------+--------------+---------+---------------------+--------+----------------------------------------------+&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; set optimizer_switch='materialization=default'; # 'on' would work too&lt;br /&gt;mysql&amp;gt; explain ...&lt;br /&gt;&lt;br /&gt;+----+-------------+----------+------+----------------------+--------------+---------+---------------------+--------+----------------------------------------------+&lt;br /&gt;| id | select_type | table    | type | possible_keys        | key          | key_len | ref                 | rows   | Extra                                        |&lt;br /&gt;+----+-------------+----------+------+----------------------+--------------+---------+---------------------+--------+----------------------------------------------+&lt;br /&gt;|  1 | PRIMARY     | part     | ALL  | PRIMARY              | NULL         | NULL    | NULL                | 199742 | Using where; Using temporary; Using filesort |&lt;br /&gt;|  1 | PRIMARY     | partsupp | ref  | PRIMARY,i_ps_partkey | i_ps_partkey | 4       | dbt3.part.p_partkey |      2 | Using where; Using index                     |&lt;br /&gt;|  2 | &lt;span style=&quot;color:red;&quot;&gt;SUBQUERY&lt;/span&gt;    | supplier | ALL  | NULL                 | NULL         | NULL    | NULL                |  10113 | Using where                                  |&lt;br /&gt;+----+-------------+----------+------+----------------------+--------------+---------+---------------------+--------+----------------------------------------------+&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;If you compare, the big difference is that the third line says SUBQUERY and not DEPENDENT SUBQUERY anymore. DEPENDENT SUBQUERY means that it has be executed once per row of the top query. SUBQUERY means that it is executed only once.&lt;br /&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://glebshchepa.blogspot.com/2012/04/optimizer-new-explain-formatjson.html&quot;&gt;EXPLAIN FORMAT=JSON&lt;/a&gt;, another new feature in MySQL 5.6.5, shows more details of materialization:&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;{&lt;br /&gt;  &quot;query_block&quot;: {&lt;br /&gt;    &quot;select_id&quot;: 1,&lt;br /&gt;    &quot;ordering_operation&quot;: {&lt;br /&gt;      &quot;using_temporary_table&quot;: true,&lt;br /&gt;      &quot;using_filesort&quot;: true,&lt;br /&gt;      &quot;grouping_operation&quot;: {&lt;br /&gt;        &quot;using_filesort&quot;: true,&lt;br /&gt;        &quot;nested_loop&quot;: [&lt;br /&gt;          {&lt;br /&gt;            &quot;table&quot;: {&lt;br /&gt;              &quot;table_name&quot;: &quot;part&quot;,&lt;br /&gt;              &quot;access_type&quot;: &quot;ALL&quot;,&lt;br /&gt;              &quot;possible_keys&quot;: [&lt;br /&gt;                &quot;PRIMARY&quot;&lt;br /&gt;              ],&lt;br /&gt;              &quot;rows&quot;: 199742,&lt;br /&gt;              &quot;filtered&quot;: 100,&lt;br /&gt;              &quot;attached_condition&quot;: &quot;((`dbt3`.`part`.`p_brand` &amp;lt;&amp;gt; 'Brand#23') and (not((`dbt3`.`part`.`p_type` like 'LARGE PLATED%'))) and (`dbt3`.`part`.`p_size` in (43,1,25,5,35,12,42,40)))&quot;&lt;br /&gt;            }&lt;br /&gt;          },&lt;br /&gt;          {&lt;br /&gt;            &quot;table&quot;: {&lt;br /&gt;              &quot;table_name&quot;: &quot;partsupp&quot;,&lt;br /&gt;              &quot;access_type&quot;: &quot;ref&quot;,&lt;br /&gt;              &quot;possible_keys&quot;: [&lt;br /&gt;                &quot;PRIMARY&quot;,&lt;br /&gt;                &quot;i_ps_partkey&quot;&lt;br /&gt;              ],&lt;br /&gt;              &quot;key&quot;: &quot;i_ps_partkey&quot;,&lt;br /&gt;              &quot;key_length&quot;: &quot;4&quot;,&lt;br /&gt;              &quot;ref&quot;: [&lt;br /&gt;                &quot;dbt3.part.p_partkey&quot;&lt;br /&gt;              ],&lt;br /&gt;              &quot;rows&quot;: 2,&lt;br /&gt;              &quot;filtered&quot;: 100,&lt;br /&gt;              &quot;using_index&quot;: true,&lt;br /&gt;              &quot;attached_condition&quot;: &quot;(not(&amp;lt; in_optimizer &amp;gt;(`dbt3`.`partsupp`.`ps_suppkey`,`dbt3`.`partsupp`.`ps_suppkey` in ( &lt;span style=&quot;color:red;&quot;&gt;&amp;lt; materialize &amp;gt;&lt;/span&gt; (select `dbt3`.`supplier`.`s_suppkey` from `dbt3`.`supplier` where (`dbt3`.`supplier`.`s_comment` like '%Customer%Complaints%') ), &lt;span style=&quot;color:red;&quot;&gt;&amp;lt; primary_index_lookup &amp;gt;&lt;/span&gt;(`dbt3`.`partsupp`.`ps_suppkey` in &lt;span style=&quot;color:red;&quot;&gt;&amp;lt; temporary table &amp;gt;&lt;/span&gt; on distinct_key where ((`dbt3`.`partsupp`.`ps_suppkey` = `materialized subselect`.`s_suppkey`)))))))&quot;,&lt;br /&gt;              &quot;attached_subqueries&quot;: [&lt;br /&gt;                {&lt;br /&gt;                  &quot;using_temporary_table&quot;: true,&lt;br /&gt;                  &quot;dependent&quot;: false,&lt;br /&gt;                  &quot;cacheable&quot;: true,&lt;br /&gt;                  &quot;table&quot;: {&lt;br /&gt;                    &quot;access_type&quot;: &quot;eq_ref&quot;,&lt;br /&gt;                    &quot;key&quot;: &quot;&amp;lt; auto_key &amp;gt;&quot;,&lt;br /&gt;                    &quot;rows&quot;: 1&lt;br /&gt;                  },&lt;br /&gt;                  &quot;query_block&quot;: {&lt;br /&gt;                    &quot;select_id&quot;: 2,&lt;br /&gt;                    &quot;table&quot;: {&lt;br /&gt;                      &quot;table_name&quot;: &quot;supplier&quot;,&lt;br /&gt;                      &quot;access_type&quot;: &quot;ALL&quot;,&lt;br /&gt;                      &quot;rows&quot;: 10113,&lt;br /&gt;                      &quot;filtered&quot;: 100,&lt;br /&gt;                      &quot;attached_condition&quot;: &quot;(`dbt3`.`supplier`.`s_comment` like '%Customer%Complaints%')&quot;&lt;br /&gt;                    }&lt;br /&gt;                  }&lt;br /&gt;                }&lt;br /&gt;              ]&lt;br /&gt;            }&lt;br /&gt;          }&lt;br /&gt;        ]&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;Don't forget to scroll the box above to the right, because lines are long. This shows that:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;For the top query, we read a row from '&lt;i&gt;part&lt;/i&gt;', then one row from '&lt;i&gt;partsupp&lt;/i&gt;', then execute the subquery.&lt;/li&gt;&lt;li&gt;the very first execution of the subquery materializes (&lt;i&gt;&amp;lt;materialize&amp;gt;&lt;/i&gt;) &lt;i&gt;select `dbt3`.`supplier`.`s_suppkey` from `dbt3`.`supplier` where (`dbt3`.`supplier`.`s_comment` like '%Customer%Complaints%')&lt;/i&gt; into a temporary table&lt;/li&gt;&lt;li&gt;Each subquery execution does a lookup on the primary key of this temporary table (&lt;i&gt;&amp;lt;primary_index_lookup&amp;gt; ... in &amp;lt;temporary table&amp;gt;&lt;/i&gt;)&lt;/li&gt;&lt;li&gt;Going further down, we see how the temporary table will be filled: it will be the resultset of a table scan (&lt;i&gt;&quot;access_type&quot;: &quot;ALL&quot;&lt;/i&gt;) of '&lt;i&gt;supplier&lt;/i&gt;' with a filtering LIKE condition.&lt;/li&gt;&lt;/ol&gt;More details on the feature's usage can be found in the &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.6/en/subquery-materialization.html&quot;&gt;manual&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;This is the end of this post. I hope that it puts in good light the work we have put into 5.6. There are many other Optimizer features in this version, like EXPLAIN FORMAT=JSON and others; they are described in &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://mysqloptimizerteam.blogspot.co.uk/&quot;&gt;my colleagues' blog posts.&lt;/a&gt;</description>
         <author>Guilhem Bichot</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-9191231274999121063.post-8342165928358203100</guid>
         <pubDate>Tue, 10 Apr 2012 08:55:00 +0000</pubDate>
      </item>
      <item>
         <title>Semi-join in MySQL 5.6</title>
         <link>http://roylyseng.blogspot.com/2012/04/semi-join-in-mysql-56.html</link>
         <description>&lt;br /&gt;MySQL 5.6.5 Development Milestone Release has a whole new set of algorithms for processing subqueries. It is based on transforming a subquery into a &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://en.wikipedia.org/wiki/Semijoin&quot;&gt;semi-join operation&lt;/a&gt;, and then treating semi-join like another join operation throughout the optimizer.&lt;br /&gt;&lt;br /&gt;A subquery can be transformed to a semi-join if it matches these criteria:&lt;br /&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;The subquery is part of an IN or =ANY predicate. It cannot be e.g. NOT IN.&lt;/li&gt;&lt;li&gt;The subquery consists of a single query block (it must not contain UNION).&lt;/li&gt;&lt;li&gt;The subquery does not contain GROUP BY or HAVING.&lt;/li&gt;&lt;li&gt;The subquery is not implicitly grouped (it contains no aggregate functions).&lt;/li&gt;&lt;li&gt;The subquery predicate is part of a WHERE clause.&lt;/li&gt;&lt;li&gt;The subquery predicate must not be part of a disjunctive nor a negated search condition.&lt;/li&gt;&lt;li&gt;Neither query block contains the STRAIGHT_JOIN qualifier.&lt;/li&gt;&lt;li&gt;The statement must be a SELECT or INSERT statement. Semi-joins are not allowed with UPDATE or DELETE statements.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Example subquery that can be transformed to a semi-join:&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;b&gt;&amp;nbsp; SELECT * FROM nation&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;b&gt;&amp;nbsp; WHERE n_regionkey IN (SELECT r_regionkey FROM region&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; WHERE r_name='AFRICA');&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;h3&gt; &lt;/h3&gt;&lt;h3&gt;          What is a semi-join operation&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;A semi-join operation is similar to a regular join operation: It takes two (sets of) tables and combines them using a join condition.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Like an outer join, a semi-join is a noncommutative join operator. We denote the tables of the containing query block the outer tables and the tables of the subquery the inner tables.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Two factors distinguish a semi-join from a regular join:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;In a semi-join, the inner tables do not cause duplicates in the result.&lt;/li&gt;&lt;li&gt;No columns from the inner tables are added to the result of the operation.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;This means that the result of the semi-join is a subset of the rows from the outer tables. It also means that much of the special handling of semi-join is about efficiently eliminating duplicates from the inner tables.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;We can represent the above query using the artificial SEMI JOIN operator as follows:&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;b&gt;&amp;nbsp; SELECT nation.*&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;b&gt;&amp;nbsp; FROM nation SEMI JOIN region&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;ON nation.n_regionkey = region.r_regionkey AND&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; region.r_name='AFRICA';&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;h3&gt; &lt;/h3&gt;&lt;h3&gt;          Semi-join optimization&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;If possible, a subquery is transformed into a semi-join during the query resolution stage. The query blocks containing the outer tables and the inner tables are then combined into a larger query block.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The next step of the optimization is to determine inner tables that are candidate for table pullout. If the inner table of a semi-join has an eq-ref relationship to the outer part of the query, there can be no duplicates from the inner side, and the semi-join can be converted to a regular join. We call that a table pullout, because the table is &quot;pulled out&quot; from the semi-join and joined with the outer tables.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The above subquery can be transformed using table pullout, because there is a unique index on the r_regionkey column. The explain result of the query is as follows:&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;line-height:100%;overflow:scroll;padding:10px;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;| id | select_type | table  | type | possible_keys | key           | key_len | ref                     | rows | Extra       |&lt;br /&gt;+----+-------------+--------+------+---------------+---------------+---------+-------------------------+------+-------------+&lt;br /&gt;|  1 | PRIMARY     | region | ALL  | PRIMARY       | NULL          | NULL    | NULL                    |    5 | Using where |&lt;br /&gt;|  1 | PRIMARY     | nation | ref  | i_n_regionkey | i_n_regionkey | 5       | dbt3.region.r_regionkey |    2 |             |&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;As you can see, there is no difference between this explain result and the explain result of a regular join between these two tables.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The cost-based optimization procedure is then applied to the tables of the query block. Tables taking part in semi-joins are combined in various ways, with the appropriate cost added to the total plan cost. The end result is an optimal table order and an optimal set of semi-join execution strategies.&lt;/div&gt;&lt;/div&gt;&lt;h3&gt; &lt;/h3&gt;&lt;h3&gt;          Semi-join execution strategies&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;As said before, semi-join is a regular join operation, combined with removal of possible duplicates from the semi-join inner tables. MySQL implements four different semi-join execution strategies, which have different ways of removing duplicates:&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;FirstMatch&lt;/li&gt;&lt;li&gt;DuplicateWeedout&lt;/li&gt;&lt;li&gt;Materialization&lt;/li&gt;&lt;li&gt;LooseScan&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;I will handle the FirstMatch strategy in more detail below. The other semi-join strategies will be handled in later blogs. How semi-join strategies can be combined with join buffering will also be handled.&lt;/div&gt;&lt;/div&gt;&lt;h3&gt; &lt;/h3&gt;&lt;h3&gt;          Controlling semi-join strategies&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;Use of semi-join is controlled with the optimizer_switch flag semijoin:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&amp;nbsp; set optimizer_switch='semijoin=on'&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This command enables transformation of subqueries into semi-join operations. This flag is on by default in MySQL 5.6.5.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In addition, individual semi-join strategies can be turned on and off with the following optimizer_switch flags:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&amp;nbsp; firstmatch, materialization, loosescan&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;These flags enable or disable the use of the respective semi-join strategies. Notice that there is no way to disable the DuplicateWeedout strategy, as this is the &quot;last resort&quot; strategy selected by the cost-based optimizer. There is also no way to disable the TablePullout strategy, when semi-join is enabled.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The default for all semi-join related optimizer_switch flags are:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&amp;nbsp; 'semijoin=on,firstmatch=on,materialization=on,loosescan=on'&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Notice also that the optimizer_switch flag materialization has a second meaning: It controls the use of the subquery materialization feature (which should not be confused with the semi-join materialization strategy).&lt;/div&gt;&lt;/div&gt;&lt;h3&gt; &lt;/h3&gt;&lt;h3&gt;          Semi-join FirstMatch strategy&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;The semi-join FirstMatch strategy executes a subquery very similar to how the IN-TO-EXISTS strategy familiar from earlier versions of MySQL works: for each matching row in the outer table, check for a match in the inner table. When a match is found, return the row from the outer table, otherwise continue scanning the inner table until reaching the end.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here is a query that is processed using FirstMatch strategy:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;b&gt;&amp;nbsp; SELECT * FROM nation&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;b&gt;&amp;nbsp; WHERE n_nationkey IN (SELECT c_nationkey FROM customer);&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Notice the FirstMatch(nation) indication in explain output, which means that after a match has been found in table customer, the query executor goes back to scan more rows in table nation:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;line-height:100%;overflow:scroll;padding:10px;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;| id | select_type | table    | type | possible_keys | key           | key_len | ref                     | rows | Extra                           |&lt;br /&gt;+----+-------------+----------+------+---------------+---------------+---------+-------------------------+------+---------------------------------+&lt;br /&gt;|  1 | PRIMARY     | nation   | ALL  | PRIMARY       | NULL          | NULL    | NULL                    |   25 |                                 |&lt;br /&gt;|  1 | PRIMARY     | customer | ref  | i_c_nationkey | i_c_nationkey | 5       | dbt3.nation.n_nationkey | 1115 | Using index; FirstMatch(nation) |&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here is the result from IN-TO-EXISTS transformation, which can still be enabled by setting optimizer_switch flags:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;set optimizer_switch='semijoin=off,materialization=off'&lt;/span&gt; :&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;line-height:100%;overflow:scroll;padding:10px;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;| id | select_type        | table    | type           | possible_keys | key           | key_len | ref  | rows | Extra       |&lt;br /&gt;+----+--------------------+----------+----------------+---------------+---------------+---------+------+------+-------------+&lt;br /&gt;|  1 | PRIMARY            | nation   | ALL            | NULL          | NULL          | NULL    | NULL |   25 | Using where |&lt;br /&gt;|  2 | DEPENDENT SUBQUERY | customer | index_subquery | i_c_nationkey | i_c_nationkey | 5       | func | 1115 | Using index |&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;As this is basically the same strategy that MySQL 5.5 would choose, there is usually no speedup to gain when FirstMatch is chosen. The advantage may however come when FirstMatch is not the most efficient strategy, or when the cost-based optimizer chooses a more efficient table order. The optimizer can determine a better table order, because it will estimate a realistic cost for a semi-join operation. This contrasts MySQL 5.5 where the placement of a subquery in the query plan was strictly rule-based.&lt;/div&gt;&lt;/div&gt;&lt;h3&gt; &lt;/h3&gt;&lt;h3&gt;          Multiple subqueries&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;It is possible to combine multiple subqueries with AND and still have them transformed to a semi-join:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;b&gt;&amp;nbsp; SELECT * FROM nation&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;b&gt;&amp;nbsp; WHERE n_regionkey IN (SELECT r_regionkey FROM region&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; WHERE r_name='AFRICA') AND&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; n_nationkey IN (SELECT c_nationkey FROM customer);&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The explain output shows one FirstMatch strategy:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;line-height:100%;overflow:scroll;padding:10px;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;| id | select_type | table    | type | possible_keys         | key           | key_len | ref                     | rows | Extra                           |&lt;br /&gt;+----+-------------+----------+------+-----------------------+---------------+---------+-------------------------+------+---------------------------------+&lt;br /&gt;|  1 | PRIMARY     | region   | ALL  | PRIMARY               | NULL          | NULL    | NULL                    |    5 | Using where                     |&lt;br /&gt;|  1 | PRIMARY     | nation   | ref  | PRIMARY,i_n_regionkey | i_n_regionkey | 5       | dbt3.region.r_regionkey |    2 |                                 |&lt;br /&gt;|  1 | PRIMARY     | customer | ref  | i_c_nationkey         | i_c_nationkey | 5       | dbt3.nation.n_nationkey | 1115 | Using index; FirstMatch(nation) |&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3&gt; &lt;/h3&gt;&lt;h3&gt;          Nested subqueries&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;MySQL can process nested subqueries and transform them into one semi-join operation. Here is a slightly artificial example:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;b&gt;&amp;nbsp; SELECT * FROM nation&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;b&gt;&amp;nbsp; WHERE n_nationkey IN&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;(SELECT c_nationkey FROM customer&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; WHERE c_acctbal IN&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;(SELECT o_totalprice FROM orders&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; WHERE o_orderstatus = 'P'));&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;From the explain output, we see a FirstMatch strategy applied to tables customer and order, jumping back to table nation when a match is found:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;line-height:100%;overflow:scroll;padding:10px;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;| id | select_type | table    | type | possible_keys | key           | key_len | ref                     | rows    | Extra                           |&lt;br /&gt;+----+-------------+----------+------+---------------+---------------+---------+-------------------------+---------+---------------------------------+&lt;br /&gt;|  1 | PRIMARY     | nation   | ALL  | PRIMARY       | NULL          | NULL    | NULL                    |      25 |                                 |&lt;br /&gt;|  1 | PRIMARY     | customer | ref  | i_c_nationkey | i_c_nationkey | 5       | dbt3.nation.n_nationkey |    1115 |                                 |&lt;br /&gt;|  1 | PRIMARY     | orders   | ALL  | NULL          | NULL          | NULL    | NULL                    | 1486350 | Using where; FirstMatch(nation) |&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3&gt; &lt;/h3&gt;&lt;h3&gt;          Want to know more?&lt;/h3&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;Check out the MySQL documentation for semi-join optimization&amp;nbsp;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.6/en/semi-joins.html&quot;&gt;here&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;</description>
         <author>Roy Lyseng</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-7355718097481623110.post-957950860003048344</guid>
         <pubDate>Tue, 10 Apr 2012 07:37:00 +0000</pubDate>
      </item>
      <item>
         <title>Optimizer: new EXPLAIN FORMAT=JSON</title>
         <link>http://glebshchepa.blogspot.com/2012/04/optimizer-new-explain-formatjson.html</link>
         <description>&lt;div class=&quot;_post&quot;&gt;&lt;h1&gt;New feature: structured EXPLAIN&lt;/h1&gt;&lt;br /&gt;&lt;p&gt;Probably, most of us tried to read the output of MySQL EXPLAIN command at least once.&lt;br /&gt;And probably many of us have decided, that it isn't so readable and understandable as it has to be.&lt;br /&gt;Some of us even tried to create or use external pretty-printing programs and other complicated converters to make EXPLAIN's output less cryptic, for example nice Percona's &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://www.percona.com/doc/percona-toolkit/2.1/pt-visual-explain.html&quot;&gt;Visual Explain&lt;/a&gt; script.&lt;br /&gt;But from &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;https://dev.mysql.com/doc/refman/5.6/en/explain.html&quot;&gt;now&lt;/a&gt; we have a native MySQL command that explains query execution plan in a better human-readable and machine-readable way: &lt;b&gt;EXPLAIN FORMAT=JSON&lt;/b&gt;!&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;A trivial example, SELECT from a single table:&lt;/h3&gt;&lt;div class=&quot;_code&quot;&gt;&lt;pre&gt;mysql&amp;gt; CREATE TABLE t1 (i INT, j INT);&lt;br /&gt;Query OK, 0 rows affected (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; EXPLAIN SELECT * FROM t1;&lt;br /&gt;+----+-------------+-------+--------+---------------+------+---------+------+------+---------------------+&lt;br /&gt;| id | select_type | table | type   | possible_keys | key  | key_len | ref  | rows | Extra               |&lt;br /&gt;+----+-------------+-------+--------+---------------+------+---------+------+------+---------------------+&lt;br /&gt;|  1 | SIMPLE      | t1    | system | NULL          | NULL | NULL    | NULL |    0 | const row not found |&lt;br /&gt;+----+-------------+-------+--------+---------------+------+---------+------+------+---------------------+&lt;br /&gt;1 row in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; EXPLAIN FORMAT=JSON SELECT * FROM t1;&lt;br /&gt;+------------------------------------------------- ...&lt;br /&gt;| EXPLAIN                                          &lt;br /&gt;+------------------------------------------------- ...&lt;br /&gt;| {&lt;br /&gt;  &lt;i&gt;&quot;query_block&quot;&lt;/i&gt;: {&lt;br /&gt;    &lt;i&gt;&quot;select_id&quot;&lt;/i&gt;: 1,&lt;br /&gt;    &lt;i&gt;&quot;table&quot;&lt;/i&gt;: {&lt;br /&gt;      &lt;i&gt;&quot;table_name&quot;&lt;/i&gt;: &quot;t1&quot;,&lt;br /&gt;      &lt;i&gt;&quot;access_type&quot;&lt;/i&gt;: &quot;system&quot;,&lt;br /&gt;      &lt;i&gt;&quot;rows&quot;&lt;/i&gt;: 0,&lt;br /&gt;      &lt;i&gt;&quot;filtered&quot;&lt;/i&gt;: 0,&lt;br /&gt;      &lt;i&gt;&quot;const_row_not_found&quot;&lt;/i&gt;: true&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;} |&lt;br /&gt;+------------------------------------------------- ...&lt;br /&gt;1 row in set, 1 warning (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; SHOW WARNINGS;&lt;br /&gt;+-------+------+----------------------------------------------------------------+&lt;br /&gt;| Level | Code | Message                                                        |&lt;br /&gt;+-------+------+----------------------------------------------------------------+&lt;br /&gt;| Note  | 1003 | /* select#1 */ select NULL AS `i`,NULL AS `j` from `test`.`t1` |&lt;br /&gt;+-------+------+----------------------------------------------------------------+&lt;br /&gt;1 row in set (0.00 sec)&lt;br /&gt;  &lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Moreover, new EXPLAIN is not just a 1-to-1 translator of the old EXPLAIN command output.&lt;br /&gt;JSON output is much more informative:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;it tries to display a real execution path of the query;&lt;/li&gt;&lt;li&gt;it describes pushed conditions;&lt;/li&gt;&lt;li&gt;temporary table and index creation is displayed in more precise&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;&lt;h3&gt;More advanced example, nested loop join and pushed conditions (see &quot;attached_conditions&quot;):&lt;/h3&gt;&lt;div class=&quot;_code&quot;&gt;&lt;pre&gt;mysql&amp;gt; CREATE TABLE t2 (i INT KEY, j INT);&lt;br /&gt;Query OK, 0 rows affected (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);&lt;br /&gt;Query OK, 3 rows affected (0.00 sec)&lt;br /&gt;Records: 3  Duplicates: 0  Warnings: 0&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; INSERT INTO t2 SELECT * FROM t1;&lt;br /&gt;Query OK, 3 rows affected (0.00 sec)&lt;br /&gt;Records: 3  Duplicates: 0  Warnings: 0&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; EXPLAIN SELECT * FROM t1 JOIN t2 ON t1.i = t2.i WHERE t1.j &amp;gt; 1 AND t2.j &amp;lt; 3;&lt;br /&gt;+----+-------------+-------+--------+---------------+---------+---------+-----------+------+-------------+&lt;br /&gt;| id | select_type | table | type   | possible_keys | key     | key_len | ref       | rows | Extra       |&lt;br /&gt;+----+-------------+-------+--------+---------------+---------+---------+-----------+------+-------------+&lt;br /&gt;|  1 | SIMPLE      | t1    | ALL    | NULL          | NULL    | NULL    | NULL      |    3 | Using where |&lt;br /&gt;|  1 | SIMPLE      | t2    | eq_ref | PRIMARY       | PRIMARY | 4       | test.t1.i |    1 | Using where |&lt;br /&gt;+----+-------------+-------+--------+---------------+---------+---------+-----------+------+-------------+&lt;br /&gt;2 rows in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; SET end_markers_in_json=true;&lt;br /&gt;Query OK, 0 rows affected (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; EXPLAIN FORMAT=JSON SELECT * FROM t1 JOIN t2 ON t1.i = t2.i WHERE t1.j &amp;gt; 1 AND t2.j &amp;lt; 3;&lt;br /&gt;+------------------------------------------------- ...&lt;br /&gt;| EXPLAIN&lt;br /&gt;+------------------------------------------------- ...&lt;br /&gt;| {&lt;br /&gt;  &lt;i&gt;&quot;query_block&quot;&lt;/i&gt;: {&lt;br /&gt;    &lt;i&gt;&quot;select_id&quot;&lt;/i&gt;: 1,&lt;br /&gt;    &lt;u&gt;&lt;i&gt;&quot;nested_loop&quot;&lt;/i&gt;&lt;/u&gt;: [&lt;br /&gt;      {&lt;br /&gt;        &lt;i&gt;&quot;table&quot;&lt;/i&gt;: {&lt;br /&gt;          &lt;i&gt;&quot;table_name&quot;&lt;/i&gt;: &quot;t1&quot;,&lt;br /&gt;          &lt;i&gt;&quot;access_type&quot;&lt;/i&gt;: &quot;ALL&quot;,&lt;br /&gt;          &lt;i&gt;&quot;rows&quot;&lt;/i&gt;: 3,&lt;br /&gt;          &lt;i&gt;&quot;filtered&quot;&lt;/i&gt;: 100,&lt;br /&gt;          &lt;u&gt;&lt;i&gt;&quot;attached_condition&quot;&lt;/i&gt;&lt;/u&gt;: &quot;((`test`.`t1`.`j` &amp;gt; 1) and (`test`.`t1`.`i` is not null))&quot;&lt;br /&gt;        } /* table */&lt;br /&gt;      },&lt;br /&gt;      {&lt;br /&gt;        &lt;i&gt;&quot;table&quot;&lt;/i&gt;: {&lt;br /&gt;          &lt;i&gt;&quot;table_name&quot;&lt;/i&gt;: &quot;t2&quot;,&lt;br /&gt;          &lt;i&gt;&quot;access_type&quot;&lt;/i&gt;: &quot;eq_ref&quot;,&lt;br /&gt;          &lt;i&gt;&quot;possible_keys&quot;&lt;/i&gt;: [&lt;br /&gt;            &quot;PRIMARY&quot;&lt;br /&gt;          ] /* possible_keys */,&lt;br /&gt;          &lt;i&gt;&quot;key&quot;&lt;/i&gt;: &quot;PRIMARY&quot;,&lt;br /&gt;          &lt;i&gt;&quot;key_length&quot;&lt;/i&gt;: &quot;4&quot;,&lt;br /&gt;          &lt;i&gt;&quot;ref&quot;&lt;/i&gt;: [&lt;br /&gt;            &quot;test.t1.i&quot;&lt;br /&gt;          ] /* ref */,&lt;br /&gt;          &lt;i&gt;&quot;rows&quot;&lt;/i&gt;: 1,&lt;br /&gt;          &lt;i&gt;&quot;filtered&quot;&lt;/i&gt;: 100,&lt;br /&gt;          &lt;u&gt;&lt;i&gt;&quot;attached_condition&quot;&lt;/i&gt;&lt;/u&gt;: &quot;(`test`.`t2`.`j` &amp;lt; 3)&quot;&lt;br /&gt;        } /* table */&lt;br /&gt;      }&lt;br /&gt;    ] /* nested_loop */&lt;br /&gt;  } /* query_block */&lt;br /&gt;} |&lt;br /&gt;+------------------------------------------------- ...&lt;br /&gt;1 row in set, 1 warning (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; SHOW WARNINGS;&lt;br /&gt;+-------+------+---------------------------------- ...&lt;br /&gt;| Level | Code | Message&lt;br /&gt;+-------+------+---------------------------------- ...&lt;br /&gt;| Note  | 1003 | /* select#1 */ select `test`.`t1`.`i` AS `i`,`test`.`t1`.`j` AS `j`,`test`.`t2`.`i` AS `i`,`test`.`t2`.`j` AS `j` from `test`.`t1` join `test`.`t2` where ((`test`.`t2`.`i` = `test`.`t1`.`i`) and (`test`.`t1`.`j` &amp;gt; 1) and (`test`.`t2`.`j` &amp;lt; 3)) |&lt;br /&gt;+-------+------+---------------------------------- ...&lt;br /&gt;1 row in set (0.00 sec)&lt;br /&gt;  &lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Thus, instead of the &quot;Using where&quot; in the traditional EXPLAIN output we can see where each part of query condition was pushed down and what part was pushed.&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;Similar example: &quot;Using index&quot; goes &quot;index_condition&quot;:&lt;/h3&gt;&lt;div class=&quot;_code&quot;&gt;&lt;pre&gt;mysql&amp;gt; EXPLAIN SELECT * FROM t2 WHERE i &amp;gt; 1 AND j &amp;lt; 3;&lt;br /&gt;+----+-------------+-------+-------+---------------+---------+---------+------+------+------------------------------------+&lt;br /&gt;| id | select_type | table | type  | possible_keys | key     | key_len | ref  | rows | Extra                              |&lt;br /&gt;+----+-------------+-------+-------+---------------+---------+---------+------+------+------------------------------------+&lt;br /&gt;|  1 | SIMPLE      | t2    | range | PRIMARY       | PRIMARY | 4       | NULL |    2 | Using index condition; Using where |&lt;br /&gt;+----+-------------+-------+-------+---------------+---------+---------+------+------+------------------------------------+&lt;br /&gt;1 row in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; EXPLAIN FORMAT=JSON SELECT * FROM t2 WHERE i &amp;gt; 1 AND j &amp;lt; 3;&lt;br /&gt;+------------------------------------------------- ...&lt;br /&gt;| EXPLAIN&lt;br /&gt;+------------------------------------------------- ...&lt;br /&gt;| {&lt;br /&gt;  &lt;i&gt;&quot;query_block&quot;&lt;/i&gt;: {&lt;br /&gt;    &lt;i&gt;&quot;select_id&quot;&lt;/i&gt;: 1,&lt;br /&gt;    &lt;i&gt;&quot;table&quot;&lt;/i&gt;: {&lt;br /&gt;      &lt;i&gt;&quot;table_name&quot;&lt;/i&gt;: &quot;t2&quot;,&lt;br /&gt;      &lt;i&gt;&quot;access_type&quot;&lt;/i&gt;: &quot;range&quot;,&lt;br /&gt;      &lt;i&gt;&quot;possible_keys&quot;&lt;/i&gt;: [&lt;br /&gt;        &lt;i&gt;&quot;PRIMARY&quot;&lt;/i&gt;&lt;br /&gt;      ] /* possible_keys */,&lt;br /&gt;      &lt;i&gt;&quot;key&quot;&lt;/i&gt;: &quot;PRIMARY&quot;,&lt;br /&gt;      &lt;i&gt;&quot;key_length&quot;&lt;/i&gt;: &quot;4&quot;,&lt;br /&gt;      &lt;i&gt;&quot;rows&quot;&lt;/i&gt;: 2,&lt;br /&gt;      &lt;i&gt;&quot;filtered&quot;&lt;/i&gt;: 100,&lt;br /&gt;      &lt;u&gt;&lt;i&gt;&quot;index_condition&quot;&lt;/i&gt;&lt;/u&gt;: &quot;(`test`.`t2`.`i` &amp;gt; 1)&quot;,&lt;br /&gt;      &lt;u&gt;&lt;i&gt;&quot;attached_condition&quot;&lt;/i&gt;&lt;/u&gt;: &quot;(`test`.`t2`.`j` &amp;lt; 3)&quot;&lt;br /&gt;    } /* table */&lt;br /&gt;  } /* query_block */&lt;br /&gt;} |&lt;br /&gt;+------------------------------------------------- ...&lt;br /&gt;1 row in set, 1 warning (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; SHOW WARNINGS;&lt;br /&gt;+-------+------+---------------------------------- ...&lt;br /&gt;| Level | Code | Message                                                                                                                                      |&lt;br /&gt;+-------+------+---------------------------------- ...&lt;br /&gt;| Note  | 1003 | /* select#1 */ select `test`.`t2`.`i` AS `i`,`test`.`t2`.`j` AS `j` from `test`.`t2` where ((`test`.`t2`.`i` &amp;gt; 1) and (`test`.`t2`.`j` &amp;lt; 3)) |&lt;br /&gt;+-------+------+---------------------------------- ...&lt;br /&gt;1 row in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;  &lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Also the new explain reflects the proper execution order of &quot;grouping_operation&quot; (GROUP BY clause), &quot;ordering_operation&quot; (ORDER BY) and &quot;duplicates_removal&quot; (DISTINCT etc.) operations.&lt;br /&gt;Each of them may be an initiator of temporary table or temporary index (filesort) creation.&lt;br /&gt;One of the most advanced features of new EXPLAIN is how it displays temporary tables and temporary indices.&lt;br /&gt;Whereas the traditional EXPLAIN may &quot;aggregate&quot; two facts of temporary table creation into the single &quot;Using temporary table&quot; message (and similar for temporary index), the new JSON output tries to display both (if any) at the proper place: i.e. if the &quot;ORDER BY&quot; clause is the initiator of a temporary table creation, the new EXPLAIN shows &lt;i&gt;&quot;using_temporary_table&quot;: true&lt;/i&gt; key-value pair under the &quot;ordering_operation&quot; node, and if, for example, the &quot;GROUP BY&quot; causes a temporary index creation, the EXPLAIN outputs &lt;i&gt;&quot;using_filesort&quot;: true&lt;/i&gt; pair under the &quot;grouping_operation&quot; node.&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;Simple example with DISTINCT, GROUP BY and ORDER BY:&lt;/h3&gt;&lt;div class=&quot;_code&quot;&gt;&lt;pre&gt;mysql&amp;gt; EXPLAIN SELECT DISTINCT * FROM t1 GROUP BY i ORDER BY j;&lt;br /&gt;+----+-------------+-------+------+---------------+------+---------+------+------+---------------------------------+&lt;br /&gt;| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra                           |&lt;br /&gt;+----+-------------+-------+------+---------------+------+---------+------+------+---------------------------------+&lt;br /&gt;|  1 | SIMPLE      | t1    | ALL  | NULL          | NULL | NULL    | NULL |    3 | Using temporary; Using filesort |&lt;br /&gt;+----+-------------+-------+------+---------------+------+---------+------+------+---------------------------------+&lt;br /&gt;1 row in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; EXPLAIN FORMAT=JSON SELECT DISTINCT * FROM t1 GROUP BY i ORDER BY j;&lt;br /&gt;+------------------------------------------------- ...&lt;br /&gt;| EXPLAIN&lt;br /&gt;+------------------------------------------------- ...&lt;br /&gt;| {&lt;br /&gt;  &lt;i&gt;&quot;query_block&quot;&lt;/i&gt;: {&lt;br /&gt;    &lt;i&gt;&quot;select_id&quot;&lt;/i&gt;: 1,&lt;br /&gt;    &lt;u&gt;&lt;i&gt;&quot;ordering_operation&quot;&lt;/i&gt;&lt;/u&gt;: {&lt;br /&gt;      &lt;u&gt;&lt;i&gt;&quot;using_filesort&quot;&lt;/i&gt;&lt;/u&gt;: true,&lt;br /&gt;      &lt;u&gt;&lt;i&gt;&quot;duplicates_removal&quot;&lt;/i&gt;&lt;/u&gt;: {&lt;br /&gt;        &lt;u&gt;&lt;i&gt;&quot;using_filesort&quot;&lt;/i&gt;&lt;/u&gt;: false,&lt;br /&gt;        &lt;u&gt;&lt;i&gt;&quot;grouping_operation&quot;&lt;/i&gt;&lt;/u&gt;: {&lt;br /&gt;          &lt;u&gt;&lt;i&gt;&quot;using_temporary_table&quot;&lt;/i&gt;&lt;/u&gt;: true,&lt;br /&gt;          &lt;u&gt;&lt;i&gt;&quot;using_filesort&quot;&lt;/i&gt;&lt;/u&gt;: false,&lt;br /&gt;          &lt;i&gt;&quot;table&quot;&lt;/i&gt;: {&lt;br /&gt;            &lt;i&gt;&quot;table_name&quot;&lt;/i&gt;: &quot;t1&quot;,&lt;br /&gt;            &lt;i&gt;&quot;access_type&quot;&lt;/i&gt;: &quot;ALL&quot;,&lt;br /&gt;            &lt;i&gt;&quot;rows&quot;&lt;/i&gt;: 3,&lt;br /&gt;            &lt;i&gt;&quot;filtered&quot;&lt;/i&gt;: 100&lt;br /&gt;          } /* table */&lt;br /&gt;        } /* grouping_operation */&lt;br /&gt;      } /* duplicates_removal */&lt;br /&gt;    } /* ordering_operation */&lt;br /&gt;  } /* query_block */&lt;br /&gt;} |&lt;br /&gt;+------------------------------------------------- ...&lt;br /&gt;1 row in set, 1 warning (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; SHOW WARNINGS;&lt;br /&gt;+-------+------+---------------------------------- ...&lt;br /&gt;| Level | Code | Message                                                                                                                                         |&lt;br /&gt;+-------+------+---------------------------------- ...&lt;br /&gt;| Note  | 1003 | /* select#1 */ select distinct `test`.`t1`.`i` AS `i`,`test`.`t1`.`j` AS `j` from `test`.`t1` group by `test`.`t1`.`i` order by `test`.`t1`.`j` |&lt;br /&gt;+-------+------+---------------------------------- ...&lt;br /&gt;1 row in set (0.00 sec)&lt;br /&gt;  &lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Another advanced feature is how the new EXPLAIN displays table materializations. It includes &quot;derived tables&quot;, semi-join materialization etc.&lt;br /&gt;Update: See &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://guilhembichot.blogspot.co.uk/2012/04/faster-subqueries-with-materialization.html&quot;&gt;Guilhem's post&lt;/a&gt; about subqueries with materialization for a complicated example.&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;Trivial subquery materialization example: &quot;derived table&quot;&lt;/h3&gt;&lt;div class=&quot;_code&quot;&gt;&lt;pre&gt;mysql&amp;gt; EXPLAIN SELECT * FROM (SELECT * FROM t1) t;&lt;br /&gt;+----+-------------+------------+------+---------------+------+---------+------+------+-------+&lt;br /&gt;| id | select_type | table      | type | possible_keys | key  | key_len | ref  | rows | Extra |&lt;br /&gt;+----+-------------+------------+------+---------------+------+---------+------+------+-------+&lt;br /&gt;|  1 | PRIMARY     | &amp;lt;derived2&amp;gt; | ALL  | NULL          | NULL | NULL    | NULL |    3 | NULL  |&lt;br /&gt;|  2 | DERIVED     | t1         | ALL  | NULL          | NULL | NULL    | NULL |    3 | NULL  |&lt;br /&gt;+----+-------------+------------+------+---------------+------+---------+------+------+-------+&lt;br /&gt;2 rows in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; EXPLAIN FORMAT=JSON SELECT * FROM (SELECT * FROM t1) t;&lt;br /&gt;+------------------------------------------------- ...&lt;br /&gt;| EXPLAIN&lt;br /&gt;+------------------------------------------------- ...&lt;br /&gt;| {&lt;br /&gt;  &lt;i&gt;&quot;query_block&quot;&lt;/i&gt;: {&lt;br /&gt;    &lt;i&gt;&quot;select_id&quot;&lt;/i&gt;: 1,&lt;br /&gt;    &lt;i&gt;&quot;table&quot;&lt;/i&gt;: {&lt;br /&gt;      &lt;i&gt;&quot;table_name&quot;&lt;/i&gt;: &quot;t&quot;,&lt;br /&gt;      &lt;i&gt;&quot;access_type&quot;&lt;/i&gt;: &quot;ALL&quot;,&lt;br /&gt;      &lt;i&gt;&quot;rows&quot;&lt;/i&gt;: 3,&lt;br /&gt;      &lt;i&gt;&quot;filtered&quot;&lt;/i&gt;: 100,&lt;br /&gt;      &lt;u&gt;&lt;i&gt;&quot;materialized_from_subquery&quot;&lt;/i&gt;&lt;/u&gt;: {&lt;br /&gt;        &lt;i&gt;&quot;using_temporary_table&quot;&lt;/i&gt;: true,&lt;br /&gt;        &lt;i&gt;&quot;dependent&quot;&lt;/i&gt;: false,&lt;br /&gt;        &lt;i&gt;&quot;cacheable&quot;&lt;/i&gt;: true,&lt;br /&gt;        &lt;i&gt;&quot;query_block&quot;&lt;/i&gt;: {&lt;br /&gt;          &lt;i&gt;&quot;select_id&quot;&lt;/i&gt;: 2,&lt;br /&gt;          &lt;i&gt;&quot;table&quot;&lt;/i&gt;: {&lt;br /&gt;            &lt;i&gt;&quot;table_name&quot;&lt;/i&gt;: &quot;t1&quot;,&lt;br /&gt;            &lt;i&gt;&quot;access_type&quot;&lt;/i&gt;: &quot;ALL&quot;,&lt;br /&gt;            &lt;i&gt;&quot;rows&quot;&lt;/i&gt;: 3,&lt;br /&gt;            &lt;i&gt;&quot;filtered&quot;&lt;/i&gt;: 100&lt;br /&gt;          } /* table */&lt;br /&gt;        } /* query_block */&lt;br /&gt;      } /* materialized_from_subquery */&lt;br /&gt;    } /* table */&lt;br /&gt;  } /* query_block */&lt;br /&gt;} |&lt;br /&gt;+------------------------------------------------- ...&lt;br /&gt;1 row in set, 1 warning (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; SHOW WARNINGS;&lt;br /&gt;+-------+------+---------------------------------- ...&lt;br /&gt;| Level | Code | Message                                                                                                                                             |&lt;br /&gt;+-------+------+---------------------------------- ...&lt;br /&gt;| Note  | 1003 | /* select#1 */ select `t`.`i` AS `i`,`t`.`j` AS `j` from (/* select#2 */ select `test`.`t1`.`i` AS `i`,`test`.`t1`.`j` AS `j` from `test`.`t1`) `t` |&lt;br /&gt;+-------+------+---------------------------------- ...&lt;br /&gt;1 row in set (0.00 sec)&lt;br /&gt;  &lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The new JSON EXPLAIN is feature rich, please try it and have fun! &lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;And, at the end of this post, let me thank &lt;a rel=&quot;nofollow&quot;&gt;Evgeny Potemkin&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://guilhembichot.blogspot.com&quot;&gt;Guilhem Bichot&lt;/a&gt; for their great help in design and implementation of structured EXPLAIN!&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;/div&gt;</description>
         <author>Gleb Shchepa</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-1969531358897361384.post-6234665777315496824</guid>
         <pubDate>Tue, 10 Apr 2012 06:25:00 +0000</pubDate>
      </item>
      <item>
         <title>DATETIME DEFAULT NOW() finally available.</title>
         <link>http://optimize-this.blogspot.com/2012/04/datetime-default-now-finally-available.html</link>
         <description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align:left;&quot;&gt;&lt;div style=&quot;text-align:left;&quot;&gt;&lt;div style=&quot;text-align:left;&quot;&gt;&lt;span style=&quot;font-family:Georgia, serif;font-size:16px;&quot;&gt;Having been rather desired for a while now,&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;font-size:100%;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;&quot;&gt;this feature is finally available a&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;font-size:16px;&quot;&gt;s of MySQL Server version 5.6.5&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;font-size:100%;&quot;&gt;. It started out as the innocuous bug #27645 back in 2007, not really commanding much attention from anyone. But since, the page has seen around a hundred posts from users. This is a lot of posts for a bug page.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;font-family:Georgia, serif;font-size:100%;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;font-size:100%;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;&quot;&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt;When I got to work on this, I started out looking at how functions in the default clause worked for the one supported case, &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt; (a.k.a. &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;NOW()&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt; in MySQL) for &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;TIMESTAMP&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt; columns. It turned out to be a little more complex than it had to, the &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;TIMESTAMP&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt; type has a lot of special rules attached to it that no other types have, not even &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;DATETIME&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt;.&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family:Georgia, serif;font-size:100%;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;font-size:100%;font-variant:normal;font-weight:normal;line-height:normal;&quot;&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt;One thing that struck me as a little odd was that you can only have &lt;i style=&quot;font-family:Georgia, serif;&quot;&gt;one&lt;/i&gt; column with a function in the default or on update clause. So I had to t&lt;/span&gt;&lt;span style=&quot;font-family:georgia;font-size:100%;&quot;&gt;ake the decision how to deal with that restriction as we introduced&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;font-size:100%;&quot;&gt;DATETIME DEFAULT CURRENT_TIMESTAMP&lt;/span&gt;&lt;span style=&quot;font-family:georgia;font-size:100%;&quot;&gt;. Should we forbid having a &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;font-size:100%;&quot;&gt;TIMESTAMP&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;font-size:100%;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;font-size:100%;&quot;&gt;DEFAULT CURRENT TIMESTAMP&lt;/span&gt;&lt;span style=&quot;font-family:georgia;font-size:100%;&quot;&gt; if there was a &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;&lt;span style=&quot;font-size:100%;&quot;&gt;DATETIME&lt;/span&gt;&lt;span style=&quot;font-size:100%;&quot;&gt; DEFAULT CURRENT_TIMESTAMP&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family:georgia;font-size:100%;&quot;&gt;? No matter how I looked at it, there would have to be a bunch of new convoluted special rules added that I couldn't motivate to myself. Moreover, a lot of users seemed to request having one &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;font-size:100%;&quot;&gt;DATETIME&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;font-size:100%;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family:georgia;font-size:100%;&quot;&gt;column with &quot;default now&quot; and another one &quot;on update now&quot;, so they could track both creation and last update time to a row. The request was not lost on me: I decided to lift the one-column-with-&lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;font-size:100%;&quot;&gt;NOW()&lt;/span&gt;&lt;span style=&quot;font-family:georgia;font-size:100%;&quot;&gt; restriction altogether. It made documentation simpler and avoided having rules applying only&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;font-size:100%;&quot;&gt;TIMESTAMP&lt;/span&gt;&lt;span style=&quot;font-family:georgia;font-size:100%;&quot;&gt; spill over on &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;font-size:100%;&quot;&gt;DATETIME&lt;/span&gt;&lt;span style=&quot;font-family:georgia;font-size:100%;&quot;&gt;. As expected, there was no technical reason for forbidding it, and the code involved needed an overhaul anyways.&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family:Georgia, serif;font-size:100%;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;&quot;&gt;&lt;h3 style=&quot;text-align:left;&quot;&gt;    The rules&lt;/h3&gt;&lt;span style=&quot;font-size:100%;&quot;&gt;I will not attempt to describe the various &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;font-size:100%;&quot;&gt;TIMESTAMP&lt;/span&gt;&lt;span style=&quot;font-size:100%;&quot;&gt; rules here, the manual does a great job at it already. Instead I will present the new set of rules for how &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;font-size:100%;&quot;&gt;DATETIME DEFAULT&lt;/span&gt;&lt;span style=&quot;font-size:100%;&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;font-size:100%;&quot;&gt;ON UPDATE CURRENT_TIMESTAMP&lt;/span&gt;&lt;span style=&quot;font-size:100%;&quot;&gt; works.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;span style=&quot;font-family:georgia;&quot;&gt;Trying to store a&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt;null value&lt;/span&gt;&lt;span style=&quot;font-family:georgia;&quot;&gt;&amp;nbsp;in a &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;DATETIME NOT NULL&lt;/span&gt;&lt;span style=&quot;font-family:georgia;&quot;&gt; column will always yield an error.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family:georgia;&quot;&gt;A &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;DATETIME&lt;/span&gt;&lt;span style=&quot;font-family:georgia;&quot;&gt; column accepting &lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt;nulls&lt;/span&gt;&lt;span style=&quot;font-family:georgia;&quot;&gt; has &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;NULL&lt;/span&gt;&lt;span style=&quot;font-family:georgia;&quot;&gt; as default value unless specified.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family:georgia;&quot;&gt;There are no implicit defaults for &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;DATETIME&lt;/span&gt;&lt;span style=&quot;font-family:georgia;&quot;&gt; columns except the one above.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family:georgia;&quot;&gt;A &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;DATETIME DEFAULT CURRENT_TIMESTAMP&lt;/span&gt;&lt;span style=&quot;font-family:georgia;&quot;&gt; column will store &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span style=&quot;font-family:georgia;&quot;&gt; if and only if an insert statement does not list the column or store a full row.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family:georgia;&quot;&gt;A &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;DATETIME ON UPDATE CURRENT_TIMESTAMP&lt;/span&gt;&lt;span style=&quot;font-family:georgia;&quot;&gt; column will store &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span style=&quot;font-family:georgia;&quot;&gt; if the row is updated and the new row is different from the previous row.&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div style=&quot;font-family:Georgia, serif;&quot;&gt;These rules sure look simple enough, don't they? Well, of course there are some more ins and outs to it if you look close enough. Hopefully you should not have to memorize all of these scenarios but it would be unfair of me not to at least mention them.&lt;/div&gt;&lt;div style=&quot;font-family:Georgia, serif;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt;In rule #4, &quot;an insert statement&quot; includes, of course &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;INSERT&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;LOAD&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt; and &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;REPLACE&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt; statements. &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;LOAD&lt;/span&gt; &lt;span style=&quot;font-family:georgia;&quot;&gt;statements will store &quot;0000-00-00 00:00:00&quot; (a.k.a. the 'zero date') in a &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;DATETIME&lt;/span&gt;&lt;span style=&quot;font-family:georgia;&quot;&gt; column if the file contains a &lt;/span&gt;null&lt;span style=&quot;font-family:georgia;&quot;&gt; at the column's position. Under strict &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;sql_mode&lt;/span&gt;&lt;span style=&quot;font-family:georgia;&quot;&gt;, the entire statement will fail. &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;TIMESTAMP&lt;/span&gt;&lt;span style=&quot;font-family:georgia;&quot;&gt; columns still store &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span style=&quot;font-family:georgia;&quot;&gt; in this case, regardless of default.&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family:Georgia, serif;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt;In rule #5, the statements that can update a row are &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt; and &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;INSERT&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt; ... &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;ON DUPLICATE KEY UPDATE&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt; ... when there is a duplicate primary key.&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family:Georgia, serif;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;font-family:Georgia, serif;&quot;&gt;Lifting the one-column-with-function-default restriction also has some side-effects when you add or move columns.&lt;/div&gt;&lt;div style=&quot;font-family:Georgia, serif;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt;As before, the first column in a table that is declared simply &quot;&lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;TIMESTAMP&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt;&quot; will implicitly be promoted to&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt;. If you add another column that is &quot;&lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;TIMESTAMP&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt;&quot; before it, they will both be as that long line I just wrote. Previously, you would get an error in this case.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt;Oh, yeah, error number 1293, a.k.a.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;ER_TOO_MUCH_AUTO_TIMESTAMP_COLS&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt; [sic], can't happen anymore. There's no limit to how many columns with &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt; you can have. Knock yourself out.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align:left;&quot;&gt;    &lt;span style=&quot;font-family:Georgia, serif;&quot;&gt;When is &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;NOW()&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt;?&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt;In MySQL, &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, serif;&quot;&gt; is defined as the query start time, the time when the query arrives at the server. This means that the value is constant during the execution of the query. So don't be surprised if you update a million rows of type &lt;/span&gt;&lt;span style=&quot;font-family:'Courier New', Courier, monospace;&quot;&gt;DATETIME(6) ON UPDATE NOW(6)&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, 'Times New Roman', serif;&quot;&gt;&amp;nbsp;- microsecond precision - and they all get the same update time&lt;/span&gt;&lt;span style=&quot;font-family:Georgia, 'Times New Roman', serif;&quot;&gt;.&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family:Georgia, serif;&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</description>
         <author>Martin</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-4934478127523643312.post-4384652683490254268</guid>
         <pubDate>Tue, 10 Apr 2012 06:04:00 +0000</pubDate>
      </item>
      <item>
         <title>Index Condition Pushdown to the rescue!</title>
         <link>http://jorgenloland.blogspot.com/2012/03/index-condition-pushdown-to-rescue.html</link>
         <description>A while ago, I &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://jorgenloland.blogspot.com/2011/08/mysql-range-access-method-explained.html&quot;&gt;explained&lt;/a&gt; how range access in a multiple-part index works and why MySQL can&amp;#39;t utilize key parts beyond the first occurrence of some often used comparison operators. Luckily, there is a great improvement underway in MySQL 5.6 that will remedy much of this limitation. Meet Index Condition Pushdown.&lt;br&gt;&lt;br&gt;&lt;b&gt;How does ICP work? &lt;/b&gt;&lt;br&gt;&lt;br&gt;Index Condition Pushdown is a new way for MySQL to evaluate conditions. Instead of evaluating conditions on rows read from a table, ICP makes it possible to evaluate conditions in the index and thereby avoid looking at the table if the condition is false.&lt;br&gt;&lt;br&gt;Let&amp;#39;s assume that we have a multiple-part index covering columns &lt;span style=&quot;font-size:x-small;&quot;&gt;(keypart_1, ..., keypart_n)&lt;/span&gt;. Further assume that we have a condition with a comparison operator on &lt;span style=&quot;font-size:x-small;&quot;&gt;keypart_1&lt;/span&gt; that does not allow &lt;span style=&quot;font-size:x-small;&quot;&gt;keypart_2,...,keypart_n&lt;/span&gt; to be used as a range condition (more on that &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://jorgenloland.blogspot.com/2011/08/mysql-range-access-method-explained.html&quot;&gt;here&lt;/a&gt;).&lt;br&gt;&lt;br&gt;When MySQL does range access without ICP enabled, the index is traversed to locate rows in the table that are within the range(s) relevant for the conditions on &lt;span style=&quot;font-size:x-small;&quot;&gt;keypart_1&lt;/span&gt;. The rows in these ranges are read from the table and returned from the storage engine to the MySQL server. The server then evaluates the remaining conditions, including those that apply to &lt;span style=&quot;font-size:x-small;&quot;&gt;keypart_2,..,keypart_n&lt;/span&gt;.&lt;br&gt;&lt;br&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://jorgenloland.blogspot.com/2012/03/index-condition-pushdown-to-rescue.html#more&quot;&gt;Read more »&lt;/a&gt;</description>
         <author>Jørgen Løland</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-7171477457129187873.post-5804563285401580241</guid>
         <pubDate>Mon, 19 Mar 2012 13:22:00 +0000</pubDate>
         <media:thumbnail height="72" url="http://3.bp.blogspot.com/-WjYPEu6AxXE/T2NcMTuZI4I/AAAAAAAAACU/VOODlxzjfCs/s72-c/icp-illustration.jpg" width="72" xmlns:media="http://search.yahoo.com/mrss/"/>
      </item>
      <item>
         <title>Optimizer tracing used by others!</title>
         <link>http://guilhembichot.blogspot.com/2012/02/in-previous-post-i-had-explained-how-to.html</link>
         <description>In a previous post, I had explained &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://guilhembichot.blogspot.com/2011/09/optimizer-tracing-how-to-configure-it.html&quot;&gt;how to use MySQL's optimizer tracing&lt;/a&gt;, a new feature which appeared in MySQL 5.6.3.&lt;br /&gt;&lt;br /&gt;As a developer, it feels really good to see others adopt my work and make something useful out of it! My colleague Dimitri Kravtchuk, who is one of our top Benchmarking experts, has written a blog post where he shows how the optimizer tracing has helped him to figure out why, under load, once in a while and randomly, a query performed badly. His investigation technique may be reusable by other people, so I encourage you to read more about it, &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dimitrik.free.fr/blog/archives/2012/01/mysql-performance-overhead-of-optimizer-tracing-in-mysql-56.html&quot;&gt;here&lt;/a&gt;.</description>
         <author>Guilhem Bichot</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-9191231274999121063.post-6442720126303134298</guid>
         <pubDate>Thu, 09 Feb 2012 07:51:00 +0000</pubDate>
      </item>
      <item>
         <title>Understanding the unique_subquery optimization</title>
         <link>http://guilhembichot.blogspot.com/2011/11/understanding-uniquesubquery.html</link>
         <description>If you use the EXPLAIN SELECT statement to see how your subqueries are treated by MySQL, you may sometimes meet the &quot;unique_subquery&quot; optimization. Here is how the &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.5/en/explain-output.html&quot;&gt;manual&lt;/a&gt; describes it:&lt;br /&gt;&lt;blockquote class=&quot;tr_bq&quot;&gt;&quot;&lt;i&gt;unique_subquery&lt;/i&gt;: this type replaces &lt;i&gt;ref&lt;/i&gt; for some IN subqueries of the following form: &lt;i&gt;value IN (SELECT primary_key FROM single_table WHERE some_expr); &lt;/i&gt;unique_subquery is just an index lookup function that replaces the subquery completely for better efficiency&quot;.&lt;/blockquote&gt;Few weeks ago, while I was reviewing a patch fixing a &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://bazaar.launchpad.net/%7Emysql/mysql-server/trunk/revision/jorgen.loland@oracle.com-20110929124732-t0s1x214cu3nkngu&quot;&gt;bug&lt;/a&gt; in unique_subquery, I got a &quot;simplification&quot; pulsion. I told myself that:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&amp;nbsp;unique_subquery is an optimization for a special case of simple subqueries (single inner table, using index, no aggregates);&lt;/li&gt;&lt;li&gt;we have a more general system around, used for more complex subqueries, naturally capable of handling simple ones too if we wanted;&lt;/li&gt;&lt;li&gt;this general system does not have the bug in question...&lt;/li&gt;&lt;/ul&gt;Then I wondered: what if we removed the unique_subquery optimization, and let the general system handle this simple subquery? This would certainly simplify code, and thus maintainance...But before removing it, of course, we should check whether unique_subquery brings a significant performance benefit. &lt;br /&gt;&lt;br /&gt;So today I'm testing unique_subquery against the DBT3 benchmark. I grab a copy of MySQL 5.6.3, and focus on the sixteenth query of DBT3, which contains a subquery (in red) suitable for handling by unique_subquery:&lt;br /&gt;&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;select&lt;br /&gt; p_brand,&lt;br /&gt; p_type,&lt;br /&gt; p_size,&lt;br /&gt; count(distinct ps_suppkey) as supplier_cnt&lt;br /&gt;from&lt;br /&gt; partsupp,&lt;br /&gt; part&lt;br /&gt;where&lt;br /&gt; p_partkey = ps_partkey&lt;br /&gt; and p_brand &amp;lt;&amp;gt; 'Brand#23'&lt;br /&gt; and p_type not like 'LARGE PLATED%'&lt;br /&gt; and p_size in (43, 1, 25, 5, 35, 12, 42, 40)&lt;br /&gt; and &lt;span style=&quot;color:red;&quot;&gt;ps_suppkey not in (&lt;br /&gt;  select&lt;br /&gt;   s_suppkey&lt;br /&gt;  from&lt;br /&gt;   supplier&lt;br /&gt;  where&lt;br /&gt;   s_comment like '%Customer%Complaints%'&lt;br /&gt; )&lt;/span&gt;&lt;br /&gt;group by&lt;br /&gt; p_brand,&lt;br /&gt; p_type,&lt;br /&gt; p_size&lt;br /&gt;order by&lt;br /&gt; supplier_cnt desc,&lt;br /&gt; p_brand,&lt;br /&gt; p_type,&lt;br /&gt; p_size;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;This query executes in 0.65 seconds on my Linux box, and EXPLAIN is:&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;+----+--------------------+----------+-----------------+----------------------+--------------+---------+---------------------+--------+----------------------------------------------+&lt;br /&gt;| id | select_type&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | table&amp;nbsp;&amp;nbsp;&amp;nbsp; | type&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | possible_keys&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | key&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | key_len | ref&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | rows&amp;nbsp;&amp;nbsp; | Extra&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;br /&gt;+----+--------------------+----------+-----------------+----------------------+--------------+---------+---------------------+--------+----------------------------------------------+&lt;br /&gt;|&amp;nbsp; 1 | PRIMARY&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | part&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | ALL&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | PRIMARY&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | NULL&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | NULL&amp;nbsp;&amp;nbsp;&amp;nbsp; | NULL&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | 199498 | Using where; Using temporary; Using filesort |&lt;br /&gt;|&amp;nbsp; 1 | PRIMARY&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | partsupp | ref&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | PRIMARY,i_ps_partkey | i_ps_partkey | 4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | dbt3.part.p_partkey |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2 | Using where; Using index&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;br /&gt;|&amp;nbsp; 2 | DEPENDENT SUBQUERY | supplier | &lt;span style=&quot;color:red;&quot;&gt;unique_subquery&lt;/span&gt; | PRIMARY&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | PRIMARY&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | 4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | func&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1 | Using where&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;br /&gt;+----+--------------------+----------+-----------------+----------------------+--------------+---------+---------------------+--------+----------------------------------------------+&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;When I disable unique_subquery (by modifying MySQL's C++ code), EXPLAIN becomes:&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;+----+--------------------+----------+--------+----------------------+--------------+---------+---------------------+--------+----------------------------------------------+&lt;br /&gt;| id | select_type&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | table&amp;nbsp;&amp;nbsp;&amp;nbsp; | type&amp;nbsp;&amp;nbsp; | possible_keys&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | key&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | key_len | ref&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | rows&amp;nbsp;&amp;nbsp; | Extra&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;br /&gt;+----+--------------------+----------+--------+----------------------+--------------+---------+---------------------+--------+----------------------------------------------+&lt;br /&gt;|&amp;nbsp; 1 | PRIMARY&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | part&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | ALL&amp;nbsp;&amp;nbsp;&amp;nbsp; | PRIMARY&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | NULL&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | NULL&amp;nbsp;&amp;nbsp;&amp;nbsp; | NULL&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | 199498 | Using where; Using temporary; Using filesort |&lt;br /&gt;|&amp;nbsp; 1 | PRIMARY&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | partsupp | ref&amp;nbsp;&amp;nbsp;&amp;nbsp; | PRIMARY,i_ps_partkey | i_ps_partkey | 4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | dbt3.part.p_partkey |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2 | Using where; Using index&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;br /&gt;|&amp;nbsp; 2 | DEPENDENT SUBQUERY | supplier | &lt;span style=&quot;color:red;&quot;&gt;eq_ref&lt;/span&gt; | PRIMARY&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | PRIMARY&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | 4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | func&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1 | Using where&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;br /&gt;+----+--------------------+----------+--------+----------------------+--------------+---------+---------------------+--------+----------------------------------------------+&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;The only change, as expected, is that &quot;unique_subquery&quot; becomes &quot;eq_ref&quot;. The used index is the same (the primary key of the &quot;supplier&quot; table). The optimizer has the same notion of unicity: &quot;unique_subquery&quot; and &quot;eq_ref&quot; both denote that a single lookup is needed, as the index is UNIQUE. Same index, same number of lookups: execution could well be as fast with &quot;eq_ref&quot; as it was with &quot;unique_subquery&quot;.&lt;br /&gt;But... no. Query now executes in 0.80 seconds. &lt;i&gt;23% slower&lt;/i&gt; than with unique_subquery!&lt;br /&gt;&lt;br /&gt;Finer-grained timing shows that the extra 0.15 seconds are indeed lost in the subquery evaluation code.&lt;br /&gt;&lt;br /&gt;To understand this, let's follow the execution in detail, based on EXPLAIN output above.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;First line of EXPLAIN output: we do a table scan on the &quot;part&quot; table (&quot;type=ALL&quot; means &quot;table scan&quot;) . The &quot;rows&quot; column of EXPLAIN suggests that we are going to have 199,498 rows of &quot;part&quot;.&lt;/li&gt;&lt;li&gt;Second line of EXPLAIN output: for each row from the &quot;part&quot; table, we do an index lookup (&quot;ref&quot;) into the &quot;i_ps_partkey&quot; index of the &quot;partsupp&quot; table; apparently such lookup will find two rows (&quot;rows=2&quot;).&lt;/li&gt;&lt;li&gt;At this point, we have a row made of needed columns of &quot;part&quot; and of &quot;partsupp&quot;. An upper estimate of the number of those rows is 199,498 multiplied by 2: 400,000. Actually, the real number is around 120,000 (there has been filtering going on, as the &quot;Using where&quot; indicates).&lt;/li&gt;&lt;li&gt;Then we evaluate the &lt;i&gt;WHERE&lt;/i&gt; clause and thus the &quot;&lt;i&gt;NOT IN (subquery)&lt;/i&gt;&quot; predicate (the &quot;DEPENDENT SUBQUERY&quot;). 120,000 evaluations of such predicate. And that's where the difference is.&lt;/li&gt;&lt;/ul&gt;EXPLAIN EXTENDED and then SHOW WARNINGS show how the predicate&lt;br /&gt;looks like. Let's start with the case where unique_subquery is disabled:&lt;br /&gt;&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;/* select#1 */ select `dbt3`.`part`.`p_brand` AS `p_brand`,`dbt3`.`part`.`p_type` AS `p_type`,`dbt3`.`part`.`p_size` AS `p_size`,count(distinct `dbt3`.`partsupp`.`ps_suppkey`) AS `supplier_cnt` from `dbt3`.`partsupp` join `dbt3`.`part` where ((`dbt3`.`partsupp`.`ps_partkey` = `dbt3`.`part`.`p_partkey`) and (`dbt3`.`part`.`p_brand` &amp;lt;&amp;gt; 'Brand#23') and (not((`dbt3`.`part`.`p_type` like 'LARGE PLATED%'))) and (`dbt3`.`part`.`p_size` in (43,1,25,5,35,12,42,40)) and (not&lt;span style=&quot;color:red;&quot;&gt;(&amp;lt;in_optimizer&amp;gt;(`dbt3`.`partsupp`.`ps_suppkey`,&amp;lt;exists&amp;gt;(/* select#2 */ select 1 from `dbt3`.`supplier` where ((`dbt3`.`supplier`.`s_comment` like '%Customer%Complaints%') and (&amp;lt;cache&amp;gt;(`dbt3`.`partsupp`.`ps_suppkey`) = `dbt3`.`supplier`.`s_suppkey`)))))&lt;/span&gt;)) group by `dbt3`.`part`.`p_brand`,`dbt3`.`part`.`p_type`,`dbt3`.`part`.`p_size` order by count(distinct `dbt3`.`partsupp`.`ps_suppkey`) desc,`dbt3`.`part`.`p_brand`,`dbt3`.`part`.`p_type`,`dbt3`.`part`.`p_size`&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;Above, the part in red says that&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;ps_suppkey not in (&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; select&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; s_suppkey&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; from&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; supplier&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; where&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; s_comment like '%Customer%Complaints%'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; )&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;has been transformed from &quot;IN(non correlated subquery)&quot; to &quot;EXISTS(correlated subquery)&quot;, yielding this:&lt;br /&gt;&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;not exists (&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; select&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; 1&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; from&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; supplier&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; where&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; s_comment like '%Customer%Complaints%'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; AND s_suppkey = ps_suppkey&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; )&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;or, more exactly (leaving out the NOT operator, for brevity):&lt;br /&gt;&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;&amp;lt;exists&amp;gt;(/* select#2 */ select 1 from `dbt3`.`supplier`&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; where ((`dbt3`.`supplier`.`s_comment` like '%Customer%Complaints%')&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; and (&amp;lt;cache&amp;gt;(`dbt3`.`partsupp`.`ps_suppkey`) = `dbt3`.`supplier`.`s_suppkey`)))&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;Evaluating this EXISTS() evaluates the new subquery. This means all the subquery evaluation machinery: calls to JOIN::exec(), sub_select(), evaluate_join_record()... Sure, deep down it does an index lookup like unique_subquery does, but all those function calls have a cost, and so has all the logic which is lying around ready to handle any complexity in the subquery, as this is generic subquery evaluation code (&quot;if group_by do this&quot;, &quot;if order_by do this&quot;, &quot;if left_join do this&quot;: none of those if()s are entered, but deciding to enter them or not has a cost). Plus some initialization code. Plus some de-initialization code. This overhead, repeated 120,000 times, amounts to 0.15 seconds...&lt;br /&gt;&lt;br /&gt;Now, EXPLAIN EXTENDED when unique_subquery is enabled:&lt;br /&gt;&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;/* select#1 */ select `dbt3`.`part`.`p_brand` AS `p_brand`,`dbt3`.`part`.`p_type` AS `p_type`,`dbt3`.`part`.`p_size` AS `p_size`,count(distinct `dbt3`.`partsupp`.`ps_suppkey`) AS `supplier_cnt` from `dbt3`.`partsupp` join `dbt3`.`part` where ((`dbt3`.`partsupp`.`ps_partkey` = `dbt3`.`part`.`p_partkey`) and (`dbt3`.`part`.`p_brand` &amp;lt;&amp;gt; 'Brand#23') and (not((`dbt3`.`part`.`p_type` like 'LARGE PLATED%'))) and (`dbt3`.`part`.`p_size` in (43,1,25,5,35,12,42,40)) and (not&lt;span style=&quot;color:red;&quot;&gt;(&amp;lt;in_optimizer&amp;gt;(`dbt3`.`partsupp`.`ps_suppkey`,&amp;lt;exists&amp;gt;(&amp;lt;primary_index_lookup&amp;gt;(&amp;lt;cache&amp;gt;(`dbt3`.`partsupp`.`ps_suppkey`) in supplier on PRIMARY where ((`dbt3`.`supplier`.`s_comment` like '%Customer%Complaints%') and (&amp;lt;cache&amp;gt;(`dbt3`.`partsupp`.`ps_suppkey`) = `dbt3`.`supplier`.`s_suppkey`))))))&lt;/span&gt;)) group by `dbt3`.`part`.`p_brand`,`dbt3`.`part`.`p_type`,`dbt3`.`part`.`p_size` order by count(distinct `dbt3`.`partsupp`.`ps_suppkey`) desc,`dbt3`.`part`.`p_brand`,`dbt3`.`part`.`p_type`,`dbt3`.`part`.`p_size`&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;The optimizer has first done the same transformation (IN to EXISTS) as we saw before, then has done one more transformation, and EXISTS has become, as written in red above:&lt;br /&gt;&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;&amp;lt;exists&amp;gt;(&amp;lt;primary_index_lookup&amp;gt;(&amp;lt;cache&amp;gt;(`dbt3`.`partsupp`.`ps_suppkey`)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; in supplier on PRIMARY&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; where ((`dbt3`.`supplier`.`s_comment` like '%Customer%Complaints%')&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; and (&amp;lt;cache&amp;gt;(`dbt3`.`partsupp`.`ps_suppkey`) = `dbt3`.`supplier`.`s_suppkey`))))&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;which is &lt;i&gt;directly&lt;/i&gt; an index lookup (&quot;&amp;lt;primary_index_lookup&amp;gt;&quot;), followed by an additional WHERE clause. So the overhead of full-blown subquery evaluation is&lt;br /&gt;avoided. And this overhead is not neglectable, compared to the index lookup (assuming the relevant index pages are already in memory).&lt;br /&gt;&lt;br /&gt;So the conclusion of my experiment is that unique_subquery is worth having. I'll have to direct simplification pulsions to some other code!&lt;br /&gt;&lt;br /&gt;Note that there also exists a similar &quot;index_subquery&quot; optimization applying to non-unique indices. And it's worth having, for the same reasons.</description>
         <author>Guilhem Bichot</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-9191231274999121063.post-4377293817366385411</guid>
         <pubDate>Tue, 29 Nov 2011 06:37:00 +0000</pubDate>
      </item>
      <item>
         <title>Optimizer tracing: how to configure it</title>
         <link>http://guilhembichot.blogspot.com/2011/09/optimizer-tracing-how-to-configure-it.html</link>
         <description>In this &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://jorgenloland.blogspot.com/2011/10/optimizer-tracing-query-execution-plan.html&quot;&gt;blog post&lt;/a&gt;, my colleague Jørgen Løland described a new feature of MySQL 5.6: &lt;i&gt;Optimizer Tracing&lt;/i&gt;. I recommend reading his article, as it presents this new feature in a simple, easy-to-read manner.&lt;br /&gt;&lt;br /&gt;The Optimizer Tracing feature can help understanding what the Optimizer is doing; it is available since milestone 5.6.3, announced October 3rd at Oracle Open World  (here is the &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.6/en/news-5-6-3.html&quot;&gt;changelog&lt;/a&gt;). It's good to see it mature now; I remember that Sergey Petrunia did the first prototype back in March 2009!&lt;br /&gt;&lt;br /&gt;Today&amp;nbsp; I will be giving some must-have tips related to handling &lt;b&gt;big&lt;/b&gt; traces.&lt;br /&gt;&lt;br /&gt;First thing to know, a trace lives in main memory (internally it is allocated on the &lt;i&gt;heap&lt;/i&gt; or &lt;i&gt;free store&lt;/i&gt; of the MySQL Server). An SQL statement which gives the optimizer a lot of work (for example, by joining many tables) will generate a large trace. Up to gigabytes in some pathological cases! To avoid hogging memory then, a trace will never grow beyond the value of the &lt;i&gt;@@optimizer_trace_max_mem_size&lt;/i&gt; session variable. Which has a default of 16 kilobytes; yes, it's a low value, to protect the innocent. I often raise it in my session, to 1 megabyte, which is enough for most queries in my daily work:&lt;br /&gt;&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;mysql&amp;gt; SET OPTIMIZER_TRACE_MAX_MEM_SIZE=1000000;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;If a trace was truncated because exceeding this limit, the column &lt;i&gt;INFORMATION_SCHEMA.OPTIMIZER_TRACE.MISSING_BYTES_BEYOND_MAX_MEM_SIZE &lt;/i&gt;shows a non-zero value: the number of bytes which could not be added to the trace.&lt;br /&gt;&lt;br /&gt;When I read a trace I like to scroll up and down in it, search for some optimization phase (that means searching for some keyword in the trace); to do this, I set, in the &quot;&lt;i&gt;mysql&lt;/i&gt;&quot; command-line client, the pager to &quot;&lt;i&gt;less&lt;/i&gt;&quot; (I'm using Unix):&lt;br /&gt;&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;mysql&amp;gt; pager less;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;This is very useful. I have all &quot;&lt;i&gt;less&lt;/i&gt;&quot; commands under hand: can save to a file, can search for a regular expression match, search forward or backward... One last benefit is that when I have finished reading, quitting &quot;&lt;i&gt;less&lt;/i&gt;&quot; makes the trace go away from the terminal, it does not linger on my screen forever, does not occupy the terminal's display history...&lt;br /&gt;&lt;br /&gt;When there are many tables to join, as I said above the trace can grow a lot, because the Optimizer evaluates many possible join orders (using an algorithm known as &quot;&lt;i&gt;greedy search&quot;&lt;/i&gt;): each evaluated join order is mentioned in the trace. Greedy search ends up being the greatest part of the trace. What if I want to see the trace of what happens in the Optimizer &lt;i&gt;after&lt;/i&gt; greedy search is complete? If I set &lt;i&gt;@@optimizer_trace_max_mem_size&lt;/i&gt; to a low value, it will trim greedy search and what follows. If I set &lt;i&gt;@@optimizer_trace_max_mem_size&lt;/i&gt; to a high value, to see what I want, greedy search will be traced which will possibly exceed the amount of memory I can afford on this machine... It would be nice if I could tell the system: &quot;please do not trace this and that&quot;. In my case, it would be &quot;please do not trace greedy search&quot;.&lt;br /&gt;&lt;br /&gt;As an example, let's consider twenty tables, t1...t20, all similar to this one:&lt;br /&gt;&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;mysql&amp;gt; CREATE TABLE t1 (a INT, INDEX(a)) ENGINE=MYISAM;&lt;br /&gt;mysql&amp;gt; INSERT INTO t1 VALUES(x),(y); # x and y being some integers&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;and let's run this query, after turning tracing on:&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;mysql&amp;gt; SET OPTIMIZER_TRACE=&quot;ENABLED=ON,END_MARKER=ON&quot;;&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;mysql&amp;gt; EXPLAIN SELECT 1 FROM t1  JOIN t2 ON t2.a=t1.a JOIN t3 ON t3.a=t2.a JOIN t4 ON t4.a=t3.a JOIN t5 ON t5.a=t4.a JOIN t6 ON t6.a=t5.a JOIN t7 ON t7.a=t6.a JOIN t8 ON t8.a=t7.a JOIN t9 ON t9.a=t8.a JOIN t10 ON t10.a=t9.a JOIN t11 ON t11.a=t10.a JOIN t12 ON t12.a=t11.a JOIN t13 ON t13.a=t12.a JOIN t14 ON t14.a=t13.a JOIN t15 ON t15.a=t14.a JOIN t16 ON t16.a=t15.a JOIN t17 ON t17.a=t16.a JOIN t18 ON t18.a=t17.a JOIN t19 ON t19.a=t18.a JOIN t20 ON t20.a=t19.a;&lt;br /&gt;+----+-------------+-------+-------+---------------+------+---------+------------+------+--------------------------+&lt;br /&gt;| id | select_type | table | type  | possible_keys | key  | key_len | ref        | rows | Extra                    |&lt;br /&gt;+----+-------------+-------+-------+---------------+------+---------+------------+------+--------------------------+&lt;br /&gt;|  1 | SIMPLE      | t1    | index | a             | a    | 5       | NULL       |    2 | Using where; Using index |&lt;br /&gt;|  1 | SIMPLE      | t2    | ref   | a             | a    | 5       | test.t1.a  |    1 | Using where; Using index |&lt;br /&gt;|  1 | SIMPLE      | t3    | ref   | a             | a    | 5       | test.t2.a  |    1 | Using where; Using index |&lt;br /&gt;|  1 | SIMPLE      | t4    | ref   | a             | a    | 5       | test.t1.a  |    1 | Using index              |&lt;br /&gt;|  1 | SIMPLE      | t5    | ref   | a             | a    | 5       | test.t1.a  |    1 | Using index              |&lt;br /&gt;|  1 | SIMPLE      | t6    | ref   | a             | a    | 5       | test.t1.a  |    1 | Using index              |&lt;br /&gt;|  1 | SIMPLE      | t7    | ref   | a             | a    | 5       | test.t1.a  |    1 | Using index              |&lt;br /&gt;|  1 | SIMPLE      | t8    | ref   | a             | a    | 5       | test.t1.a  |    1 | Using index              |&lt;br /&gt;|  1 | SIMPLE      | t9    | ref   | a             | a    | 5       | test.t1.a  |    1 | Using where; Using index |&lt;br /&gt;|  1 | SIMPLE      | t10   | ref   | a             | a    | 5       | test.t9.a  |    1 | Using where; Using index |&lt;br /&gt;|  1 | SIMPLE      | t11   | ref   | a             | a    | 5       | test.t1.a  |    1 | Using where; Using index |&lt;br /&gt;|  1 | SIMPLE      | t12   | ref   | a             | a    | 5       | test.t11.a |    1 | Using where; Using index |&lt;br /&gt;|  1 | SIMPLE      | t13   | ref   | a             | a    | 5       | test.t12.a |    1 | Using where; Using index |&lt;br /&gt;|  1 | SIMPLE      | t14   | ref   | a             | a    | 5       | test.t12.a |    1 | Using where; Using index |&lt;br /&gt;|  1 | SIMPLE      | t15   | ref   | a             | a    | 5       | test.t12.a |    1 | Using where; Using index |&lt;br /&gt;|  1 | SIMPLE      | t16   | ref   | a             | a    | 5       | test.t12.a |    1 | Using where; Using index |&lt;br /&gt;|  1 | SIMPLE      | t17   | ref   | a             | a    | 5       | test.t12.a |    1 | Using where; Using index |&lt;br /&gt;|  1 | SIMPLE      | t18   | ref   | a             | a    | 5       | test.t11.a |    1 | Using where; Using index |&lt;br /&gt;|  1 | SIMPLE      | t19   | ref   | a             | a    | 5       | test.t12.a |    1 | Using where; Using index |&lt;br /&gt;|  1 | SIMPLE      | t20   | ref   | a             | a    | 5       | test.t11.a |    1 | Using where; Using index |&lt;br /&gt;+----+-------------+-------+-------+---------------+------+---------+------------+------+--------------------------+&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;As optimizer developer, it catches my eye that some tables have &quot;&lt;i&gt;Using where&lt;/i&gt;&quot; and others don't. &quot;&lt;i&gt;Using where&lt;/i&gt;&quot; tells me that a condition is evaluated after fetching a row from the table, but does not tell me &lt;b&gt;what&lt;/b&gt; condition. To know more, I will look at the trace. But I'm not interested in the trace of greedy search, which likely accounts, in this 20-table example, for most of the total trace, and would just make my reading less comfortable, or even hog memory, if I had more than those 20 tables or more indices on them, or more complex conditions. So I do:&lt;br /&gt;&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;mysql&amp;gt; SET OPTIMIZER_TRACE_FEATURES=&quot;GREEDY_SEARCH=OFF&quot;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;and run the &lt;i&gt;EXPLAIN&lt;/i&gt; again. To show the resulting trace, I'm posting a screenshot of how it is displaid by the &lt;i&gt;JsonView&lt;/i&gt; Firefox add-on. Using this add-on requires taking two precautions:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;turning off the &lt;i&gt;end_marker&lt;/i&gt; flag &lt;i&gt;of @@optimizer_trace &lt;/i&gt;(this flag is good for human-readability but is not JSON-compliant)&lt;/li&gt;&lt;li&gt; sending the trace to a file without escaping of newlines (which is why I use &lt;i&gt;INTO DUMPFILE&lt;/i&gt; instead of &lt;i&gt;INTO OUTFILE&lt;/i&gt;):&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;overflow:auto;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;mysql&amp;gt; SET OPTIMIZER_TRACE=&quot;END_MARKER=OFF&quot;;&lt;br /&gt;mysql&amp;gt; SELECT TRACE INTO DUMPFILE &quot;/tmp/trace.json&quot; FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;Here's the trace now in Firefox:&lt;br /&gt;&lt;div class=&quot;separator&quot; style=&quot;clear:both;text-align:center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear:both;text-align:center;&quot;&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://1.bp.blogspot.com/-4nRBJC6RyRQ/Tom7NqxGISI/AAAAAAAAAAQ/_BY0hDJep5k/s1600/capt2.png&quot; style=&quot;margin-left:1em;margin-right:1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;640&quot; src=&quot;http://1.bp.blogspot.com/-4nRBJC6RyRQ/Tom7NqxGISI/AAAAAAAAAAQ/_BY0hDJep5k/s640/capt2.png&quot; width=&quot;491&quot;/&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;We see how &lt;i&gt;greedy_search=off&lt;/i&gt; has eliminated the trace of greedy search (&lt;i&gt;&quot;considered_execution_plans&quot;&lt;/i&gt;), replacing it with just an ellipsis (&quot;&lt;i&gt;...&lt;/i&gt;&quot;)! Then there is what I wanted to see: the part after greedy search, named &quot;&lt;i&gt;attached_conditions_summary&quot;&lt;/i&gt;, which describes what condition is behind each &quot;&lt;i&gt;Using where&quot;&lt;/i&gt; in &lt;i&gt;EXPLAIN.&lt;/i&gt; There are equality conditions of course. Some coming directly from the &lt;i&gt;&quot;JOIN ON&lt;/i&gt;&quot; conditions. Some deduced by &lt;i&gt;equality propagation&lt;/i&gt; (if &lt;i&gt;t1.a=t2.a&lt;/i&gt; and &lt;i&gt;t2.a=t3.a&lt;/i&gt; then &lt;i&gt;t1.a=t3.a&lt;/i&gt;, a new condition which we see is attached to t3). There are also &lt;i&gt;&quot;IS NOT NULL&lt;/i&gt;&quot; conditions; indeed, an equality condition of the form &lt;i&gt;t1.a=t2.a&lt;/i&gt; allows us to deduce that both &lt;i&gt;t1.a &lt;/i&gt;and&lt;i&gt; t2.a &lt;/i&gt;are not NULL, so if rows of t1 are retrieved first they can be filtered with this deduced condition, known as &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://forge.mysql.com/wiki/MySQL_Internals_Optimizer&quot;&gt;early NULL filtering&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;There are other flags in &lt;i&gt;@@optimizer_trace_features&lt;/i&gt;; we added a flag for each feature which we think can make the trace grow unreasonably large:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;greedy search&lt;/li&gt;&lt;li&gt;repeated execution of subqueries (one execution per row) &lt;/li&gt;&lt;li&gt;repeated range optimization (known as &lt;i&gt;&quot;range checked for each record&quot;&lt;/i&gt; in EXPLAIN)&lt;/li&gt;&lt;li&gt;range optimization in general.&lt;/li&gt;&lt;/ul&gt;I think I have shown enough for today. A positive note to finish: in the Optimizer team, this tracing has already helped us to debug some problems, so has started to fulfill its goal!&lt;br /&gt;&lt;br /&gt;For the curious: the Optimizer Trace is covered in full detail, including the above, and more, in a &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://forge.mysql.com/wiki/MySQL_Internals_Optimizer_tracing&quot;&gt;chapter of the &quot;MySQL Internals&quot; manual&lt;/a&gt;.</description>
         <author>Guilhem Bichot</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-9191231274999121063.post-1139226807338979871</guid>
         <pubDate>Tue, 04 Oct 2011 15:40:00 +0000</pubDate>
         <media:thumbnail height="72" url="http://1.bp.blogspot.com/-4nRBJC6RyRQ/Tom7NqxGISI/AAAAAAAAAAQ/_BY0hDJep5k/s72-c/capt2.png" width="72" xmlns:media="http://search.yahoo.com/mrss/"/>
      </item>
      <item>
         <title>&lt;div class=&quot;_post&quot;&gt;&lt;h1&gt;Optimizer: new EXPLAIN for&amp;hellip;</title>
         <link>http://glebshchepa.blogspot.com/2011/10/optimizer-new-explain-for-data.html</link>
         <description>&lt;div class=&quot;_post&quot;&gt;&lt;h1&gt;Optimizer: new EXPLAIN for data-modifiers&lt;br /&gt;&lt;/h1&gt;&lt;br /&gt;&lt;p&gt;One of the new features of &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.6/en/news-5-6-3.html&quot;&gt;MySQL 5.6.3&lt;/a&gt; is EXPLAIN command for data-modifying statements.&lt;br /&gt;As it's obvious from its name, it outputs QEP (query execution plan) for INSERT, REPLACE, UPDATE and DELETE like old good one does for SELECT:&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;example:&lt;/h3&gt;&lt;div class=&quot;_code&quot;&gt;&lt;pre&gt;mysql&amp;gt; EXPLAIN UPDATE t1 SET b = 'b' WHERE a &amp;gt; 2;&lt;br /&gt;+----+-------------+-------+-------+---------------+------+---------+------+------+------------------------------+&lt;br /&gt;| id | select_type | table | type  | possible_keys | key  | key_len | ref  | rows | Extra                        |&lt;br /&gt;+----+-------------+-------+-------+---------------+------+---------+------+------+------------------------------+&lt;br /&gt;|  1 | SIMPLE      | t1    | range | a,a_2         | a    | 16      | NULL |    2 | Using where; Using temporary |&lt;br /&gt;+----+-------------+-------+-------+---------------+------+---------+------+------+------------------------------+&lt;br /&gt;1 row in set (0.00 sec)&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;All kinds of MySQL syntax are supported:&lt;/p&gt;&lt;h3&gt;INSERT from SELECT:&lt;/h3&gt;&lt;div class=&quot;_code&quot;&gt;&lt;pre&gt;mysql&amp;gt; EXPLAIN INSERT INTO t1 SELECT * FROM t2 WHERE a IN (1, 3, 5);&lt;br /&gt;+----+-------------+-------+-------+---------------+------+---------+------+------+-----------------------+&lt;br /&gt;| id | select_type | table | type  | possible_keys | key  | key_len | ref  | rows | Extra                 |&lt;br /&gt;+----+-------------+-------+-------+---------------+------+---------+------+------+-----------------------+&lt;br /&gt;|  1 | SIMPLE      | t2    | range | t2i1          | t2i1 | 4       | NULL |    3 | Using index condition |&lt;br /&gt;+----+-------------+-------+-------+---------------+------+---------+------+------+-----------------------+&lt;br /&gt;1 row in set (0.00 sec)&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;EXPLAIN for INSERT ... VALUES works as a special case of the INSERT ... SELECT VALUES FROM DUAL:&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;trivial EXPLAIN INSERT ... VALUES:&lt;/h3&gt;&lt;div class=&quot;_code&quot;&gt;&lt;pre&gt;mysql&amp;gt; EXPLAIN INSERT INTO t1 VALUES (1, 2, 3);&lt;br /&gt;+----+-------------+-------+------+---------------+------+---------+------+------+----------------+&lt;br /&gt;| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra          |&lt;br /&gt;+----+-------------+-------+------+---------------+------+---------+------+------+----------------+&lt;br /&gt;|  1 | SIMPLE      | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | No tables used |&lt;br /&gt;+----+-------------+-------+------+---------------+------+---------+------+------+----------------+&lt;br /&gt;1 row in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; EXPLAIN INSERT INTO t1 SELECT 1, 2, 3 FROM DUAL;&lt;br /&gt;+----+-------------+-------+------+---------------+------+---------+------+------+----------------+&lt;br /&gt;| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra          |&lt;br /&gt;+----+-------------+-------+------+---------------+------+---------+------+------+----------------+&lt;br /&gt;|  1 | SIMPLE      | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | No tables used |&lt;br /&gt;+----+-------------+-------+------+---------------+------+---------+------+------+----------------+&lt;br /&gt;1 row in set (0.00 sec)&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p/&gt;&lt;h3&gt;multi-table UPDATE:&lt;/h3&gt;&lt;div class=&quot;_code&quot;&gt;&lt;pre&gt;mysql&amp;gt; EXPLAIN UPDATE t1, t2 SET t1.b = t2.b WHERE t1.a = t2.a;&lt;br /&gt;+----+-------------+-------+------+---------------+------+---------+-----------+------+-------+&lt;br /&gt;| id | select_type | table | type | possible_keys | key  | key_len | ref       | rows | Extra |&lt;br /&gt;+----+-------------+-------+------+---------------+------+---------+-----------+------+-------+&lt;br /&gt;|  1 | SIMPLE      | t1    | ALL  | PRIMARY       | NULL | NULL    | NULL      | 3072 |       |&lt;br /&gt;|  1 | SIMPLE      | t2    | ref  | t2i1          | t2i1 | 4       | test.t1.a |    1 |       |&lt;br /&gt;+----+-------------+-------+------+---------------+------+---------+-----------+------+-------+&lt;br /&gt;2 rows in set (0.00 sec)&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p/&gt;&lt;h3&gt;multi-table DELETE:&lt;/h3&gt;&lt;div class=&quot;_code&quot;&gt;&lt;pre&gt;mysql&amp;gt; EXPLAIN DELETE FROM t1 USING t1 JOIN t2 ON t1.a = t2.a;&lt;br /&gt;+----+-------------+-------+------+---------------+------+---------+-----------+------+-------------+&lt;br /&gt;| id | select_type | table | type | possible_keys | key  | key_len | ref       | rows | Extra       |&lt;br /&gt;+----+-------------+-------+------+---------------+------+---------+-----------+------+-------------+&lt;br /&gt;|  1 | SIMPLE      | t1    | ALL  | PRIMARY       | NULL | NULL    | NULL      | 3072 |             |&lt;br /&gt;|  1 | SIMPLE      | t2    | ref  | t2i1          | t2i1 | 4       | test.t1.a |    1 | Using index |&lt;br /&gt;+----+-------------+-------+------+---------------+------+---------+-----------+------+-------------+&lt;br /&gt;2 rows in set (0.00 sec)&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For single-table UPDATE and DELETE it may be interesting to explain subquery execution:&lt;/p&gt;&lt;h3&gt;single-table UPDATE with a subquery:&lt;/h3&gt;&lt;div class=&quot;_code&quot;&gt;&lt;pre&gt;mysql&amp;gt; EXPLAIN UPDATE t1 SET b = '12345' WHERE a IN (SELECT a FROM t2);&lt;br /&gt;+----+--------------------+-------+----------------+---------------+------+---------+------+------+-------------+&lt;br /&gt;| id | select_type        | table | type           | possible_keys | key  | key_len | ref  | rows | Extra       |&lt;br /&gt;+----+--------------------+-------+----------------+---------------+------+---------+------+------+-------------+&lt;br /&gt;|  1 | PRIMARY            | t1    | ALL            | PRIMARY       | NULL | NULL    | NULL | 3072 | Using where |&lt;br /&gt;|  2 | DEPENDENT SUBQUERY | t2    | index_subquery | t2i1          | t2i1 | 4       | func |    1 | Using index |&lt;br /&gt;+----+--------------------+-------+----------------+---------------+------+---------+------+------+-------------+&lt;br /&gt;2 rows in set (0.00 sec)&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;... or even:&lt;/p&gt;&lt;h3&gt;single-table DELETE with subqueries:&lt;/h3&gt;&lt;div class=&quot;_code&quot;&gt;&lt;pre&gt;mysql&amp;gt; EXPLAIN DELETE FROM t1 WHERE a IN (SELECT t2.a FROM t2 JOIN t3 WHERE t2.b = t3.b AND t3.a IN (SELECT a FROM t4));&lt;br /&gt;+----+--------------------+-------+------+---------------+------+---------+------+------+-------------- - - -&lt;br /&gt;| id | select_type        | table | type | possible_keys | key  | key_len | ref  | rows | Extra&lt;br /&gt;+----+--------------------+-------+------+---------------+------+---------+------+------+-------------- - - -&lt;br /&gt;|  1 | PRIMARY            | t1    | ALL  | PRIMARY       | NULL | NULL    | NULL | 3072 | Using where&lt;br /&gt;|  2 | DEPENDENT SUBQUERY | t2    | ref  | t2i1          | t2i1 | 4       | func |    1 |&lt;br /&gt;|  2 | DEPENDENT SUBQUERY | t3    | ALL  | NULL          | NULL | NULL    | NULL | 3072 | Using where;&lt;br /&gt;|  3 | DEPENDENT SUBQUERY | t4    | ALL  | NULL          | NULL | NULL    | NULL | 3072 | Using where&lt;br /&gt;+----+--------------------+-------+------+---------------+------+---------+------+------+-------------- - - -&lt;br /&gt;4 rows in set (0.00 sec)&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;&lt;p&gt;Manuals are available &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href='http://dev.mysql.com/doc/refman/5.6/en/explain.html'&gt;here&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href='http://dev.mysql.com/doc/refman/5.6/en/explain-output.html'&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;</description>
         <author>Gleb Shchepa</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-1969531358897361384.post-3149619546999821780</guid>
         <pubDate>Tue, 04 Oct 2011 11:08:00 +0000</pubDate>
      </item>
      <item>
         <title>Optimizer tracing: Query Execution Plan descriptions beyond EXPLAIN</title>
         <link>http://jorgenloland.blogspot.com/2011/10/optimizer-tracing-query-execution-plan.html</link>
         <description>Understanding why MySQL chooses a particular join order or why table scan is chosen instead of range scan is often very hard even for experienced MySQL users. Two almost identical queries, differing only in constant values, may produce completely different plans. That&amp;#39;s why we&amp;#39;re introducing a great new feature in 5.6: &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://forge.mysql.com/wiki/MySQL_Internals_Optimizer_tracing&quot;&gt;&lt;i&gt;Optimizer Tracing&lt;/i&gt;&lt;/a&gt;. The target users of this feature are developers and MySQL users experienced enough to understand the ins and outs of EXPLAIN.&lt;br&gt;&lt;br&gt;&lt;b&gt;What Optimizer Tracing is&lt;/b&gt;&lt;br&gt;You may already have guessed  this, but optimizer tracing is a printout  of important decisions the  MySQL optimizer has done during the process of making the Query  Execution Plan. &lt;br&gt;&lt;br&gt;The  trace is presented in JSON format which is easy to read both for humans and others.&lt;br&gt;&lt;br&gt;Currently, the optimizer trace includes short explanations for:&lt;br&gt;&lt;ul&gt;&lt;li&gt;Why the join order was chosen.&lt;/li&gt;&lt;li&gt;Important query transformations like IN to EXISTS.&lt;/li&gt;&lt;li&gt;The access methods applicable and why one of them was chosen.&lt;/li&gt;&lt;li&gt;Which conditions are attached to each table (i.e., what hides behind &amp;quot;Using where&amp;quot; in EXPLAIN)&lt;/li&gt;&lt;/ul&gt;More coverage will be added as we go along.&lt;br&gt;&lt;b&gt;&lt;/b&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;What Optimizer Tracing is NOT&lt;/b&gt;&lt;br&gt;The feature is not, and never will be, complete in the sense that it does not describe all choices the optimizer does. However, we&amp;#39;re going to add more information when we find valuable things to add. We will not guarantee backwards compatibility like we do for EXPLAIN. This gives us the freedom to improve tracing at a high pace without going through multi-release deprecation warnings etc. In other words: optimizer tracing is not a replacement for EXPLAIN. &lt;br&gt;&lt;br&gt;&lt;b&gt;A quick tour&lt;/b&gt;&lt;br&gt;The best way get a feeling of optimizer tracing is to give it a try. Below is a teaser:&lt;br&gt;&lt;b&gt;&lt;/b&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://jorgenloland.blogspot.com/2011/10/optimizer-tracing-query-execution-plan.html#more&quot;&gt;Read more »&lt;/a&gt;</description>
         <author>Jørgen Løland</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-7171477457129187873.post-8814469961574103155</guid>
         <pubDate>Tue, 04 Oct 2011 10:02:00 +0000</pubDate>
         <media:thumbnail height="72" url="http://2.bp.blogspot.com/--EakL2J2zoQ/ToBsHpbaesI/AAAAAAAAAB4/wP5ICrANpfg/s72-c/trace-json.jpg" width="72" xmlns:media="http://search.yahoo.com/mrss/"/>
      </item>
      <item>
         <title>Batched Key Access Speeds Up Disk-Bound Join Queries</title>
         <link>http://oysteing.blogspot.com/2011/10/bacthed-key-access-speeds-up-disk-bound.html</link>
         <description>&lt;style type=&quot;text/css&quot;&gt;&lt;!--div.codebox {height:100%;width:100%;border:1px solid;background-color:#EEEEEE;padding:4px;}--&gt;&lt;/style&gt;A new feature in &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://blogs.oracle.com/MySQL/entry/more_early_access_features_in&quot;&gt;MySQL 5.6.3 Development Milestone Release&lt;/a&gt; is &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.6/en/bka-optimization.html&quot;&gt;Batched Key Access (BKA)&lt;/a&gt;. BKA can be applied when an index lookup can be used to execute a join query. One example of such a query is:&lt;br /&gt;&lt;br /&gt;&lt;div class=&quot;codebox&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;mysql&amp;gt; SELECT * FROM customer JOIN orders ON c_custkey = o_custkey;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;Given that the &lt;tt&gt;customer&lt;/tt&gt; table is significantly smaller than the &lt;tt&gt;orders&lt;/tt&gt; table, and assuming that we have an index on the &lt;tt&gt;o_custkey&lt;/tt&gt; column of &lt;tt&gt;orders&lt;/tt&gt;, MySQL have traditionally executed this join as follows: Scan the entire &lt;tt&gt;customer&lt;/tt&gt; table, and for each row in the &lt;tt&gt;customer&lt;/tt&gt; table, do an index look-up into the &lt;tt&gt;orders&lt;/tt&gt; table to find matching rows.  This strategy is know as &lt;i&gt;Index Nested Loops Join&lt;/i&gt;, or as MySQL EXPLAIN puts it:&lt;br /&gt;&lt;br /&gt;&lt;div class=&quot;codebox&quot; style=&quot;overflow:auto;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;+----+-------------+----------+------+---------------+-------------+---------+-------------------------+--------+-------+&lt;br /&gt;| id | select_type | table    | type | possible_keys | key         | key_len | ref                     | rows   | Extra |&lt;br /&gt;+----+-------------+----------+------+---------------+-------------+---------+-------------------------+--------+-------+&lt;br /&gt;|  1 | SIMPLE      | customer | ALL  | PRIMARY       | NULL        | NULL    | NULL                    | 150000 |       |&lt;br /&gt;|  1 | SIMPLE      | orders   | ref  | i_o_custkey   | i_o_custkey | 5       | dbt3.customer.c_custkey |      7 |       |&lt;br /&gt;+----+-------------+----------+------+---------------+-------------+---------+-------------------------+--------+-------+&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;If BKA is applied, MySQL will instead buffer keys to be used for index look-up in the &lt;i&gt;join buffer&lt;/i&gt;, and each time the join buffer is full, it will do a batched look-up on the index.  That is, the whole set of keys from the join buffer is sent to the storage engine in one batch. For this purpose, MySQL Server uses the &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.6/en/mrr-optimization.html&quot;&gt;Multi-Range Read interface&lt;/a&gt; of the Storage Engine API.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-size:large;&quot;&gt;Multi-Range Read (MRR)&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;The advantage of requesting a batch of rows from the storage engine, is that the storage engine may be able to deliver the rows more efficiently that way. The MRR interface has for some time been &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://www.clusterdb.com/mysql-cluster/mysql-cluster-multi-range-read-using-ndb-api/&quot;&gt;used in MySQL Cluster&lt;/a&gt; since it reduces communication between nodes by making it possible to request more than one row per round-trip. &lt;br /&gt;&lt;br /&gt;A new feature in &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://blogs.oracle.com/mysql/2011/04/top_features_in_mysql_562_development_milestone_release.html&quot;&gt;MySQL 5.6.2&lt;/a&gt; was Disk-Sweep Multi-Range Read (DS-MRR) optimizations for both InnoDB and MyISAM.  When DS-MRR is applied, the storage engine will access the data rows in the order given by the base table; instead of in the order given by the index. For a disk-bound query this has two advantages:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;All interesting rows on the same disk page will be accessed together.  Hence, one do not risk that a page has been removed from the buffer pool between two accesses to the page.&lt;/li&gt;&lt;li&gt;Even if modern disks/file systems do not give any guarantees, accessing the pages in table order, will normally give close to sequential access to the disk.  As we know, that should give much better performance compared to random access.  (If multiple sessions access the disks simultaneously, the advantage of sequential access within a session will of course be less significant.)&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-size:large;&quot;&gt;Turning On Batched Key Access&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;While BKA is advantageous in a disk-bound system, it may actually reduce performance in a CPU-bound setting. (More on this below.)  Since the MySQL Optimizer currently has no information on whether a table resides in memory or on disk, BKA is off by default.&lt;br /&gt;&lt;br /&gt;You can turn on BKA by setting an &lt;tt&gt;optimizer_switch&lt;/tt&gt; flag.  Since BKA depends on MRR, the MRR flag also needs to be on. (This is default.)  In addition, since the cost model for MRR is currently way too conservative, in order to use MRR, cost-based MRR optimization needs to be turned off.  In other words, the following settings must be done in order for the Optimizer to consider BKA:&lt;br /&gt;&lt;br /&gt;&lt;div class=&quot;codebox&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;mysql&amp;gt; SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;With BKA turned on, EXPLAIN for the query presented earlier, will look like this:&lt;br /&gt;&lt;br /&gt;&lt;div class=&quot;codebox&quot; style=&quot;overflow:auto;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;+----+-------------+----------+------+---------------+-------------+---------+-------------------------+--------+----------------------------------------+&lt;br /&gt;| id | select_type | table    | type | possible_keys | key         | key_len | ref                     | rows   | Extra                                  |&lt;br /&gt;+----+-------------+----------+------+---------------+-------------+---------+-------------------------+--------+----------------------------------------+&lt;br /&gt;|  1 | SIMPLE      | customer | ALL  | PRIMARY       | NULL        | NULL    | NULL                    | 150000 |                                        |&lt;br /&gt;|  1 | SIMPLE      | orders   | ref  | i_o_custkey   | i_o_custkey | 5       | dbt3.customer.c_custkey |      7 | Using join buffer (Batched Key Access) |&lt;br /&gt;+----+-------------+----------+------+---------------+-------------+---------+-------------------------+--------+----------------------------------------+&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-size:large;&quot;&gt;Performance Impact of BKA on DBT-3 Queries&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;The chart below shows query execution times, with and without BKA, for all the queries of the &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://osdldbt.sourceforge.net/#dbt3&quot;&gt;DBT-3 benchmark&lt;/a&gt; where BKA can be used.  The size of the database is DBT-3 scale 1 (4 GB), and in order to get disk bound queries, the size of the InnoDB buffer pool is just 50 MB. We see that, for most of the queries, the run time with BKA is around half the run time without BKA .&lt;br /&gt;&lt;br /&gt;&lt;div class=&quot;separator&quot; style=&quot;clear:both;text-align:center;&quot;&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://2.bp.blogspot.com/-U408wL-cX8M/TomseziTiPI/AAAAAAAAABg/FcrBheu4Brs/s1600/bka-dbt3.jpg&quot; style=&quot;margin-left:1em;margin-right:1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://2.bp.blogspot.com/-U408wL-cX8M/TomseziTiPI/AAAAAAAAABg/FcrBheu4Brs/s480/bka-dbt3.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Note that Query 17 is an example of a query where it is not beneficial to use BKA even when the query is disk-bound.  In that particular case, locality of accesses are very good without BKA, and applying BKA actually spreads accesses to related rows in time, decreasing the buffer cache hit ratio.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-size:large;&quot;&gt;Effects of Increasing the Join Buffer Size&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;As mention above, the size of the batches used by BKA is determined by the size of the join buffer.  It follows from the discussion above that the larger the batch, the more efficiently the storage engine may arrange its disk accesses.  The results presented above were achieved with a default setting of &lt;tt&gt;join_buffer_size&lt;/tt&gt; (128 kB).  We will now look closer at what happens if we increase the size of the join buffer.  For this purpose, we will use Query 13 (&lt;i&gt;Customer Distribution Query&lt;/i&gt;) from the DBT-3 benchmark:&lt;br /&gt;&lt;br /&gt;&lt;div class=&quot;codebox&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;SELECT c_count, COUNT(*) AS custdist&lt;br /&gt;FROM (&lt;br /&gt;      SELECT c_custkey, COUNT(o_orderkey) AS c_count&lt;br /&gt;      FROM customer LEFT OUTER JOIN orders ON&lt;br /&gt;        c_custkey = o_custkey AND o_comment NOT LIKE '%express%requests%'&lt;br /&gt;      GROUP BY c_custkey&lt;br /&gt;     ) AS c_orders &lt;br /&gt;GROUP BY c_count&lt;br /&gt;ORDER BY custdist DESC, c_count DESC;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;The heart of this query is the join between &lt;tt&gt;customer&lt;/tt&gt; table and &lt;tt&gt;orders&lt;/tt&gt; table:&lt;br /&gt;&lt;br /&gt;&lt;div class=&quot;codebox&quot; style=&quot;overflow:auto;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;+----+-------------+------------+-------+---------------+---------------+---------+-------------------------+--------+-----------------------------------------------------+&lt;br /&gt;| id | select_type | table      | type  | possible_keys | key           | key_len | ref                     | rows   | Extra                                               |&lt;br /&gt;+----+-------------+------------+-------+---------------+---------------+---------+-------------------------+--------+-----------------------------------------------------+&lt;br /&gt;|  1 | PRIMARY     | &amp;lt;derived2&amp;gt; | ALL   | NULL          | NULL          | NULL    | NULL                    | 1050000 | Using temporary; Using filesort                     |&lt;br /&gt;|  2 | DERIVED     | customer   | index | NULL          | i_c_nationkey | 5       | NULL                    |  150000 | Using index; Using temporary; Using filesort        |&lt;br /&gt;|  2 | DERIVED     | orders     | ref   | i_o_custkey   | i_o_custkey   | 5       | dbt3.customer.c_custkey |       7 | Using where; Using join buffer (Batched Key Access) |&lt;br /&gt;+----+-------------+------------+-------+---------------+---------------+---------+-------------------------+--------+-----------------------------------------------------+&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;Using BKA for this query, MySQL will fill the join buffer with customer keys and send a batch of keys to the storage engine. The storage engine will find the primary keys for the requested rows in the &lt;tt&gt;i_o_custkey&lt;/tt&gt; index, sort the primary keys, and access the &lt;tt&gt;orders&lt;/tt&gt; table in primary key order. In other words, there will be one pass over the &lt;tt&gt;orders&lt;/tt&gt; table for each batch.  The graph below shows the effect of increasing the join buffer on Query 13. (Note the logarithmic scale of the y-axis.)  The query execution time goes down from 20 minutes with a default join buffer to less than 10 secs with a 32 MB join buffer!&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class=&quot;separator&quot; style=&quot;clear:both;text-align:center;&quot;&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://3.bp.blogspot.com/-20uvXuiAPLk/Tom7NhCV6LI/AAAAAAAAABw/4P1FdafunIk/s1600/bka-slide2.jpg&quot; style=&quot;margin-left:1em;margin-right:1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://3.bp.blogspot.com/-20uvXuiAPLk/Tom7NhCV6LI/AAAAAAAAABw/4P1FdafunIk/s480/bka-slide2.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-size:large;&quot;&gt;BKA Performance When Queries are CPU-bound&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;As discussed above, BKA is designed to speed up disk-bound queries.  Now we will look at what happens if BKA is used in a system where all the data in the database reside in main memory.  The chart below presents the results of running the same DBT-3 queries with a InnoDB buffer pool of 5 GB.  While the difference is not very significant for most queries, we see there are a few queries where the impact of BKA is quite negative.&lt;br /&gt;&lt;br /&gt;&lt;div class=&quot;separator&quot; style=&quot;clear:both;text-align:center;&quot;&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://4.bp.blogspot.com/-kabmXHPBMRc/TonCY6EJ46I/AAAAAAAAAB4/Nueaes-isAk/s1600/bka-mem.jpg&quot; style=&quot;margin-left:1em;margin-right:1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://4.bp.blogspot.com/-kabmXHPBMRc/TonCY6EJ46I/AAAAAAAAAB4/Nueaes-isAk/s480/bka-mem.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;The negative impact of BKA is not just the overhead of sorting the keys before accessing the table.  If you look at the execution plan for QUERY 13 as presented above, it shows that the result of the join needs to be sorted in order to perform the group by operation of the subquery.  This would not have been necessary, had not BKA been used.  Without BKA, the result from the join would have been delivered in customer key order and grouping could be done without sorting.  Hence, in this experiment, the execution time for Query 13 increased from 3.6 to 4.8 seconds when using BKA.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-size:large;&quot;&gt;Use with Care&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;As I have shown, join query execution can benefit significantly from the use of Batched Key Access.  However, there is also examples of queries where BKA do more harm than good.  Hence, it should be used with care. My advice is to study the BKA performance for your typical queries and to only turn on BKA for queries that are known to benefit from it.</description>
         <author>Øystein</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-1508669603650457962.post-1806241750750192314</guid>
         <pubDate>Mon, 03 Oct 2011 18:13:00 +0000</pubDate>
         <media:thumbnail height="72" url="http://2.bp.blogspot.com/-U408wL-cX8M/TomseziTiPI/AAAAAAAAABg/FcrBheu4Brs/s72-c/bka-dbt3.jpg" width="72" xmlns:media="http://search.yahoo.com/mrss/"/>
      </item>
      <item>
         <title>Kick-off</title>
         <link>http://guilhembichot.blogspot.com/2011/10/kick-off.html</link>
         <description>Hi all!&lt;br /&gt;Here is a new blog; I'll post here some thoughts, tutorials... all related to my job - software developer at Oracle Corporation, working on &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://www.mysql.com/&quot;&gt;MySQL&lt;/a&gt;. It will soon be nine years since I joined MySQL AB. After working on interesting topics: Replication, Online Backup, the Maria storage engine, each of them having taught me something, I'm now full-time on the Optimizer, and guess what - it is an interesting topic and it teaches me something!</description>
         <author>Guilhem Bichot</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-9191231274999121063.post-9192408677861980784</guid>
         <pubDate>Mon, 03 Oct 2011 06:52:00 +0000</pubDate>
      </item>
      <item>
         <title>Tips and tricks: Killer response time for non-overlapping intervals</title>
         <link>http://jorgenloland.blogspot.com/2011/09/tips-and-tricks-killer-response-time.html</link>
         <description>Assume you have a table where you store &lt;i&gt;non-overlapping intervals&lt;/i&gt; using two columns, e.g. IP ranges. IP ranges are simple to represent using &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://www.silisoftware.com/tools/ipconverter.php&quot;&gt;integer notation&lt;/a&gt;:&lt;br&gt;&lt;br&gt;&lt;div style=&quot;font-family:Verdana, sans-serif;&quot;&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;CREATE TABLE ip_owner (&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family:Verdana, sans-serif;&quot;&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;   owner_id int NOT NULL,&lt;br&gt;   /* some columns */ &lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family:Verdana, sans-serif;&quot;&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;   ip_start_int bigint NOT NULL,      /* IP address converted to integer */&lt;br&gt;   ip_end_int bigint NOT NULL,        /* IP address converted to integer */&lt;br&gt;   PRIMARY KEY (owner_id),&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family:Verdana, sans-serif;&quot;&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;   INDEX ip_range (ip_start_int, ip_end_int)&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family:Verdana, sans-serif;&quot;&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;) ENGINE=InnoDB;&lt;/span&gt;&lt;/div&gt;&lt;br&gt;&lt;br&gt;And then you find yourself in a situation where you want to know who, if anyone, owns the IP address &lt;span style=&quot;font-size:x-small;&quot;&gt;X&lt;/span&gt;. This can be done using the following query:&lt;br&gt;&lt;br&gt;&lt;div style=&quot;font-family:Verdana, sans-serif;&quot;&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;SELECT * FROM ip_owner WHERE ip_start_int &amp;lt;= X AND ip_end_int &amp;gt;= X;&lt;/span&gt;&lt;/div&gt;&lt;br&gt;MySQL can resolve this using a range scan, but will unfortunately only be able to use the &lt;span style=&quot;font-size:x-small;&quot;&gt;ip_start_int &amp;lt;= X&lt;/span&gt; part of the condition as a range &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://jorgenloland.blogspot.com/2011/08/mysql-range-access-method-explained.html&quot;&gt;as explained here&lt;/a&gt;. Thus, the query will either be resolved by range scan if fairly few records have &lt;span style=&quot;font-size:x-small;&quot;&gt;ip_start_int &amp;lt;= X&lt;/span&gt; or table scan otherwise. That means unreliable response time because it will be much quicker to query low-valued IPs than high valued IPs. I inserted 1M records into the table before running the queries below:&lt;br&gt;&lt;br&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://jorgenloland.blogspot.com/2011/09/tips-and-tricks-killer-response-time.html#more&quot;&gt;Read more »&lt;/a&gt;</description>
         <author>Jørgen Løland</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-7171477457129187873.post-6369433373687039343</guid>
         <pubDate>Tue, 27 Sep 2011 13:42:00 +0000</pubDate>
      </item>
      <item>
         <title>The MySQL range access method explained</title>
         <link>http://jorgenloland.blogspot.com/2011/08/mysql-range-access-method-explained.html</link>
         <description>The range access method uses an index to read a subset of rows that form one or multiple continuous index value intervals. The intervals are defined by the query&amp;#39;s range predicates, which are comparisons using any of &lt;span style=&quot;font-size:x-small;&quot;&gt;=, &amp;lt;=&amp;gt;, IN(), IS NULL, IS NOT NULL, &amp;gt;, &amp;lt;, &amp;gt;=, &amp;lt;=, BETWEEN, !=, &amp;lt;&amp;gt; or LIKE&lt;/span&gt;.&lt;br&gt;&lt;br&gt;Some examples:&lt;br&gt;&lt;div class=&quot;separator&quot; style=&quot;clear:both;text-align:center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear:both;text-align:center;&quot;&gt;&lt;/div&gt;&lt;div style=&quot;font-family:Verdana, sans-serif;&quot;&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;SELECT * FROM blog WHERE author_id IN (1, 7, 8, 10)&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family:Verdana, sans-serif;&quot;&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;SELECT * FROM orders WHERE value &amp;gt; 1000&lt;/span&gt;&lt;/div&gt;&lt;br&gt;&lt;div class=&quot;separator&quot; style=&quot;clear:both;text-align:center;&quot;&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://3.bp.blogspot.com/-qFiyZp1rRHI/TlOC9HkoStI/AAAAAAAAABs/1G_bNboXP70/s1600/single.JPG&quot; style=&quot;clear:left;float:left;margin-bottom:1em;margin-right:1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;231&quot; src=&quot;http://3.bp.blogspot.com/-qFiyZp1rRHI/TlOC9HkoStI/AAAAAAAAABs/1G_bNboXP70/s320/single.JPG&quot; width=&quot;320&quot;&gt;&lt;/a&gt;&lt;/div&gt;You know that the range access method is used when EXPLAIN shows &lt;span style=&quot;font-size:x-small;&quot;&gt;type=range&lt;/span&gt;.&lt;br&gt;&lt;br&gt;Naturally, there has to be an index on the column used by the range predicate. Since indexes are ordered, MySQL will, for each interval, dive down the index using the interval start value and read it&amp;#39;s way through the index leaves until it reaches the interval end value:&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://jorgenloland.blogspot.com/2011/08/mysql-range-access-method-explained.html#more&quot;&gt;Read more »&lt;/a&gt;</description>
         <author>Jørgen Løland</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-7171477457129187873.post-1638616608708737558</guid>
         <pubDate>Tue, 23 Aug 2011 13:32:00 +0000</pubDate>
         <media:thumbnail height="72" url="http://3.bp.blogspot.com/-qFiyZp1rRHI/TlOC9HkoStI/AAAAAAAAABs/1G_bNboXP70/s72-c/single.JPG" width="72" xmlns:media="http://search.yahoo.com/mrss/"/>
      </item>
      <item>
         <title>The meaning of ref=func in MySQL EXPLAIN</title>
         <link>http://jorgenloland.blogspot.com/2011/08/meaning-of-reffunc-in-mysql-explain.html</link>
         <description>When EXPLAIN shows that a table is accessed using the [eq_]ref access type, we&amp;#39;re used to look at the ref column to see where MySQL gets the value to look up from. Usually, we see either &amp;quot;const&amp;quot; if the value is provided as a constant in the query or a column name if the value is read from a column in an already read table:&lt;br&gt;&lt;br&gt;&lt;div style=&quot;background:none repeat scroll 0% 0% black;color:white;height:100%;line-height:100%;overflow:auto;padding:10px;width:100%;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:xx-small;&quot;&gt;EXPLAIN 
&lt;br&gt;SELECT acc_rec.cust_id, acc_rec.amount, acc_rec.invoicenumber
&lt;br&gt;FROM accounts_receivable as acc_rec
&lt;br&gt;WHERE &lt;span style=&quot;color:lime;&quot;&gt;acc_rec.cust_id=1&lt;/span&gt;;
&lt;br&gt;+----+-------------+---------+------+---------+-------+
&lt;br&gt;| id | select_type | table   | type | key     | ref   |
&lt;br&gt;+----+-------------+---------+------+---------+-------+
&lt;br&gt;| 1  | SIMPLE      | acc_rec | ref  | cust_id | &lt;span style=&quot;color:lime;&quot;&gt;const&lt;/span&gt; |
&lt;br&gt;+----+-------------+---------+------+---------+-------+
&lt;br&gt;
&lt;br&gt;EXPLAIN 
&lt;br&gt;SELECT acc_rec.cust_id, acc_rec.amount, acc_rec.invoicenumber,
&lt;br&gt;       customers.firstname, customers.lastname, customers.phone
&lt;br&gt;FROM accounts_receivable AS acc_rec JOIN customers AS cust
&lt;br&gt;ON &lt;span style=&quot;color:lime;&quot;&gt;acc_rec.cust_id = customers.cust_id&lt;/span&gt;;
&lt;br&gt;+----+-------------+---------+--------+---------+-----------------+
&lt;br&gt;| id | select_type | table   | type   | key     | ref             |
&lt;br&gt;+----+-------------+---------+--------+---------+-----------------+
&lt;br&gt;| 1  | SIMPLE      | acc_rec | ALL    | NULL    | NULL            |
&lt;br&gt;| 1  | SIMPLE      | cust    | eq_ref | PRIMARY | &lt;span style=&quot;color:lime;&quot;&gt;acc_rec.cust_id&lt;/span&gt; |
&lt;br&gt;+----+-------------+---------+--------+---------+-----------------+&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;But what if ref shows the value &amp;quot;func&amp;quot;? In this case, the value used as input to [eq_]ref is the output of some function. A few examples:&lt;br&gt;&lt;br&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://jorgenloland.blogspot.com/2011/08/meaning-of-reffunc-in-mysql-explain.html#more&quot;&gt;Read more »&lt;/a&gt;</description>
         <author>Jørgen Løland</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-7171477457129187873.post-3181404526195026473</guid>
         <pubDate>Wed, 17 Aug 2011 12:52:00 +0000</pubDate>
      </item>
      <item>
         <title>InnoDB Persistent Statistics Save the Day</title>
         <link>http://oysteing.blogspot.com/2011/05/innodb-persistent-statistics-save-day.html</link>
         <description>&lt;style type=&quot;text/css&quot;&gt;&lt;!--div.codebox {height:100%;width:100%;border:1px solid;background-color:#EEEEEE;padding:4px;}--&gt;&lt;/style&gt;In my previous &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://oysteing.blogspot.com/2011/04/more-stable-query-execution-time-by.html&quot;&gt;blog posting&lt;/a&gt;, I explained how I was able to get more stable query execution times by increasing the amount of sampling used to by InnoDB to calculate statistics.  However, for my example query, Query 8 of the DBT-3 benchmark, the MySQL Optimizer still toggled between three different indexes to use when accessing one of the 8 tables. &lt;br /&gt;&lt;br /&gt;I decided to try out &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://blogs.innodb.com/wp/2011/04/innodb-persistent-statistics-at-last/&quot;&gt;InnoDB Persistent Statistics&lt;/a&gt; that is one of the new features in the recent &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://blogs.oracle.com/mysql/2011/04/top_features_in_mysql_562_development_milestone_release.html&quot;&gt;MySQL 5.6.2 Development Milestone Release&lt;/a&gt;. According to the advertisement, this should give both more accurate and more stable statistics. The improved accuracy is achieved by using a more precise sampling algorithm, while the increased stability is achieved by giving the user full control over when statistics are recalculated. That is, the persistent statistics are not recalculated automatically.&lt;br /&gt;&lt;br /&gt;In order to activate persistent statistics, you first need to run a script to create the tables to be used to store the statistics. Then, you can enable the feature by setting the system variable &lt;tt&gt;innodb_analyze_is_persistent&lt;/tt&gt;. For the details, see the &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.6/en/innodb-performance.html#innodb-persistent-stats&quot;&gt;MySQL 5.6 Manual&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-size:large;&quot;&gt;More Accurate Statistics&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;To investigate whether the new sampling algorithm gives more accurate statistics, I looked at the average and variance in the estimated cardinality of indexed columns over 100 runs of ANALYZE on the DBT-3 tables. The average of the estimates did not change much as it is actually pretty spot-on with the old sampling algorithm.  It is the great variance that causes the observed inaccuracies.&lt;br /&gt;&lt;br /&gt;The below chart shows the &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://en.wikipedia.org/wiki/Coefficient_of_variation&quot;&gt;coefficient of variation&lt;/a&gt; for the estimated cardinality. (Columns that are listed more than once, are part of multiple indexes. If a bar in the bar chart is not visible, it means that there was no or very little variance between runs for this column.)  The bar chart clearly shows that for most indexes, the sampling algorithm for persistent statistics gives less variance than the old sampling algorithm used with transient statistics. (The number of sampled index pages was 100 in both cases.)&lt;br /&gt;&lt;br /&gt;&lt;div class=&quot;separator&quot; style=&quot;clear:both;text-align:center;&quot;&gt;&lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://1.bp.blogspot.com/-XMJQoCj5FtQ/TcE-9K1tbHI/AAAAAAAAAAw/so-You4bMMM/s1600/stat100_cv.jpg&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://1.bp.blogspot.com/-XMJQoCj5FtQ/TcE-9K1tbHI/AAAAAAAAAAw/so-You4bMMM/s480/stat100_cv.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-size:large;&quot;&gt;More Stable Statistics&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;As mentioned above, the persistent statistics will be more stable because it is not updated automatically like the old transient statistics.  This way, for our performance regression test, we can run ANALYZE once for each table, and all later runs on this database will use the same statistics.  Another advantage of being able to control when statistics are recalculated, is that it can be scheduled at times when one can afford to use a higher sampling rate.  The disadvantage is that it becomes the burden of the DBA to make sure to regularly initiate a recalculation of statistics to prevent that the statistics become outdated.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-size:large;&quot;&gt;InnoDB Statistics Tables&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;The InnoDB statistics tables are ordinary tables that are created in a database called &lt;tt&gt;innodb&lt;/tt&gt;. There are two tables: &lt;tt&gt;table_stats&lt;/tt&gt; and &lt;tt&gt;index_stats&lt;/tt&gt; that contain per-table and per-index statistics, respectively:&lt;br /&gt;&lt;br /&gt;&lt;div class=&quot;codebox&quot; style=&quot;overflow:scroll;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;mysql&amp;gt; describe innodb.table_stats;&lt;br /&gt;+--------------------------+---------------------+------+-----+-------------------+-----------------------------+&lt;br /&gt;| Field                    | Type                | Null | Key | Default           | Extra                       |&lt;br /&gt;+--------------------------+---------------------+------+-----+-------------------+-----------------------------+&lt;br /&gt;| database_name            | varchar(64)         | NO   | PRI | NULL              |                             |&lt;br /&gt;| table_name               | varchar(64)         | NO   | PRI | NULL              |                             |&lt;br /&gt;| stats_timestamp          | timestamp           | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |&lt;br /&gt;| n_rows                   | bigint(20) unsigned | NO   |     | NULL              |                             |&lt;br /&gt;| clustered_index_size     | bigint(20) unsigned | NO   |     | NULL              |                             |&lt;br /&gt;| sum_of_other_index_sizes | bigint(20) unsigned | NO   |     | NULL              |                             |&lt;br /&gt;+--------------------------+---------------------+------+-----+-------------------+-----------------------------+&lt;br /&gt;6 rows in set (0.01 sec)&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; describe innodb.index_stats;&lt;br /&gt;+------------------+---------------------+------+-----+-------------------+-----------------------------+&lt;br /&gt;| Field            | Type                | Null | Key | Default           | Extra                       |&lt;br /&gt;+------------------+---------------------+------+-----+-------------------+-----------------------------+&lt;br /&gt;| database_name    | varchar(64)         | NO   | PRI | NULL              |                             |&lt;br /&gt;| table_name       | varchar(64)         | NO   | PRI | NULL              |                             |&lt;br /&gt;| index_name       | varchar(64)         | NO   | PRI | NULL              |                             |&lt;br /&gt;| stat_timestamp   | timestamp           | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |&lt;br /&gt;| stat_name        | varchar(64)         | NO   | PRI | NULL              |                             |&lt;br /&gt;| stat_value       | bigint(20) unsigned | NO   |     | NULL              |                             |&lt;br /&gt;| sample_size      | bigint(20) unsigned | YES  |     | NULL              |                             |&lt;br /&gt;| stat_description | varchar(1024)       | NO   |     | NULL              |                             |&lt;br /&gt;+------------------+---------------------+------+-----+-------------------+-----------------------------+&lt;br /&gt;8 rows in set (0.00 sec)&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;&lt;tt&gt;table_stats&lt;/tt&gt; contains one row per table:&lt;br /&gt;&lt;div class=&quot;codebox&quot; style=&quot;overflow:scroll;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;mysql&amp;gt; select * from innodb.table_stats where table_name='customer';&lt;br /&gt;+---------------+------------+---------------------+--------+----------------------+--------------------------+&lt;br /&gt;| database_name | table_name | stats_timestamp     | n_rows | clustered_index_size | sum_of_other_index_sizes |&lt;br /&gt;+---------------+------------+---------------------+--------+----------------------+--------------------------+&lt;br /&gt;| dbt3          | customer   | 2011-04-01 20:53:30 | 149911 |                 1764 |                      225 |&lt;br /&gt;+---------------+------------+---------------------+--------+----------------------+--------------------------+&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;&lt;tt&gt;index_stats&lt;/tt&gt; contains several rows per index, and the column &lt;tt&gt;stat_name&lt;/tt&gt; identifies the type of statistics as described by the content of the &lt;tt&gt;stat_description&lt;/tt&gt; column.  The &lt;tt&gt;n_diff_pfx_&lt;/tt&gt; rows contain the cardinality statistics:  For rows with &lt;tt&gt;stat_name = 'n_diff_pfx_01'&lt;/tt&gt;, &lt;tt&gt;stat_value&lt;/tt&gt; contains the number of distinct values for the first column of the given index. For rows with &lt;tt&gt;stat_name = 'n_diff_pfx_02'&lt;/tt&gt;, &lt;tt&gt;stat_value&lt;/tt&gt; contains the number of unique combinations of the two first columns of the given index, and so on.  (Note that InnoDB indexes, in addition to the columns that were specified when the index was created, contain all columns of the primary key.) &lt;br /&gt;&lt;div class=&quot;codebox&quot; style=&quot;overflow:scroll;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;mysql&amp;gt; select * from innodb.index_stats where table_name='customer';&lt;br /&gt;+---------------+------------+---------------+---------------------+--------------+------------+-------------+-----------------------------------+&lt;br /&gt;| database_name | table_name | index_name    | stat_timestamp      | stat_name    | stat_value | sample_size | stat_description                  |&lt;br /&gt;+---------------+------------+---------------+---------------------+--------------+------------+-------------+-----------------------------------+&lt;br /&gt;| dbt3          | customer   | i_c_nationkey | 2011-04-01 11:40:29 | n_diff_pfx01 |         25 |         171 | c_nationkey                       |&lt;br /&gt;| dbt3          | customer   | i_c_nationkey | 2011-04-01 11:40:29 | n_diff_pfx02 |     150000 |         171 | c_nationkey,c_custkey             |&lt;br /&gt;| dbt3          | customer   | i_c_nationkey | 2011-04-01 11:40:29 | n_leaf_pages |        171 |        NULL | Number of leaf pages in the index |&lt;br /&gt;| dbt3          | customer   | i_c_nationkey | 2011-04-01 11:40:29 | size         |        225 |        NULL | Number of pages in the index      |&lt;br /&gt;| dbt3          | customer   | PRIMARY       | 2011-04-01 11:40:29 | n_diff_pfx01 |     149911 |         100 | c_custkey                         |&lt;br /&gt;| dbt3          | customer   | PRIMARY       | 2011-04-01 11:40:29 | n_leaf_pages |       1746 |        NULL | Number of leaf pages in the index |&lt;br /&gt;| dbt3          | customer   | PRIMARY       | 2011-04-01 11:40:29 | size         |       1764 |        NULL | Number of pages in the index      |&lt;br /&gt;+---------------+------------+---------------+---------------------+--------------+------------+-------------+-----------------------------------+&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-size:large;&quot;&gt;Manually Updating InnoDB Statistics Tables&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Since the statistics tables are normal tables, they can be updated like any other MySQL table.  For our performance regression tests, we have decided to record our own numbers in these tables.  That way, we will be able to get the exact same numbers even if we for some reason need to recreate the statistics tables (e.g, in another database instance).  &lt;br /&gt;&lt;br /&gt;When we manually update the statistics tables for the DBT-3 benchmarks, we first turn on persistent statistics and run ANALYZE once for each table.  This inserts all necessary rows in the statistics tables, and we will only need to update the relevant rows.  Here are an example UPDATE statement for each table:&lt;br /&gt;&lt;div class=&quot;codebox&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;UPDATE innodb.table_stats SET n_rows=150000 &lt;br /&gt;  WHERE database_name='dbt3' AND table_name='customer' ;&lt;br /&gt;UPDATE innodb.index_stats SET stat_value=25, sample_size=NULL &lt;br /&gt;  WHERE database_name='dbt3' AND table_name='customer' &lt;br /&gt;    AND index_name='i_c_nationkey' AND stat_name='n_diff_pfx01';&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;By convention, I set &lt;tt&gt;index_stats.sample_size&lt;/tt&gt; to &lt;tt&gt;NULL&lt;/tt&gt; to indicate that the value is recorded and not computed by sampling.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-size:large;&quot;&gt;A World of New Possibilities&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;As discussed above, InnoDB Persistent Statistics will be very useful in order to ensure that the same query execution plan is used every time a query is executed.  It also gives the users the ability to control when the statistics are recalculated.  However, persistent statistics opens up for even more new possibilities.  &lt;br /&gt;&lt;br /&gt;We will be able to use this feature to extend our testing of the query optimizer.  By &quot;faking&quot; the statistics, we will be able to explore and test the execution of different query plans without having to actually modify the database.  &lt;br /&gt;&lt;br /&gt;It will also be possible for users to change the statistics in order to force a specific query plan. However, one risks introducing side-effects on other queries so this will have to be done with caution.</description>
         <author>Øystein</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-1508669603650457962.post-2126423813645872715</guid>
         <pubDate>Thu, 05 May 2011 13:10:00 +0000</pubDate>
         <media:thumbnail height="72" url="http://1.bp.blogspot.com/-XMJQoCj5FtQ/TcE-9K1tbHI/AAAAAAAAAAw/so-You4bMMM/s72-c/stat100_cv.jpg" width="72" xmlns:media="http://search.yahoo.com/mrss/"/>
      </item>
      <item>
         <title>More Stable Query Execution Times by Improving InnoDB Statistics</title>
         <link>http://oysteing.blogspot.com/2011/04/more-stable-query-execution-time-by.html</link>
         <description>&lt;style type=&quot;text/css&quot;&gt;&lt;!--div.codebox {height:100%;width:100%;border:1px solid;background-color:#EEEEEE;padding:4px;}--&gt;&lt;/style&gt;As part of ensuring that changes to the MySQL Optimizer does not introduce performance regressions, we have started a project to create a new performance regression test suite. One component in this test suite will be the &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://osdldbt.sourceforge.net/#dbt3&quot;&gt;DBT-3 test suite&lt;/a&gt;. However, we observed that the execution times for DBT-3 varied so much that, in its present form, it was not usable for detecting performance regressions.&lt;br /&gt;&lt;br /&gt;In order to get a better understanding of what was going on, I looked closer at one of the queries that were run, Query 8.  For this particular query, which contains an 8-table join, the execution times varied from 1 minute to 5 hours!  Looking at 100 runs of this query, I detected 8 different query execution plans.  Four of these plans represented differences in which sequence the tables were joined, while the last four differed from the first four with respect to which indexes were used.&lt;br /&gt;&lt;br /&gt;Since the MySQL Optimizer is cost based, when the execution plans vary, this is usually because the underlying statistics reported from the storage engine varies.  One way to investigate the statistics that the optimizer bases its decisions on, is to use the SHOW INDEX command:&lt;br /&gt;&lt;br /&gt;&lt;div class=&quot;codebox&quot; style=&quot;overflow:scroll;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;mysql&amp;gt; show index from customer;&lt;br /&gt;+----------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+&lt;br /&gt;| Table    | Non_unique | Key_name      | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |&lt;br /&gt;+----------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+&lt;br /&gt;| customer |          0 | PRIMARY       |            1 | c_custkey   | A         |      150000 |     NULL | NULL   |      | BTREE      |         |               |&lt;br /&gt;| customer |          1 | i_c_nationkey |            1 | c_nationkey | A         |          47 |     NULL | NULL   | YES  | BTREE      |         |               |&lt;br /&gt;+----------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;In the above example, the &lt;tt&gt;customer&lt;/tt&gt; table has two indexes, a primary index on &lt;tt&gt;c_custkey&lt;/tt&gt; and a secondary index on &lt;tt&gt;c_nationkey&lt;/tt&gt;.  The most important thing to note from this output, is the estimated number of different key values, cardinality . For the two indexed columns of the &lt;tt&gt;customer&lt;/tt&gt; table, the cardinality is 150,000 and 47, respectively.&lt;br /&gt;&lt;br /&gt;The InnoDB statistics are calculated on-the-fly the first time a table is used after the server has been started.  The statistics may be automatically recalculated at various times, and ANALYZE TABLE can be used to force a recalculation. For a description of how InnoDB calculates its statistics, see the &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.5/en/innodb-other-changes-statistics-estimation.html&quot;&gt; MySQL 5.5 Reference Manual&lt;/a&gt;.  The important thing to note is that there is a system variable, &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/doc/refman/5.5/en/innodb-parameters.html#sysvar_innodb_stats_sample_pages&quot;&gt;&lt;tt&gt;innodb_stats_sample_pages&lt;/tt&gt;&lt;/a&gt;, that controls the accuracy of the statistics.  It determines the number of index pages that are sampled in order to calculate the statistics.  The default value is 8.&lt;br /&gt;&lt;br /&gt;Continuing with the example above, I ran ANALYZE on the &lt;tt&gt;customer&lt;/tt&gt; table, and here is what I got:&lt;br /&gt;&lt;br /&gt;&lt;div class=&quot;codebox&quot; style=&quot;overflow:scroll;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-size:small;&quot;&gt;mysql&amp;gt; analyze table customer;&lt;br /&gt;+---------------+---------+----------+----------+&lt;br /&gt;| Table         | Op      | Msg_type | Msg_text |&lt;br /&gt;+---------------+---------+----------+----------+&lt;br /&gt;| dbt3.customer | analyze | status   | OK       |&lt;br /&gt;+---------------+---------+----------+----------+&lt;br /&gt;1 row in set (0.03 sec)&lt;br /&gt;&lt;br /&gt;mysql&amp;gt; show index from customer;&lt;br /&gt;+----------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+&lt;br /&gt;| Table    | Non_unique | Key_name      | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |&lt;br /&gt;+----------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+&lt;br /&gt;| customer |          0 | PRIMARY       |            1 | c_custkey   | A         |      150000 |     NULL | NULL   |      | BTREE      |         |               |&lt;br /&gt;| customer |          1 | i_c_nationkey |            1 | c_nationkey | A         |         134 |     NULL | NULL   | YES  | BTREE      |         |               |&lt;br /&gt;+----------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+&lt;br /&gt;2 rows in set (0.01 sec)&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;As the observant reader has already noticed, the estimated cardinality of the column &lt;tt&gt;c_nationkey&lt;/tt&gt; has changed significantly.  Running ANALYZE several times, I saw numbers as low as 5 and as high as 135 for this column.&lt;br /&gt;&lt;br /&gt;The question is then whether we can get better and more stable statistics by increasing the setting of &lt;tt&gt;innodb_stats_sample_pages&lt;/tt&gt;.  I increased the value to 100, and ran ANALYZE 100 times.  This time, the estimated cardinality was between 22 and 84, and 80% of the time it was between 30 and 60.&lt;br /&gt;&lt;br /&gt;So how did this affect the execution plan for query 8?  Running the query another 100 times with this new setting of innodb_stats_sample_pages, gave only 3 different execution plans, all with the same join ordering.  The remaining difference was in which index was used to access one of the tables. I will discuss how to further reduce the number of plans in a future blog post.&lt;br /&gt;&lt;br /&gt;Note that increasing &lt;tt&gt;innodb_stats_sample_pages&lt;/tt&gt; will cause the calculation of statistics to take longer time.  In my case, sampling 100 pages did not seem to take a noticeable amount of time, and this should normally not cause any problems when ANALYZE TABLE is run explicitly.  However, if InnoDB decides to do an automatic recalculation, the increased time may have some unwanted impact.  Hence, this variable should be used with care, and its value should not be set higher than necessary to get reasonably accurate estimates.</description>
         <author>Øystein</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-1508669603650457962.post-8091655527100412193</guid>
         <pubDate>Thu, 14 Apr 2011 09:21:00 +0000</pubDate>
      </item>
      <item>
         <title>Optimizing MySQL filesort with small limit.</title>
         <link>http://didrikdidrik.blogspot.com/2011/04/optimizing-mysql-filesort-with-small.html</link>
         <description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align:left;&quot;&gt;&quot;filesort&quot; is the MySQL catchall algorithm for producing ordered results. (For an explanation on how it works, see Sergey Petrunias blog &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://s.petrunia.net/blog/?p=24&quot;&gt;http://s.petrunia.net/blog/?p=24&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;For small data sets, &quot;filesort&quot; simply uses a quicksort in memory. However, for large data sets we create temporary (sorted) files, which are later merged.&lt;br /&gt;&lt;br /&gt;In the recent MySQL release candidate &lt;a rel=&quot;nofollow&quot; target=&quot;_blank&quot; href=&quot;http://dev.mysql.com/tech-resources/articles/whats-new-in-mysql-5.6.html&quot;&gt;http://dev.mysql.com/tech-resources/articles/whats-new-in-mysql-5.6.html&lt;/a&gt; this has been optimized for the following kinds of queries (and subqueries)&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;&quot;&gt;SELECT ... FROM single_table ... ORDER BY non_index_column [DESC] LIMIT N [OFFSET M];&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;If we have space in memory for (N + M) rows, we create a (bounded) Priority Queue to hold the data. This means we can produce the ordered result using a single table scan, thus saving the writing/reading of temporary files, and saving the merge step.&lt;br /&gt;&lt;br /&gt;How can you see it in action? Sorry, there's no explain for it (yet). What you can do currently is to inspect the status counters which are associated with filesort, and verify they are all zero.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;&lt;span style=&quot;&quot;&gt;FLUSH STATUS;&lt;/span&gt;&lt;br style=&quot;&quot;/&gt;&lt;span style=&quot;&quot;&gt;SHOW SESSION STATUS LIKE 'Sort%';&lt;/span&gt;&lt;br style=&quot;&quot;/&gt;&lt;span style=&quot;&quot;&gt;&amp;lt;run your select ... limit query&amp;gt;&lt;/span&gt;&lt;br style=&quot;&quot;/&gt;&lt;span style=&quot;&quot;&gt;SHOW SESSION STATUS LIKE 'Sort%';&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;What kind of speedup can you expect? This depends on your data set, and your sort_buffer_size of course. With this table, and about 20 million rows, and default sort buffer, execution time drops from about 40 seconds to 10 seconds on my desktop machine:&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-size:x-small;&quot;&gt;&lt;span style=&quot;&quot;&gt;CREATE TABLE t1(&lt;/span&gt;&lt;br style=&quot;&quot;/&gt;&lt;span style=&quot;&quot;&gt;&amp;nbsp; f0 int auto_increment PRIMARY KEY,&lt;/span&gt;&lt;br style=&quot;&quot;/&gt;&lt;span style=&quot;&quot;&gt;&amp;nbsp; f1 int,&lt;/span&gt;&lt;br style=&quot;&quot;/&gt;&lt;span style=&quot;&quot;&gt;&amp;nbsp; f2 varchar(200)&lt;/span&gt;&lt;br style=&quot;&quot;/&gt;&lt;span style=&quot;&quot;&gt;);&lt;/span&gt;&lt;br style=&quot;&quot;/&gt;&lt;br style=&quot;&quot;/&gt;&lt;span style=&quot;&quot;&gt;SELECT * FROM t1 ORDER BY f2 LIMIT 100;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;</description>
         <author>didrik</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-5553694220766703655.post-3357088574885122722</guid>
         <pubDate>Tue, 12 Apr 2011 01:44:00 +0000</pubDate>
      </item>
      <item>
         <title>MySQL 5.6: Index Condition Pushdown</title>
         <link>http://olavsandstaa.blogspot.com/2011/04/mysql-56-index-condition-pushdown.html</link>
         <description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align:left;&quot;&gt;&lt;span style=&quot;font-family:Arial, Helvetica, sans-serif;&quot;&gt;Index Condition Pushdown (ICP) is one of the new optimizer features in the MySQL 5.6.2 milestone release. The goal with Index Condition Pushdown is to move as much as possible of the processing of conditions (mainly WHERE clauses) from the server to the storage engine. Instead of fetching entire rows into the server and then evaluate the conditions in the server, the optimizer &quot;pushes&quot; the parts of condition that can be evaluated using the index down to the storage engine. This gives the storage engine the possibility to filter out non-relevant rows using the index and only return relevant rows to the server. The result of using ICP should be less IO accesses to the base table and fewer accesses from the MySQL &lt;/span&gt;&lt;span style=&quot;font-family:Arial, Helvetica, sans-serif;&quot;&gt;server to the storage engine.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;To explain how Index Condition Pushdown works let us first look at how retrieval of records based on using an index is done without use of Index Condition Pushdown. The main operations that the storage engine and server perform look approximately like this:&lt;br /&gt;&lt;ol style=&quot;text-align:left;&quot;&gt;&lt;li&gt;The storage engine reads the next row by first reading the index tuple and then using the index tuple to locate and read the full row from the base table.&lt;/li&gt;&lt;li&gt;The server will then evaluate the WHERE condition on the row and based on this either use or ignore this row.&lt;/li&gt;&lt;/ol&gt;When Index Condition Pushdown is used, the server will push down the parts of the WHERE condition that can be evaluated using the index to the storage engine. The reading of records from a table will then consist of the following steps:&lt;br /&gt;&lt;br /&gt;&lt;ol style=&quot;text-align:left;&quot;&gt;&lt;li&gt;The storage engine reads the next index tuple from the index.&lt;/li&gt;&lt;li&gt;The storage engine evaluates the pushed index condition using the index tuple. If this condition is not satisfied then the storage engine will proceed to the next entry in the index (go back to step 1). Only when it has found an index entry that satisfies the pushed index condition it will continue to read data from the base table.&lt;/li&gt;&lt;li&gt;The storage engine uses the index entry to locate and read the full row from the table.&lt;/li&gt;&lt;li&gt;The server will evaluate the part of the WHERE condition that has not been pushed down the storage engine and based on this either use or ignore this row.&lt;/li&gt;&lt;/ol&gt;The savings during execution of the query comes from that the storage engine will be able to filter out and drop reading of full rows from the table every time the pushed index condition evaluates to false by just using data from the index.&lt;br /&gt;&lt;br /&gt;The optimizer can use Index Condition Pushdown for queries that need to access full table rows using the range, ref, eq_ref, and ref_or_null access methods. In MySQL 5.6.2 Index Condition Pushdown is implemented for InnoDB and MyISAM.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-size:large;&quot;&gt;How to enable Index Condition Pushdown?&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;div&gt;There is no need to do anything extra in order to use Index Condition Pushdown in MySQL 5.6.2 since it is enabled by default. If you for some reason should want to disable ICP, for instance to be able to compare the execution time with and without ICP, you can use the optimizer_switch system variable to control the use of it. For example to disable ICP you can set:&lt;span style=&quot;font-size:large;&quot;&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;&amp;nbsp; &lt;b style=&quot;&quot;&gt;optimizer_switch='index_condition_pushdown=off';&lt;/b&gt;&lt;br /&gt;This setting can be changed at run-time.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-size:large;&quot;&gt;How to know if Index Condition Pushdown is used?&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;When Index Condition is used, the Extra column in the EXPLAIN of the query will show &quot;Using index condition&quot;.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-size:large;&quot;&gt;&lt;b&gt;Example using Index Condition Pushdown&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;To illustrate the effect of using Index Condition Pushdown a small example is needed. Assume you have the following table with information about the five million people living in Norway:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; CREATE TABLE person (&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;   personid INTEGER PRIMARY KEY,&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; firstname CHAR(20),&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; lastname CHAR(20),&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;   postalcode INTEGER,&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;   age INTEGER,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;   address CHAR(50),&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;   KEY k1 (postalcode,age)&lt;br /&gt;&amp;nbsp;&amp;nbsp; ) ENGINE=InnoDB;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;and you want to find all persons who live close to the city of Bergen (with postal code in the range between 5000 and 5500) and are either 21 or 22 years old. This can be done using the following query:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; SELECT lastname, firstname &lt;br /&gt;&amp;nbsp;&amp;nbsp; FROM person &lt;br /&gt;&amp;nbsp;&amp;nbsp; WHERE postalcode BETWEEN 5000 AND 5500 AND age BETWEEN 21 AND 22;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;The MySQL optimizer will execute this as a range query using the k1 index. The range query will request the storage engine to read all records where &quot;postalcode BETWEEN 5000 AND 5500&quot;.&lt;br /&gt;&lt;br /&gt;If this query was run against MySQL 5.5 (which does not have ICP) or if ICP has been disabled then the storage engine will need to fetch all rows that has postal code between 5000 and 5500. With randomly distributed data in the table this would correspond to 250.000 rows that the storage engine has to read and return to the server for evaluation. The server would then evaluate the complete WHERE clause and filter out the records that does not satisfy the &quot;age BETWEEN 21 AND 22&quot;. This would result in 5.000 records that would be returned to the user.&lt;br /&gt;&lt;br /&gt;With ICP enabled the optimizer will evaluate the WHERE clause and find the parts of it that can be evaluated by using fields from the index. In this case the index contains both the postalcode and the age. Thus, the optimizer will push the entire WHERE clause down to the storage engine. The storage engine can now evaluate the pushed index condition by using the index. So before reading a full row from the base table, the storage engine evaluates the pushed index condition using the index entry. Only those rows that actually qualifies need to be read from the base table. So in this example, the storage engine will evaluate 250.000 index entries but only need to read 5.000 rows from the base table and return those to the server (compared to the 250.000 rows in the case where ICP was not used).&lt;br /&gt;&lt;span style=&quot;font-size:large;&quot;&gt;&lt;span style=&quot;font-size:small;&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-size:large;&quot;&gt;Performance&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;To get some performance numbers I filled the above table with five million entries with random data (uniformly distributed). The resulting size of the database was about 1 GB on disk. First I disabled the use of ICP and measured the time it took to run the query. The default size of the InnoDB buffer pool is too small to fit this table in memory. So with this setting it took about 15 seconds for this query to complete due to a lot of disk accesses. When I increased the size of the InnoDB buffer pool to 1.5 GB the query takes about 1.4 seconds to complete.&lt;br /&gt;&lt;br /&gt;With Index Condition Pushdown enabled it only takes about 90 ms to run the same query both with the default size of the InnoDB buffer pool and the 1.5 GB buffer pool.&lt;br /&gt;&lt;br /&gt;In this example the speedup is 15 times when using Index Condition Pushdown (or 160 times in the case of default InnoDB buffer pool size). This speedup is mainly caused by having to read a much lower number of records from the base table (reduced number of IO operations) and a much lower number of calls from the server to InnoDB.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;</description>
         <author>Olav Sandstå</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-8607946055890352285.post-3490863621797411691</guid>
         <pubDate>Mon, 11 Apr 2011 16:08:00 +0000</pubDate>
      </item>
      <item>
         <title>Problems with subquery transformations</title>
         <link>http://optimize-this.blogspot.com/2010/06/problems-with-subquery-transformations.html</link>
         <description>&lt;div&gt;MySQL transforms &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;IN&lt;/span&gt; predicates into nearly-equivalent &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;EXISTS &lt;/span&gt;predicates, and uses a set of mechanisms to catch all special cases. This transformation, outlined below, also applies to &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;NOT IN&lt;/span&gt; predicates since &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;a NOT IN b&lt;/span&gt; parses to &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;NOT a IN b&lt;/span&gt;. Expressions such as &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;SELECT * FROM table1 WHERE column1 IN (SELECT column2 FROM table2)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;get transformed into&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;SELECT * FROM table1 WHERE EXISTS&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;  ( SELECT column2 FROM table2&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;    WHERE column1 = column2 OR column2 IS NULL )&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;since the latter can be executed more efficiently. For instance an index can be used for accessing only the rows of &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;table2&lt;/span&gt; that will actually participate in the final result. The added &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;WHERE&lt;/span&gt; condition is called a guarded condition, or trigger condition, both terms are used. In order to get correct result, the condition &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;column1 = column2&lt;/span&gt; must be deactivated if we are comparing against a &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;NULL&lt;/span&gt; value for &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;column1&lt;/span&gt;. This guarantees correct results for most cases but there are some additional cases to consider.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If the&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt; IN&lt;/span&gt; predicate is used directly in the &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;WHERE&lt;/span&gt; clause (e.g. not as a sub-expression of &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;IS UNKNOWN&lt;/span&gt;,) a &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;NULL&lt;/span&gt; value is treated as false. There is a special property &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;top_level_item&lt;/span&gt; of an expression node that tells the execution to filter out &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;NULL&lt;/span&gt; values from the subquery. But there are problems with it.&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;It does not propagate through &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;NOT&lt;/span&gt; nodes (or any other nodes for that matter.)&lt;/li&gt;&lt;li&gt;It is only applied to the &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;WHERE&lt;/span&gt; and &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;HAVING&lt;/span&gt; clauses. Since MySQL allows quantified predicates in the &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;SELECT &lt;/span&gt;list - this is not in the SQL standard - an extra hack is needed to make it work. The solution is to add - when &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;top_level_item&lt;/span&gt; is false - another guarded condition as &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;HAVING&lt;/span&gt; clause. Remember that MySQL allows &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;HAVING&lt;/span&gt; without &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;GROUP  BY&lt;/span&gt; - yet again extending the standard. Hence we end up with &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;    SELECT * FROM table1 WHERE EXISTS&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;      ( SELECT column2&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;        FROM table2&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;        WHERE column1 = column2 OR column2 IS NULL&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;        HAVING NOT column2 IS NULL )&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;   &lt;/span&gt;  This will filter out &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;NULL&lt;/span&gt; values causing the result of the subquery to be empty.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;1 and 2 together caused Bug#51070. For row valued predicands, e.g.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;SELECT * FROM t WHERE (a, b) NOT IN (SELECT c, d FROM u)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;there may be partial matches for any two rows. A partial match occurs when the corresponding positions in two rows are either equal, or at least one is &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;NULL&lt;/span&gt;. The design above handles these cases just fine as long as NULL values come from the outer table. When there is a NULL value in the row from the inner table, however, it gets filtered out by the HAVING clause. Hence the subquery, now rewritten into&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;SELECT c, d FROM u WHERE c = a OR b = d &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;HAVING NOT c IS NULL AND NOT d IS NULL&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;yields an empty result and the value of &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;IN&lt;/span&gt; is false, causing &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:'courier new';&quot;&gt;NOT IN&lt;/span&gt; to yield true.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;</description>
         <author>Martin</author>
         <guid isPermaLink="false">tag:blogger.com,1999:blog-4934478127523643312.post-940442527063041041</guid>
         <pubDate>Wed, 30 Jun 2010 01:01:00 +0000</pubDate>
      </item>
   </channel>
</rss>
<!-- fe8.yql.bf1.yahoo.com compressed/chunked Thu Oct  1 23:05:38 UTC 2015 -->
