<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-9191231274999121063</id><updated>2012-02-16T02:22:57.264-08:00</updated><category term='subquery'/><category term='optimizer_trace_features'/><category term='new feature'/><category term='mysql'/><category term='optimizer trace'/><category term='unique_subquery'/><category term='explain'/><category term='range'/><category term='mysql 5.6'/><category term='optimizer_trace_max_mem_size'/><category term='benchmark'/><category term='dbt3'/><category term='optimizer_trace'/><title type='text'>About MySQL development (Optimizer mainly)</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://guilhembichot.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9191231274999121063/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://guilhembichot.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Guilhem Bichot</name><uri>http://www.blogger.com/profile/11253623513851616256</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://3.bp.blogspot.com/-2GhSYpO8Jno/Tom_uIPnXlI/AAAAAAAAABI/4hERSI-bjxg/s220/Gray%2527s_skull.png'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>4</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-9191231274999121063.post-6442720126303134298</id><published>2012-02-09T07:51:00.000-08:00</published><updated>2012-02-09T08:37:22.284-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='optimizer trace'/><category scheme='http://www.blogger.com/atom/ns#' term='optimizer_trace'/><category scheme='http://www.blogger.com/atom/ns#' term='mysql 5.6'/><category scheme='http://www.blogger.com/atom/ns#' term='benchmark'/><title type='text'>Optimizer tracing used by others!</title><content type='html'>In a previous post, I had explained &lt;a href="http://guilhembichot.blogspot.com/2011/09/optimizer-tracing-how-to-configure-it.html"&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 href="http://dimitrik.free.fr/blog/archives/2012/01/mysql-performance-overhead-of-optimizer-tracing-in-mysql-56.html"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9191231274999121063-6442720126303134298?l=guilhembichot.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://guilhembichot.blogspot.com/feeds/6442720126303134298/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://guilhembichot.blogspot.com/2012/02/in-previous-post-i-had-explained-how-to.html#comment-form' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9191231274999121063/posts/default/6442720126303134298'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9191231274999121063/posts/default/6442720126303134298'/><link rel='alternate' type='text/html' href='http://guilhembichot.blogspot.com/2012/02/in-previous-post-i-had-explained-how-to.html' title='Optimizer tracing used by others!'/><author><name>Guilhem Bichot</name><uri>http://www.blogger.com/profile/11253623513851616256</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://3.bp.blogspot.com/-2GhSYpO8Jno/Tom_uIPnXlI/AAAAAAAAABI/4hERSI-bjxg/s220/Gray%2527s_skull.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9191231274999121063.post-4377293817366385411</id><published>2011-11-29T06:37:00.000-08:00</published><updated>2011-11-30T03:45:40.616-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='unique_subquery'/><category scheme='http://www.blogger.com/atom/ns#' term='explain'/><category scheme='http://www.blogger.com/atom/ns#' term='dbt3'/><category scheme='http://www.blogger.com/atom/ns#' term='subquery'/><title type='text'>Understanding the unique_subquery optimization</title><content type='html'>If you use the EXPLAIN SELECT statement to see how your subqueries are treated by MySQL, you may sometimes meet the "unique_subquery" optimization. Here is how the &lt;a href="http://dev.mysql.com/doc/refman/5.5/en/explain-output.html"&gt;manual&lt;/a&gt; describes it:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;"&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".&lt;/blockquote&gt;Few weeks ago, while I was reviewing a patch fixing a &lt;a href="http://bazaar.launchpad.net/%7Emysql/mysql-server/trunk/revision/jorgen.loland@oracle.com-20110929124732-t0s1x214cu3nkngu"&gt;bug&lt;/a&gt; in unique_subquery, I got a "simplification" 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="background: none repeat scroll 0% 0% black; color: white; height: 100%; overflow: auto; width: 100%;"&gt;&lt;pre&gt;&lt;span style="font-size: small;"&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="color: red;"&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="background: none repeat scroll 0% 0% black; color: white; height: 100%; overflow: auto; width: 100%;"&gt;&lt;pre&gt;&lt;span style="font-size: small;"&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="color: red;"&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="background: none repeat scroll 0% 0% black; color: white; height: 100%; overflow: auto; width: 100%;"&gt;&lt;pre&gt;&lt;span style="font-size: small;"&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="color: red;"&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 "unique_subquery" becomes "eq_ref". The used index is the same (the primary key of the "supplier" table). The optimizer has the same notion of unicity: "unique_subquery" and "eq_ref" 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 "eq_ref" as it was with "unique_subquery".&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 "part" table ("type=ALL" means "table scan") . The "rows" column of EXPLAIN suggests that we are going to have 199,498 rows of "part".&lt;/li&gt;&lt;li&gt;Second line of EXPLAIN output: for each row from the "part" table, we do an index lookup ("ref") into the "i_ps_partkey" index of the "partsupp" table; apparently such lookup will find two rows ("rows=2").&lt;/li&gt;&lt;li&gt;At this point, we have a row made of needed columns of "part" and of "partsupp". 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 "Using where" indicates).&lt;/li&gt;&lt;li&gt;Then we evaluate the &lt;i&gt;WHERE&lt;/i&gt; clause and thus the "&lt;i&gt;NOT IN (subquery)&lt;/i&gt;" predicate (the "DEPENDENT SUBQUERY"). 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="background: none repeat scroll 0% 0% black; color: white; height: 100%; overflow: auto; width: 100%;"&gt;&lt;pre&gt;&lt;span style="font-size: small;"&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="color: red;"&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="background: none repeat scroll 0% 0% black; color: white; height: 100%; overflow: auto; width: 100%;"&gt;&lt;pre&gt;&lt;span style="font-size: small;"&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 "IN(non correlated subquery)" to "EXISTS(correlated subquery)", yielding this:&lt;br /&gt;&lt;br /&gt;&lt;div style="background: none repeat scroll 0% 0% black; color: white; height: 100%; overflow: auto; width: 100%;"&gt;&lt;pre&gt;&lt;span style="font-size: small;"&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="background: none repeat scroll 0% 0% black; color: white; height: 100%; overflow: auto; width: 100%;"&gt;&lt;pre&gt;&lt;span style="font-size: small;"&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 ("if group_by do this", "if order_by do this", "if left_join do this": 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="background: none repeat scroll 0% 0% black; color: white; height: 100%; overflow: auto; width: 100%;"&gt;&lt;pre&gt;&lt;span style="font-size: small;"&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="color: red;"&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="background: none repeat scroll 0% 0% black; color: white; height: 100%; overflow: auto; width: 100%;"&gt;&lt;pre&gt;&lt;span style="font-size: small;"&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 ("&amp;lt;primary_index_lookup&amp;gt;"), 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 "index_subquery" optimization applying to non-unique indices. And it's worth having, for the same reasons.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9191231274999121063-4377293817366385411?l=guilhembichot.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://guilhembichot.blogspot.com/feeds/4377293817366385411/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://guilhembichot.blogspot.com/2011/11/understanding-uniquesubquery.html#comment-form' title='1 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9191231274999121063/posts/default/4377293817366385411'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9191231274999121063/posts/default/4377293817366385411'/><link rel='alternate' type='text/html' href='http://guilhembichot.blogspot.com/2011/11/understanding-uniquesubquery.html' title='Understanding the unique_subquery optimization'/><author><name>Guilhem Bichot</name><uri>http://www.blogger.com/profile/11253623513851616256</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://3.bp.blogspot.com/-2GhSYpO8Jno/Tom_uIPnXlI/AAAAAAAAABI/4hERSI-bjxg/s220/Gray%2527s_skull.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9191231274999121063.post-1139226807338979871</id><published>2011-10-04T15:40:00.000-07:00</published><updated>2011-10-04T06:40:14.407-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='optimizer trace'/><category scheme='http://www.blogger.com/atom/ns#' term='new feature'/><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='optimizer_trace'/><category scheme='http://www.blogger.com/atom/ns#' term='optimizer_trace_max_mem_size'/><category scheme='http://www.blogger.com/atom/ns#' term='mysql 5.6'/><category scheme='http://www.blogger.com/atom/ns#' term='optimizer_trace_features'/><category scheme='http://www.blogger.com/atom/ns#' term='explain'/><category scheme='http://www.blogger.com/atom/ns#' term='range'/><title type='text'>Optimizer tracing: how to configure it</title><content type='html'>In this &lt;a href="http://jorgenloland.blogspot.com/2011/10/optimizer-tracing-query-execution-plan.html"&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 href="http://dev.mysql.com/doc/refman/5.6/en/news-5-6-3.html"&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="background: none repeat scroll 0% 0% black; color: white; height: 100%; overflow: auto; width: 100%;"&gt;&lt;pre&gt;&lt;span style="font-size: small;"&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 "&lt;i&gt;mysql&lt;/i&gt;" command-line client, the pager to "&lt;i&gt;less&lt;/i&gt;" (I'm using Unix):&lt;br /&gt;&lt;br /&gt;&lt;div style="background: none repeat scroll 0% 0% black; color: white; height: 100%; overflow: auto; width: 100%;"&gt;&lt;pre&gt;&lt;span style="font-size: small;"&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 "&lt;i&gt;less&lt;/i&gt;" 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 "&lt;i&gt;less&lt;/i&gt;" 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 "&lt;i&gt;greedy search"&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: "please do not trace this and that". In my case, it would be "please do not trace greedy search".&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="background: none repeat scroll 0% 0% black; color: white; height: 100%; overflow: auto; width: 100%;"&gt;&lt;pre&gt;&lt;span style="font-size: small;"&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="background: none repeat scroll 0% 0% black; color: white; height: 100%; overflow: auto; width: 100%;"&gt;&lt;pre&gt;&lt;span style="font-size: small;"&gt;mysql&amp;gt; SET OPTIMIZER_TRACE="ENABLED=ON,END_MARKER=ON";&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;&lt;span style="font-size: small;"&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 "&lt;i&gt;Using where&lt;/i&gt;" and others don't. "&lt;i&gt;Using where&lt;/i&gt;" 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="background: none repeat scroll 0% 0% black; color: white; height: 100%; overflow: auto; width: 100%;"&gt;&lt;pre&gt;&lt;span style="font-size: small;"&gt;mysql&amp;gt; SET OPTIMIZER_TRACE_FEATURES="GREEDY_SEARCH=OFF";&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="background: none repeat scroll 0% 0% black; color: white; height: 100%; overflow: auto; width: 100%;"&gt;&lt;pre&gt;&lt;span style="font-size: small;"&gt;mysql&amp;gt; SET OPTIMIZER_TRACE="END_MARKER=OFF";&lt;br /&gt;mysql&amp;gt; SELECT TRACE INTO DUMPFILE "/tmp/trace.json" 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="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-4nRBJC6RyRQ/Tom7NqxGISI/AAAAAAAAAAQ/_BY0hDJep5k/s1600/capt2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="http://1.bp.blogspot.com/-4nRBJC6RyRQ/Tom7NqxGISI/AAAAAAAAAAQ/_BY0hDJep5k/s640/capt2.png" width="491" /&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;"considered_execution_plans"&lt;/i&gt;), replacing it with just an ellipsis ("&lt;i&gt;...&lt;/i&gt;")! Then there is what I wanted to see: the part after greedy search, named "&lt;i&gt;attached_conditions_summary"&lt;/i&gt;, which describes what condition is behind each "&lt;i&gt;Using where"&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;"JOIN ON&lt;/i&gt;" 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;"IS NOT NULL&lt;/i&gt;" 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 href="http://forge.mysql.com/wiki/MySQL_Internals_Optimizer"&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;"range checked for each record"&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 href="http://forge.mysql.com/wiki/MySQL_Internals_Optimizer_tracing"&gt;chapter of the "MySQL Internals" manual&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9191231274999121063-1139226807338979871?l=guilhembichot.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://guilhembichot.blogspot.com/feeds/1139226807338979871/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://guilhembichot.blogspot.com/2011/09/optimizer-tracing-how-to-configure-it.html#comment-form' title='1 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9191231274999121063/posts/default/1139226807338979871'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9191231274999121063/posts/default/1139226807338979871'/><link rel='alternate' type='text/html' href='http://guilhembichot.blogspot.com/2011/09/optimizer-tracing-how-to-configure-it.html' title='Optimizer tracing: how to configure it'/><author><name>Guilhem Bichot</name><uri>http://www.blogger.com/profile/11253623513851616256</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://3.bp.blogspot.com/-2GhSYpO8Jno/Tom_uIPnXlI/AAAAAAAAABI/4hERSI-bjxg/s220/Gray%2527s_skull.png'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-4nRBJC6RyRQ/Tom7NqxGISI/AAAAAAAAAAQ/_BY0hDJep5k/s72-c/capt2.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9191231274999121063.post-9192408677861980784</id><published>2011-10-03T06:52:00.000-07:00</published><updated>2011-10-03T06:52:37.283-07:00</updated><title type='text'>Kick-off</title><content type='html'>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 href="http://www.mysql.com/"&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!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9191231274999121063-9192408677861980784?l=guilhembichot.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://guilhembichot.blogspot.com/feeds/9192408677861980784/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://guilhembichot.blogspot.com/2011/10/kick-off.html#comment-form' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9191231274999121063/posts/default/9192408677861980784'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9191231274999121063/posts/default/9192408677861980784'/><link rel='alternate' type='text/html' href='http://guilhembichot.blogspot.com/2011/10/kick-off.html' title='Kick-off'/><author><name>Guilhem Bichot</name><uri>http://www.blogger.com/profile/11253623513851616256</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://3.bp.blogspot.com/-2GhSYpO8Jno/Tom_uIPnXlI/AAAAAAAAABI/4hERSI-bjxg/s220/Gray%2527s_skull.png'/></author><thr:total>0</thr:total></entry></feed>
