Archive for the ‘explain’ Category

The MySQL range access method explained

Август 23rd, 2011
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's range predicates, which are comparisons using any of =, <=>, IN(), IS NULL, IS NOT NULL, >, <, >=, <=, BETWEEN, !=, <> or LIKE.

Some examples:
SELECT * FROM blog WHERE author_id IN (1, 7, 8, 10)
SELECT * FROM orders WHERE value > 1000

You know that the range access method is used when EXPLAIN shows type=range.

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's way through the index leaves until it reaches the interval end value:



Read more »

PlanetMySQL Voting: Vote UP / Vote DOWN

The meaning of ref=func in MySQL EXPLAIN

Август 17th, 2011
When EXPLAIN shows that a table is accessed using the [eq_]ref access type, we're used to look at the ref column to see where MySQL gets the value to look up from. Usually, we see either "const" 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:

EXPLAIN 

SELECT acc_rec.cust_id, acc_rec.amount, acc_rec.invoicenumber
FROM accounts_receivable as acc_rec
WHERE acc_rec.cust_id=1;
+----+-------------+---------+------+---------+-------+
| id | select_type | table | type | key | ref |
+----+-------------+---------+------+---------+-------+
| 1 | SIMPLE | acc_rec | ref | cust_id | const |
+----+-------------+---------+------+---------+-------+

EXPLAIN
SELECT acc_rec.cust_id, acc_rec.amount, acc_rec.invoicenumber,
customers.firstname, customers.lastname, customers.phone
FROM accounts_receivable AS acc_rec JOIN customers AS cust
ON acc_rec.cust_id = customers.cust_id;
+----+-------------+---------+--------+---------+-----------------+
| id | select_type | table | type | key | ref |
+----+-------------+---------+--------+---------+-----------------+
| 1 | SIMPLE | acc_rec | ALL | NULL | NULL |
| 1 | SIMPLE | cust | eq_ref | PRIMARY | acc_rec.cust_id |
+----+-------------+---------+--------+---------+-----------------+

But what if ref shows the value "func"? In this case, the value used as input to [eq_]ref is the output of some function. A few examples:

Read more »

PlanetMySQL Voting: Vote UP / Vote DOWN

On Covering Indexes and Their Impact on Performance

Июль 3rd, 2011
The purpose of this post is to describe what covering indexes are and how they can be used to improve the performance of queries. People mostly use indexes to filter or sort the results but not much thought is given to actually reduce the disk reads by using proper indexes. So I will show you how to reduce disk reads and hence improve the performance of queries by utilizing indexes properly.
PlanetMySQL Voting: Vote UP / Vote DOWN

Optimizing UPDATE and DELETE statements

Февраль 24th, 2011

While most people look at performance optimizations for SELECT statements, UPDATE and DELETE statements are often overlooked. These can benefit from the principles of analyzing the Query Execution Plan (QEP). You can only run an EXPLAIN on a SELECT statement, however it’s possible to rewrite an UPDATE or DELETE statement to perform like a SELECT statement.

To optimize an UPDATE, look at the WHERE clause. If you are using the PRIMARY KEY, no further analysis is necessary. If you are not, it is of benefit to rewrite your UPDATE statement as a SELECT statement and obtain a QEP as previously detailed to ensure optimal indexes are used. For example:

UPDATE t
SET	c1 = ‘x’, c2 = ‘y’, c3 = 100
WHERE c1 = ‘x’
AND	d = CURDATE()

You can rewrite this UPDATE statement as a SELECT statement for using EXPLAIN:

EXPLAIN SELECT c1, c2, c3 FROM	t WHERE c1 = ‘x’ AND	d = CURDATE()

You should now apply the same principles as you would when optimizing SELECT statements.


PlanetMySQL Voting: Vote UP / Vote DOWN

Using EXPLAIN EXTENDED / SHOW WARNINGS to Help Troubleshoot Inefficient Queries in MySQL

Июль 9th, 2010

When examining the execution plan of troublesome queries in MySQL, most users are aware of using EXPLAIN. However, an often overlooked, yet very helpful extension of EXPLAIN, is EXPLAIN EXTENDED coupled with the SHOW WARNINGS command.

The reason being is because it provides a little more information about how the optimizer processes the query, and thus it could help to quickly identify a problem that you might not otherwise recognize with just EXPLAIN.

For instance, here is a common query which could be inefficient:

SELECT id FROM t WHERE id='1';

And here is the CREATE TABLE output:

mysql> show create table t\G
*************************** 1. row ***************************
       Table: t
Create Table: CREATE TABLE `t` (
  `id` decimal(10,0) default NULL,
  KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

You can see it’s a very basic table, and a very basic query.

And looking at the EXPLAIN output, everything still appears normal:

mysql> explain select id from t where id='1';
+----+-------------+-------+------+---------------+------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref   | rows | Extra                    |
+----+-------------+-------+------+---------------+------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | t     | ref  | id            | id   | 6       | const |    1 | Using where; Using index |
+----+-------------+-------+------+---------------+------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)

However, now let’s look at the EXPLAIN EXTENDED and SHOW WARNINGS output:

mysql> explain extended select id from t where id='1';
+----+-------------+-------+------+---------------+------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref   | rows | Extra                    |
+----+-------------+-------+------+---------------+------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | t     | ref  | id            | id   | 6       | const |    1 | Using where; Using index |
+----+-------------+-------+------+---------------+------+---------+-------+------+--------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> show warnings;
+-------+------+----------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                      |
+-------+------+----------------------------------------------------------------------------------------------+
| Note  | 1003 | select `testing`.`t`.`id` AS `id` from `testing`.`t` where (`testing`.`t`.`id` = _latin1'1') |
+-------+------+----------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

In the WHERE clause of the SHOW WARNINGS output, you see this:

where (`testing`.`t`.`id` = _latin1'1')

The _latin1′1′ is key, as it shows the value of 1 is getting cast as a latin1 value. This is because we surrounded the 1 with single quotes in the query.

Note that this does not happen if you do not surround the 1 with single quotes:

mysql> show warnings;
+-------+------+-------------------------------------------------------------------------------------+
| Level | Code | Message                                                                             |
+-------+------+-------------------------------------------------------------------------------------+
| Note  | 1003 | select `testing`.`t`.`id` AS `id` from `testing`.`t` where (`testing`.`t`.`id` = 1) |
+-------+------+-------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

In this example, the constant value of 1 is being cast, which is not horrible, but still not as efficient as it should be. However, depending on how the query is written, I’ve seen where it might choose to cast/convert the row retrived from the database (especially in a JOIN scenario - it will need to convert one column if they differ). These conversions or casts can be very inefficient (especially if within a JOIN or worse, a sub-query), and would not be otherwise caught without using EXPLAIN EXTENDED / SHOW WARNINGS.

And in addition to the above example, it is common to see similar types of conversions occurring if you try to match two columns whose character sets differ. This can be another easy-to-overlook problem, which can lead to poor performance, and you wouldn’t know it wasn’t running efficiently unless you used EXPLAIN EXTENDED (or unless it brings your system to a halt during a heavy load ..).

So in conclusion, consider using EXPLAIN EXTENDED / SHOW WARNINGS in lieu of just EXPLAIN when analyzing your queries.

Note: If you are using pre-5.1.46, you may want to consider running both EXPLAIN and EXPLAIN EXTENDED to ensure the optimization plans match. This is due to the following bug which existed:

http://bugs.mysql.com/bug.php?id=47669


PlanetMySQL Voting: Vote UP / Vote DOWN

MySQL’s SQL Deviations and Extensions

Июнь 28th, 2010

Today at Kaleidoscope I will be doing a 90-minute session comparing MySQL’s SQL syntax to the ANSI/ISO SQL:2003 standard, entitled What Do You Mean, “SQL Syntax Error”?

You can download the PDF slides now.

For those that may be following along the presentation later today (4 pm Eastern time), here are some links that I may throw out during the session:


PlanetMySQL Voting: Vote UP / Vote DOWN

MySQL University: Optimizing Queries with EXPLAIN

Февраль 2nd, 2010

This Thursday (February 4th, 14:00 UTC), Morgan Tocker will talk about Optimizing Queries with Explain. Morgan was a technical instructor at MySQL and works for Percona today.

For MySQL University sessions, point your browser to this page. You need a browser with a working Flash plugin. You may register for a Dimdim account, but you don't have to. (Dimdim is the conferencing system we're using for MySQL University sessions. It provides integrated voice streaming, chat, whiteboard, session recording, and more.) All MySQL University sessions are recorded, that is, slides and voice can be viewed as a Flash movie (.flv). You can find those recordings on the respective MySQL University session pages which are listed on the MySQL University home page.

MySQL University is a free educational online program for engineers/developers. MySQL University sessions are open to anyone, not just Sun employees. Sessions are recorded (slides and audio), so if you can't attend the live session you can look at the recording anytime after the session.

Here's the tentative list of upcoming sessions:

  • February 11: MySQL Galera - Multi-Master Replication (Seppo Jaakola & Alex Yurchenko)
  • February 18: Performance Schema: Instrumenting Code (Marc Alff)
  • February 25: Securich - Security Plugin for MySQL (Darren Cassar)
  • March 4: MySQL Column Databases (Robin Schumacher)
  • March 11: Improving MySQL Full-Text Search (Kristofer Pettersson)
By the way, did I mention that we need more speakers to fill up the 2010 schedule? If you'd like to be a speaker, have a look at this blog article!

PlanetMySQL Voting: Vote UP / Vote DOWN

how to prevent explain from executing subqueries

Январь 26th, 2010

Here’s a quick tip for using explain:

You may know this already, but mysql will actually execute some subqueries when you invoke explain.  Here’s an example:

mysql> explain select id from projects where id = (select max(id) from projects where name like 'en%');
+----+-------------+----------+-------+---------------+---------+---------+-------+-------+-------------+
| id | select_type | table    | type  | possible_keys | key     | key_len | ref   | rows  | Extra       |
+----+-------------+----------+-------+---------------+---------+---------+-------+-------+-------------+
|  1 | PRIMARY     | projects | const | PRIMARY       | PRIMARY | 4       | const |     1 | Using index |
|  2 | SUBQUERY    | projects | ALL   | NULL          | NULL    | NULL    | NULL  | 67922 | Using where |
+----+-------------+----------+-------+---------------+---------+---------+-------+-------+-------------+
2 rows in set (0.11 sec)

Take a look at the execution time (I choose an intentionally poorly executing query for my little dataset).
Here’s explain when it’s not executing:

mysql> explain select max(id) from projects where name like 'en%';
+----+-------------+----------+------+---------------+------+---------+------+-------+-------------+
| id | select_type | table    | type | possible_keys | key  | key_len | ref  | rows  | Extra       |
+----+-------------+----------+------+---------------+------+---------+------+-------+-------------+
|  1 | SIMPLE      | projects | ALL  | NULL          | NULL | NULL    | NULL | 69513 | Using where |
+----+-------------+----------+------+---------------+------+---------+------+-------+-------------+
1 row in set (0.00 sec)

If you want to work around this to prevent trouble on a production server (albeit, not getting the output from explain), you can do this:

mysql> set session max_join_size=1;
Query OK, 0 rows affected (0.00 sec)

mysql> explain select id from projects where id = (select max(id) from projects where name like 'en%');
ERROR 1104 (42000): The SELECT would examine more than MAX_JOIN_SIZE rows; check your WHERE and use SET SQL_BIG_SELECTS=1 or SET SQL_MAX_JOIN_SIZE=# if the SELECT is okay

Related posts:

  1. Using the ENUM data type to increase performance While going through the DATA TYPES section of the Certification...

Related posts brought to you by Yet Another Related Posts Plugin.


PlanetMySQL Voting: Vote UP / Vote DOWN

Materialized view makes login process 25k times faster

Январь 7th, 2010
It may sound like a dramatic number, and that’s because it is. One thing that is enjoyable about working on beta applications is finding new solutions and better methods to improve the user experience. The original method for displaying the recent addition of overview analytics data in the beta version of Kontrollbase was to run [...]
PlanetMySQL Voting: Vote UP / Vote DOWN

How (not) to find unused indexes

Октябрь 16th, 2009

I've seen a few people link to an INFORMATION_SCHEMA query to be able to find any indexes that have low cardinality, in an effort to find out what indexes should be removed.  This method is flawed - here's the first reason why:

SQL:
  1. CREATE TABLE `sales` (
  2. `id` int(11) NOT NULL AUTO_INCREMENT,
  3. `customer_id` int(11) DEFAULT NULL,
  4. `status` enum('arhicved','active') DEFAULT NULL,
  5. PRIMARY KEY (`id`),
  6. KEY `status` (`status`)
  7. ) ENGINE=MyISAM AUTO_INCREMENT=65691 DEFAULT CHARSET=latin1;
  8.  
  9. mysql&gt; SELECT count(*), STATUS FROM sales GROUP BY STATUS;
  10. +----------+---------+
  11. | count(*) | STATUS  |
  12. +----------+---------+
  13. |    65536 | archived |
  14. |      154 | active  |
  15. +----------+---------+
  16. 2 rows IN SET (0.17 sec)
  17.  
  18. mysql&gt; EXPLAIN SELECT * FROM sales WHERE STATUS='active'; # query 1
  19. +----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
  20. | id | select_type | TABLE | type | possible_keys | KEY    | key_len | ref   | rows | Extra       |
  21. +----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
  22. 1 | SIMPLE      | sales | ref  | STATUS        | STATUS | 2       | const |  196 | USING WHERE |
  23. +----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
  24. 1 row IN SET (0.06 sec)
  25.  
  26. mysql&gt; EXPLAIN SELECT * FROM sales WHERE STATUS='archived'; # query 2
  27. +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+
  28. | id | select_type | TABLE | type | possible_keys | KEY  | key_len | ref  | rows  | Extra       |
  29. +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+
  30. 1 | SIMPLE      | sales | ALL  | STATUS        | NULL | NULL    | NULL | 65690 | USING WHERE |
  31. +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+
  32. 1 row IN SET (0.01 sec)

The cardinality of status index is woeful, but provided that the application is always only sending query 1 to MySQL it's actually a pretty good index!  It's not always like this, but there are a lot of cases where applications have good selectivity with some queries despite what cardinality shows.

Not convinced?  Here's reason number two:

SQL:
  1. CREATE TABLE `Country` (
  2. `Code` char(3) NOT NULL DEFAULT '',
  3. `Name` char(52) NOT NULL DEFAULT '',
  4. `Continent` enum('Asia','Europe','North America','Africa','Oceania','Antarctica','South America') NOT NULL DEFAULT 'Asia',
  5. `Region` char(26) NOT NULL DEFAULT '',
  6. `SurfaceArea` float(10,2) NOT NULL DEFAULT '0.00',
  7. `IndepYear` smallint(6) DEFAULT NULL,
  8. `Population` int(11) NOT NULL DEFAULT '0',
  9. `LifeExpectancy` float(3,1) DEFAULT NULL,
  10. `GNP` float(10,2) DEFAULT NULL,
  11. `GNPOld` float(10,2) DEFAULT NULL,
  12. `LocalName` char(45) NOT NULL DEFAULT '',
  13. `GovernmentForm` char(45) NOT NULL DEFAULT '',
  14. `HeadOfState` char(60) DEFAULT NULL,
  15. `Capital` int(11) DEFAULT NULL,
  16. `Code2` char(2) NOT NULL DEFAULT '',
  17. PRIMARY KEY (`Code`),
  18. KEY `Population` (`Population`)
  19. ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
  20.  
  21. mysql&gt; SELECT count(*) FROM Country;
  22. +----------+
  23. | count(*) |
  24. +----------+
  25. |      239 |
  26. +----------+
  27. 1 row IN SET (0.00 sec)
  28.  
  29. mysql&gt; SELECT count(DISTINCT(population)) FROM Country;
  30. +-----------------------------+
  31. | count(DISTINCT(population)) |
  32. +-----------------------------+
  33. |                         226 |
  34. +-----------------------------+
  35. 1 row IN SET (0.05 sec)
  36.  
  37. mysql&gt; EXPLAIN SELECT * FROM country WHERE population&gt; 1000; # query 3
  38. +----+-------------+---------+------+---------------+------+---------+------+------+-------------+
  39. | id | select_type | TABLE   | type | possible_keys | KEY  | key_len | ref  | rows | Extra       |
  40. +----+-------------+---------+------+---------------+------+---------+------+------+-------------+
  41. 1 | SIMPLE      | country | ALL  | Population    | NULL | NULL    | NULL239 | USING WHERE |
  42. +----+-------------+---------+------+---------------+------+---------+------+------+-------------+
  43. 1 row IN SET (0.04 sec)
  44.  
  45. mysql&gt; EXPLAIN SELECT * FROM country WHERE population&gt; 100000000; # query 4
  46. +----+-------------+---------+-------+---------------+------------+---------+------+------+-------------+
  47. | id | select_type | TABLE   | type  | possible_keys | KEY        | key_len | ref  | rows | Extra       |
  48. +----+-------------+---------+-------+---------------+------------+---------+------+------+-------------+
  49. 1 | SIMPLE      | country | range | Population    | Population | 4       | NULL |   23 | USING WHERE |
  50. +----+-------------+---------+-------+---------------+------------+---------+------+------+-------------+
  51. 1 row IN SET (0.00 sec)

The index on query 3 had high cardinality but should not be used since too many countries have a population greater than 1000.  An automated search for low cardinality indexes wouldn't have revealed it's uselessness.  For range scans, it's very easy to lead yourself into a trap where your index can not filter out enough rows to be effective.  I see this a lot in consulting issues where customers have queries that use a BETWEEN on a date, but the window of time it is searching in is too wide.

Side Note: In some texts you'll see people quote the numbers "20-30%" as the minimum amount of rows you have to filter down to for an index to be useful (that is, eliminate 70-80% of rows).  It's not quite correct to quote this as an exact percentage, since this value is not fixed in MySQL and can be a much wider window (15-60%) depending on the circumstances.  In this case, MySQL flipped from tablescan to index at about 34%.

How am I supposed to find unused indexes then?
You really have to run queries against your server - there is no other way.  From there, there's a helpful patch in 5.0-percona called INDEX_STATISTICS that can then show you which indexes were touched and which were not.

If you are not running a patched server, then the alternative is to either use a proxy that checks EXPLAIN information (like QUAN) or set your slow query log to zero microseconds (5.1 feature) and then find someway to parse and EXPLAIN all results, then subtract the indexes that were mentioned from all indexes known.  There's an old tool called mysqlidxchx which should be able to do this.


Entry posted by Morgan Tocker | No comment

Add to: delicious | digg | reddit | netscape | Google Bookmarks


PlanetMySQL Voting: Vote UP / Vote DOWN