<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>code.openark.org &#187; Books</title>
	<atom:link href="http://code.openark.org/blog/tag/books/feed" rel="self" type="application/rss+xml" />
	<link>http://code.openark.org/blog</link>
	<description>Blog by Shlomi Noach</description>
	<lastBuildDate>Thu, 09 Sep 2010 16:15:02 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>On restoring a single table from mysqldump</title>
		<link>http://code.openark.org/blog/mysql/on-restoring-a-single-table-from-mysqldump</link>
		<comments>http://code.openark.org/blog/mysql/on-restoring-a-single-table-from-mysqldump#comments</comments>
		<pubDate>Tue, 01 Dec 2009 08:25:00 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Backup]]></category>
		<category><![CDATA[Books]]></category>
		<category><![CDATA[mysqldump]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[scripts]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=1630</guid>
		<description><![CDATA[Following Restore one table from an ALL database dump and Restore a Single Table From mysqldump, I would like to add my own thoughts and comments on the subject. I also wish to note performance issues with the two suggested solutions, and offer improvements. Problem relevance While the problem is interesting, I just want to [...]]]></description>
			<content:encoded><![CDATA[<p>Following <a href="http://everythingmysql.ning.com/profiles/blogs/restore-one-table-from-an-all">Restore one table from an ALL database dump</a> and <a href="http://gtowey.blogspot.com/2009/11/restore-single-table-from-mysqldump.html">Restore a Single Table From mysqldump</a>, I would like to add my own thoughts and comments on the subject.</p>
<p>I also wish to note performance issues with the two suggested solutions, and offer improvements.</p>
<h4>Problem relevance</h4>
<p>While the problem is interesting, I just want to note that it is relevant in very specific database dimensions. Too small &#8211; and it doesn&#8217;t matter how you solve it (e.g. just open vi/emacs and copy+paste). Too big &#8211; and it would not be worthwhile to restore from <em>mysqldump</em> anyway. I would suggest that the problem is interesting in the whereabouts of a few dozen GB worth of data.</p>
<h4>Problem recap</h4>
<p>Given a dump file (generated by mysqldump), how do you restore a single table, without making any changes to other tables?</p>
<p>Let&#8217;s review the two referenced solutions. I&#8217;ll be using the <a href="http://dev.mysql.com/doc/employee/en/employee.html">employees db</a> on <a href="https://launchpad.net/mysql-sandbox">mysql-sandbox</a> for testing. I&#8217;ll choose a very small table to restore: <strong>departments</strong> (only a few rows in this table).</p>
<h4>Security based solution</h4>
<p><a href="http://everythingmysql.ning.com/profiles/blogs/restore-one-table-from-an-all"><strong>Chris</strong></a> offers to create a special purpose account, which will only have write (CREATE, INSERT, etc.) privileges on the particular table to restore. Cool hack! But, I&#8217;m afraid, not too efficient, for two reasons:<span id="more-1630"></span></p>
<ol>
<li>MySQL needs to process all irrelevant queries (ALTER, INSERT, &#8230;) only to disallow them due to access violation errors.</li>
<li>Assuming restore is from remote host, we overload the network with all said irrelevant queries.</li>
</ol>
<p>Just how inefficient? Let&#8217;s time it:</p>
<blockquote>
<pre>mysql&gt; grant usage on *.* to 'restoreuser'@'localhost';
mysql&gt; grant select on *.* to 'restoreuser'@'localhost';
mysql&gt; grant all on employees.departments to 'restoreuser'@'localhost';

$ time mysql --user=restoreuser --socket=/tmp/mysql_sandbox21701.sock --force employees &lt; /tmp/employees.sql
...
ERROR 1142 (42000) at line 343: INSERT command denied to user 'restoreuser'@'localhost' for table 'titles'
ERROR 1142 (42000) at line 344: ALTER command denied to user 'restoreuser'@'localhost' for table 'titles'
...
(lot's of these messages)
...

real    <strong>0m31.945s</strong>
user    0m6.328s
sys     0m0.508s</pre>
</blockquote>
<p>So, at about <strong>30</strong> seconds to restore a 9 rows table.</p>
<h4>Text filtering based solution.</h4>
<p><a href="http://gtowey.blogspot.com/2009/11/restore-single-table-from-mysqldump.html"><strong>gtowey</strong></a> offers parsing the dump file beforehand:</p>
<ul>
<li>First, parse with <em>grep</em>, to detect rows where tables are referenced within dump file</li>
<li>Second, parse with <em>sed</em>, extracting relevant rows.</li>
</ul>
<p>Let&#8217;s time this one:</p>
<blockquote>
<pre>$ time grep -n 'Table structure' /tmp/employees.sql
23:-- Table structure for table `departments`
48:-- Table structure for table `dept_emp`
89:-- Table structure for table `dept_manager`
117:-- Table structure for table `employees`
161:-- Table structure for table `salaries`
301:-- Table structure for table `titles`

real    <strong>0m0.397s</strong>
user    0m0.232s
sys     0m0.164s

$ time sed -n 23,48p /tmp/employees.sql | ./use employees

real    <strong>0m0.562s</strong>
user    0m0.380s
sys     0m0.176s</pre>
</blockquote>
<p>Much faster: about <strong>1</strong> second, compared to <strong>30</strong> seconds from above.</p>
<p>Nevertheless, I find two issues here:</p>
<ol>
<li>A correctness problem: this solution somewhat assumes that there&#8217;s only a single table with desired name. I say &#8220;somewhat&#8221; since it leaves this for the user.</li>
<li>An efficiency problem: it reads the dump file <em>twice</em>. First parsing it with <em>grep</em>, then with <em>sed</em>.</li>
</ol>
<h4>A third solution</h4>
<p><em>sed</em> is much stronger than presented. In fact, the inquiry made by <em>grep</em> in gtowey&#8217;s solution can be easily handled by <em>sed</em>:</p>
<blockquote>
<pre>$ time sed -n "/^-- Table structure for table \`departments\`/,/^-- Table structure for table/p" /tmp/employees.sql | ./use employees

real    <strong>0m0.573s</strong>
user    0m0.416s
sys     0m0.152s</pre>
</blockquote>
<p>So, the <strong>&#8220;/^&#8211; Table structure for table \`departments\`/,/^&#8211; Table structure for table/p&#8221;</strong> part tells <em>sed</em> to only print those rows starting from the <strong>departments</strong> table structure, and ending in the next table structure (this is for clarity: had department been the last table, there would not be a next table, but we could nevertheless solve this using other anchors).</p>
<p>And, we only do it in <strong>0.57</strong> seconds: about half the time of previous attempt.</p>
<p>Now, just to be more correct, we only wish to consider the <strong>employees.department</strong> table. So, <em>assuming</em> there&#8217;s more than one database dumped (and, by consequence, <strong>USE</strong> statements in the dump-file), we use:</p>
<blockquote>
<pre>cat /tmp/employees.sql | sed -n "/^USE \`employees\`/,/^USE \`/p" | sed -n "/^-- Table structure for table \`departments\`/,/^-- Table structure for table/p" | ./use employees</pre>
</blockquote>
<h4>Further notes</h4>
<ul>
<li>All tests used warmed-up caches.</li>
<li>The sharp eyed readers would notice that <strong>departments</strong> is the first table in the dump file. Would that give an unfair advantage to the parsing-based restore methods? The answer is no. I&#8217;ve created an <strong>xdepartments</strong> table, to be located at the end of the dump. The difference in time is neglectful and inconclusive; we&#8217;re still at ~0.58-0.59 seconds. The effect will be more visible on really large dumps; but then, so would the security-based effects.</li>
</ul>
<p>[<strong>UPDATE</strong>: see also following similar post: <a href="http://blog.tsheets.com/2008/tips-tricks/extract-a-single-table-from-a-mysqldump-file.html">Extract a Single Table from a mysqldump File</a>]</p>
<h4>Conclusion</h4>
<p><a href="http://www.amazon.com/Classic-Shell-Scripting-Arnold-Robbins/dp/0596005954/ref=sr_1_1"><img class="alignright" title="classic-shell-scripting" src="http://code.openark.org/blog/wp-content/uploads/2009/12/classic-shell-scripting.png" alt="classic-shell-scripting" width="144" height="189" /></a>Its is always best to test on large datasets, to get a feel on performance.</p>
<p>It&#8217;s best to save MySQL the trouble of parsing &amp; ignoring statements. Scripting utilities like <em>sed</em>, <em>awk</em> &amp; <em>grep</em> have been around for ages, and are well optimized. They excel at text processing.</p>
<p>I&#8217;ve used <em>sed</em> many times in transforming dump outputs; for example, in converting MyISAM to InnoDB tables; to convert Antelope InnoDB tables to Barracuda format, etc. grep &amp; awk are also very useful.</p>
<p>May I recommend, at this point, reading <a href="http://www.amazon.com/Classic-Shell-Scripting-Arnold-Robbins/dp/0596005954/ref=sr_1_1">Classic Shell Scripting</a>, a very easy to follow book, which lists the most popular command line utilities like <em>grep</em>, <em>sed</em>, <em>awk</em>, <em>sort</em>, (countless more) and shell scripting in general. While most of these utilities are well known, the book excels in providing suprisingly practical, simple solution to common tasks.</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/on-restoring-a-single-table-from-mysqldump/feed</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>High Performance MySQL &#8211; a book to re-read</title>
		<link>http://code.openark.org/blog/mysql/high-performance-mysql-a-book-to-re-read</link>
		<comments>http://code.openark.org/blog/mysql/high-performance-mysql-a-book-to-re-read#comments</comments>
		<pubDate>Sun, 27 Sep 2009 07:56:59 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Books]]></category>
		<category><![CDATA[Performance]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=1346</guid>
		<description><![CDATA[I first read High Performance MySQL, 2nd edition about a year ago, when it first came out. I since re-read a few pages on occasion. In my previous posts I&#8217;ve suggested ways to improve upon the common ranking solution. Very innovative stuff! Or&#8230; so I thought. I happened to browse through the book today, and [...]]]></description>
			<content:encoded><![CDATA[<p>I first read <a href="http://www.amazon.com/High-Performance-MySQL-Optimization-Replication/dp/0596101716">High Performance MySQL, 2nd edition</a> about a year ago, when it first came out. I since re-read a few pages on occasion.</p>
<p>In my previous posts I&#8217;ve suggested ways to improve upon the common ranking solution. Very innovative stuff! Or&#8230; so I thought.</p>
<p>I happened to browse through the book today, and a section on User Variables caught my eye. &#8220;<em>Let&#8217;s see if I get get some insight</em>&#8220;, I thought to myself. Imagine my surprise when I realized almost everything I&#8217;ve suggested is discussed in this modest section, black on white, sitting on my bookshelf for over a year!</p>
<p>I have read it a year back, have forgotten all about it, have re-invented stuff already solved and discussed&#8230; Oh, for more brain capacity&#8230;</p>
<p>To be honest, this has happened to me more than once in the past few months; I&#8217;m taking the habit of browsing the web when I&#8217;m looking for answers to my problems; I forget that this book contains the answers to so many common, practical MySQL problems, and does so in a very direct and helpful manner.</p>
<p>So, yet again, thumbs up to <em>High Performance MySQL</em>. Really a must book. Get it if you haven&#8217;t already!</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/high-performance-mysql-a-book-to-re-read/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Unwalking a string with GROUP_CONCAT</title>
		<link>http://code.openark.org/blog/mysql/unwalking-a-string-with-group_concat</link>
		<comments>http://code.openark.org/blog/mysql/unwalking-a-string-with-group_concat#comments</comments>
		<pubDate>Tue, 16 Jun 2009 05:54:49 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Books]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=840</guid>
		<description><![CDATA[&#8220;Walking a string&#8221; is an SQL technique to convert a single value into multiple rows result set. For example, walking the string &#8216;hello&#8217; results with 5 rows, each of which contains a single character from the text. I&#8217;ll present a brief example of walking a string, and then show how to &#8220;unwalk&#8221; the string: do [...]]]></description>
			<content:encoded><![CDATA[<p>&#8220;Walking a string&#8221; is an SQL technique to convert a single value into multiple rows result set. For example, walking the string <strong>&#8216;hello&#8217;</strong> results with 5 rows, each of which contains a single character from the text.</p>
<p>I&#8217;ll present a brief example of walking a string, and then show how to &#8220;unwalk&#8221; the string: do the reverse operation.</p>
<p>To walk a string, an integers table is required (or this could be a good use for <a href="http://www.mysqlconf.com/mysql2009/public/schedule/detail/6891">SeqEngine</a>):<span id="more-840"></span></p>
<blockquote>
<pre>CREATE TABLE `int_table` (
  `int_col` int(11) NOT NULL,
  PRIMARY KEY  (`int_col`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1

-- ...
-- INSERTS follow here
-- ...

mysql&gt; SELECT * FROM int_table;
+---------+
| int_col |
+---------+
|       0 |
|       1 |
|       2 |
|       3 |
|       4 |
|       5 |
|       6 |
|       7 |
|       8 |
|       9 |
+---------+
10 rows in set (0.00 sec)</pre>
</blockquote>
<p>To convert a string to rows of characters, we join the text with the integers table (we assume there are enough numbers for covering the length of the text):</p>
<blockquote>
<pre>mysql&gt; SELECT
         SUBSTRING(s, int_col+1, 1) AS c
       FROM int_table, (SELECT 'hello' AS s) sel1
       WHERE int_col &lt; char_length(s);
+---+
| c |
+---+
| h |
| e |
| l |
| l |
| o |
+---+
5 rows in set (0.00 sec)</pre>
</blockquote>
<p>More on this can be found in the excellent <a href="http://www.amazon.com/Cookbook-Cookbooks-OReilly-Anthony-Molinaro/dp/0596009763">SQL Cookbook</a>.</p>
<h4>Unwalking the string</h4>
<p>Doing the inverse action &#8211; combining the string back from the multiple rows, can be easily done using <strong>GROUP_CONCAT</strong>. It&#8217;s interesting to learn that<strong> GROUP_CONCAT</strong> does not actually require any <strong>GROUP BY</strong> clause. When no such clause is provided in the SQL query, all searched rows are used.</p>
<p>Let&#8217;s assume now that we have a table of character values, which we want to concatenate back to a complete string. We can easily build this table:</p>
<blockquote>
<pre>CREATE TABLE characters AS
  SELECT
    SUBSTRING(s, int_col+1, 1) AS c
  FROM int_table, (SELECT 'hello' AS s) sel1
  WHERE int_col &lt; char_length(s);</pre>
</blockquote>
<p>To reconstruct the text, we simply use MySQL&#8217;s <strong>GROUP_CONCAT</strong> with an empty separator:</p>
<blockquote>
<pre>mysql&gt; SELECT GROUP_CONCAT(c separator '') AS s FROM characters;
+-------+
| s     |
+-------+
| hello |
+-------+
1 row in set (0.00 sec)</pre>
</blockquote>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/unwalking-a-string-with-group_concat/feed</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>7 ways to convince MySQL to use the right index</title>
		<link>http://code.openark.org/blog/mysql/7-ways-to-convince-mysql-to-use-the-right-index</link>
		<comments>http://code.openark.org/blog/mysql/7-ways-to-convince-mysql-to-use-the-right-index#comments</comments>
		<pubDate>Thu, 02 Apr 2009 16:06:32 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Books]]></category>
		<category><![CDATA[Execution plan]]></category>
		<category><![CDATA[Indexing]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[Syntax]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=695</guid>
		<description><![CDATA[Sometimes MySQL gets it wrong. It doesn&#8217;t use the right index. It happens that MySQL generates a query plan which is really bad (EXPLAIN says it&#8217;s going to explore some 10,000,000 rows), when another plan (soon to show how was generated) says: &#8220;Sure, I can do that with 100 rows using a key&#8221;. A true [...]]]></description>
			<content:encoded><![CDATA[<p>Sometimes MySQL gets it wrong. It doesn&#8217;t use the right index.</p>
<p>It happens that MySQL generates a query plan which is really bad (EXPLAIN says it&#8217;s going to explore some 10,000,000 rows), when another plan (soon to show how was generated) says: &#8220;Sure, I can do that with 100 rows using a key&#8221;.</p>
<h4>A true story</h4>
<p>A customer had issues with his database. Queries were taking 15 minutes to complete, and the db in general was not responsive. Looking at the slow query log, I found the criminal query. Allow me to bring you up to speed:</p>
<p>A table is defined like this:</p>
<blockquote>
<pre>CREATE TABLE t (
  id INT UNSIGNED AUTO_INCREMENT,
  type INT UNSIGNED,
  level TINYINT unsigned,
  ...
  PRIMARY KEY(id),
  KEY `type` (type)
) ENGINE=InnoDB;</pre>
</blockquote>
<p>The offending query was this:</p>
<blockquote>
<pre>SELECT id FROM data
WHERE type=12345 AND level &gt; 3
ORDER BY id</pre>
</blockquote>
<p>The facts were:</p>
<ul>
<li>`t` has about 10,000,000 rows.</li>
<li>The index on `type` is selective: about 100 rows per value on average.</li>
<li>The query took a long time to complete.</li>
<li>EXPLAIN has shown that MySQL uses the PRIMARY KEY, hence searches 10,000,000 rows, filtered &#8220;using where&#8221;.</li>
<li>The <em>other</em> EXPLAIN has shown that by using the `type` key, only 110 rows are expected, to be filtered &#8220;using where&#8221;, then sorted &#8220;using filesort&#8221;</li>
</ul>
<p>So MySQL acknowledged it was generating the wrong plan. The <em>other</em> plan was better by its own standards.</p>
<h4>Solving the problem</h4>
<p>Let&#8217;s walk through 7 ways to solve the problem, starting with the more aggressive solutions, refining to achieve desired behavior through subtle changes.<span id="more-695"></span></p>
<h4>Solution #1: OPTIMIZE</h4>
<p>If MySQL got it wrong, it may be because the table was frequently changed. This affects the statistics. If we can spare the time (table is locked during that time), we could help out by rebuilding the table.</p>
<h4>Solution #2: ANALYZE</h4>
<p>ANALYZE TABLE is less time consuming, in particular on InnoDB, where it is barely noticed. An ANALYZE will update the index statistics and help out in generating better query plans.</p>
<p>But hold on, the above two solutions are fine, but in the given case, MySQL <em>already</em> acknowledges better plans are at hand. The fact was I tried to run ANALYZE a few times, to no avail.</p>
<h4>Solution #3: USE INDEX</h4>
<p>Since the issue was urgent, my first thought went for the ultimate weapon:</p>
<blockquote>
<pre>SELECT id FROM data USE INDEX(type)
WHERE type=12345 AND level &gt; 3
ORDER BY id</pre>
</blockquote>
<p>This instructs MySQL to only consider the indexes listed; in our example, I only want MySQL to consider using the `type` index. It is using this method that generated the <em>other</em> (good) EXPLAIN result. I could have gone even more ruthless and ask for FORCE INDEX.</p>
<h4>Solution #4: IGNORE INDEX</h4>
<p>A similar approach would be to explicitly negate the use of the PRIMARY KEY, like this:</p>
<blockquote>
<pre>SELECT id FROM data IGNORE INDEX(PRIMARY)
WHERE type=12345 AND level &gt; 3
ORDER BY id</pre>
</blockquote>
<h4>A moment of thinking</h4>
<p>The above solutions are &#8220;ugly&#8221;, in the sense that this is not standard SQL. It&#8217;s too MySQL specific.</p>
<p>I&#8217;ve asked the programmers to do a quick rewrite, and had a few moments to consider: why did MySQL insist on using the PRIMARY KEY. Was it because I&#8217;ve asked it for the `id` column only? I rewrote as follows:</p>
<blockquote>
<pre>SELECT id, type, level FROM data
WHERE type=12345 AND level &gt; 3
ORDER BY id</pre>
</blockquote>
<p>Nope. EXPLAIN got me the same bad plan. Then it must be the ORDER BY clause:</p>
<blockquote>
<pre>SELECT id FROM data
WHERE type=12345 AND level &gt; 3</pre>
</blockquote>
<p>Sure enough, EXPLAIN now  indicates using the `type` index, only reading 110 rows. So MySQL preferred to scan 10,000,000 rows, just so that the rows are generated in the right ORDER, and so no sorting is required, when it could have read 110 rows (where each row is a mere INT) and sort them in no time.</p>
<p>Armed with this knowledge, a few more options come at hand.</p>
<h4>Solution #5:Move some logic to the application</h4>
<p>At about that point I got a message that the programmers were unable to add the USE INDEX part. Why? They were using the EJB framework, which limits your SQL-like queries to something very generic. Well, you can always drop the ORDER BY part and sort on the application side. That isn&#8217;t fun, but it&#8217;s been done.</p>
<h4>Solution #6: Negate use of PRIMARY KEY</h4>
<p>Can we force MySQL to use the `type` index, retain the ORDER BY, and do it all with standard SQL? Sure. The following query does this:</p>
<blockquote>
<pre>SELECT id, type, level FROM data
WHERE type=12345 AND level &gt; 3
ORDER BY id+0</pre>
</blockquote>
<p>id+0 is a function on the `id` column. This makes MySQL unable to utilize the PRIMARY KEY (or any other index on `id`, had there been one).</p>
<p>In his book &#8220;<a title="SQL Tuning by Dan Tow" href="http://www.amazon.com/SQL-Tuning-Dan-Tow/dp/0596005733">SQL Tuning</a>&#8220;, Dan Tow dedicates a chapter on hints and tips like the above. He shows how to control the use or non-use of indexes, the order by which subqueries are calculated, and more.</p>
<p>Unfortunately, the EJB specification said this was not allowed. You could not ORDER BY a fucntion. Only on normal column.</p>
<h4>Solution #7: Make MySQL think the problem is harder than it really is</h4>
<p>Almost out of options. Just a moment before settling for sorting on the application side, another issue can be considered: since MySQL was fooled once, can it be fooled again to make things right? Can we fool it to believe that the PRIMARY KEY would not be worthwhile to use? The following query does this:</p>
<blockquote>
<pre>SELECT id, type, level FROM data
WHERE type=12345 AND level &gt; 3
ORDER BY id, type, level</pre>
</blockquote>
<p>Let&#8217;s reflect on this one. What is the order by which the rows are returned now? Answer: exactly as before. Since `id` is PRIMARY KEY, it is also UNIQUE, so no two `id` values are the same. Therefore, the secondary sorting column is redudant, and so is the following one. We get exactly the same result as &#8220;ORDER BY id&#8221;.</p>
<p>But MySQL didn&#8217;t catch this. This query caused MySQL to say: <em>&#8220;Mmmmm. &#8216;ORDER BY id, type, level&#8217; is not doable with the PRIMARY KEY only. Well, in this case, I had better used the `type` index&#8221;</em>. Is this a weakness of MySQL? I guess so. Maybe it will be fixed in the future. But this was the fix that made the day.</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/7-ways-to-convince-mysql-to-use-the-right-index/feed</wfw:commentRss>
		<slash:comments>18</slash:comments>
		</item>
	</channel>
</rss>
