{"id":15457,"date":"2022-08-24T08:40:46","date_gmt":"2022-08-24T15:40:46","guid":{"rendered":"https:\/\/coderpad.io\/?p=15457"},"modified":"2023-08-09T14:26:36","modified_gmt":"2023-08-09T21:26:36","slug":"optimize-query-performance-mysql","status":"publish","type":"post","link":"https:\/\/coderpad.io\/blog\/development\/optimize-query-performance-mysql\/","title":{"rendered":"How to Optimize Query Performance in MySQL Databases"},"content":{"rendered":"\n<p>The built-in MySQL <a href=\"https:\/\/dev.mysql.com\/doc\/internals\/en\/optimizer.html\" target=\"_blank\" rel=\"noopener\">query optimizer<\/a> does an excellent job of optimizing the execution of your queries. <\/p>\n\n\n\n<p>However, poorly written queries can prevent the optimizer from performing well. Even if you do other optimization techniques such as good schema design or indexing, if your queries are wrong, they will still affect your database\u2019s performance. This guide aims to help you improve your MySQL database speed by optimizing your queries. <\/p>\n\n\n\n<p>In this guide, you will:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Learn how to identify slow queries in MySQL using several query profiling techniques<\/li>\n\n\n\n<li>Learn how you can optimize your queries for faster response times<\/li>\n\n\n\n<li>Understand how MySQL optimizes and executes your queries<\/li>\n\n\n\n<li>Learn how to control and modify the default query execution plan of your query<\/li>\n\n\n\n<li>Learn how the <code>EXPLAIN<\/code> and <code>EXPLAIN ANALYZE<\/code> keywords provide information about how MySQL databases execute queries<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">A quick introduction to query processing<\/h2>\n\n\n\n<p>MySQL processes your query in a series of steps. Understanding how these steps work can unlock powerful insights into how you can optimize your queries. These steps are complex internally but can be summarized below:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/08\/img_6303c4c020b18.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" src=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/08\/img_6303c4c020b18.png\" alt=\"MySQL client talking to MySQL server. First SQL goes to Parser, then preprocessor, then query optimizer, then query execution plan, then query execution engine. Then it makes an API call to the storage engine (eg InnoDB) to retrieve the data. The storage engine then returns data to the query execution engine and then back to the mysql client. \"\/><\/a><figcaption class=\"wp-element-caption\"><em>MySQL query lifecycle <\/em>by <a href=\"https:\/\/www.elvisduru.com\/MySQL-query-lifecycle.png\" target=\"_blank\" rel=\"noopener\"><em>Elvis Duru<\/em><\/a><\/figcaption><\/figure>\n<\/div>\n\n\n<ol class=\"wp-block-list\">\n<li>The MySQL client sends your query to the MySQL server using the MySQL Client\/Server Protocol.<\/li>\n\n\n\n<li>The query is parsed, preprocessed, and finally optimized into a query execution plan by the MySQL<a href=\"https:\/\/dev.mysql.com\/doc\/internals\/en\/optimizer.html\" target=\"_blank\" rel=\"noopener\"> query optimizer<\/a>. The optimizer may ask the Storage engine for statistics about the tables referenced in your query before execution.<\/li>\n\n\n\n<li>The Query Execution Engine executes the plan by making calls to the Storage engine through<a href=\"https:\/\/dev.mysql.com\/doc\/internals\/en\/custom-engine-overview.html\" target=\"_blank\" rel=\"noopener\"> special handler interfaces<\/a>.<\/li>\n\n\n\n<li>The MySQL server sends the results to the MySQL client.<\/li>\n<\/ol>\n\n\n\n<p>In the following sections, we will see how we can get information about the default execution plan and explore ways to optimize our queries to influence the plan.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Identify and analyze slow queries in MySQL<\/h2>\n\n\n\n<p>Slow queries can be annoying, but the good news is that we can identify and fix them. <\/p>\n\n\n\n<p>The amount of time it takes MySQL to execute your query is known as its <em>response time<\/em>. It is the most critical metric used in measuring your query\u2019s speed. In other words, <strong>the performance of an individual query or transaction is directly proportional to its response time<\/strong>.<\/p>\n\n\n\n<p>There are several reasons why a query may be slow. These reasons can range from your current hardware configuration, to the permissions you may have set, improper index usage, bad schema design, or even the intention of your queries. This guide focuses on the last point &#8211; the query.<\/p>\n\n\n\n<p>Whenever you send a query to the MySQL server, you send a series of instructions that you want it to perform. <\/p>\n\n\n\n<p>Some instructions are simple &#8212; e.g., a query that searches using a single parameter &#8212; and some instructions can be complex &#8212; e.g., queries that involve complex joins and subqueries. In whatever form the MySQL server receives your queries, they will consume time. <\/p>\n\n\n\n<p>Luckily, several built-in tools can allow you to see how your query performed. The process of finding out how MySQL spends time processing and executing your query is known as <strong>query profiling<\/strong>. The following sections explore some query profiling tools and explain how we can use them in analyzing your query\u2019s performance. If you want to follow along, you can <a href=\"https:\/\/dev.mysql.com\/doc\/sakila\/en\/\" target=\"_blank\" rel=\"noopener\">download and install the sakila sample database<\/a> we use throughout this article.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The INFORMATION_SCHEMA.PROFILING table<\/h3>\n\n\n\n<p>The <code>INFORMATION_SCHEMA.PROFILING<\/code> table stores profiling information about queries you run in a current interactive session. It\u2019s disabled by default, but you can enable query profiling for your current session by setting the <code>profiling<\/code> session variable as shown below. Note that the profiling information is lost when a session ends:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-wrap-lines\">mysql&gt; SET SESSION profiling = 1;\n\nQuery OK, 0 rows affected, 1 warning (0.00 sec)<\/code><\/span><\/pre>\n\n\n<p>After that, you can select the database you want to use:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">mysql&gt; <span class=\"hljs-keyword\">USE<\/span> <span class=\"hljs-title\">sakila<\/span>;\n\nDatabase changed<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Next, you run your query (Make sure you do not use the <code>EXPLAIN<\/code> statement)<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-wrap-lines\">mysql&gt; SELECT * FROM customer;<\/code><\/span><\/pre>\n\n\n<p>Run the <code>SHOW PROFILES<\/code> query<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">mysql&gt; SHOW PROFILES;\n\n+----------+------------+------------------------+\n| Query_ID | Duration \u00a0 | Query\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 |\n+----------+------------+------------------------+\n|\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">1<\/span> | <span class=\"hljs-number\">0.02364600<\/span> | SELECT DATABASE()\u00a0 \u00a0 \u00a0 |\n|\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2<\/span> | <span class=\"hljs-number\">0.04395425<\/span> | show databases \u00a0 \u00a0 \u00a0 \u00a0 |\n|\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">3<\/span> | <span class=\"hljs-number\">0.00854575<\/span> | show tables\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 |\n|\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">4<\/span> | <span class=\"hljs-number\">0.00213000<\/span> | SELECT * FROM customer |\n+----------+------------+------------------------+\n<span class=\"hljs-number\">4<\/span> rows <span class=\"hljs-keyword\">in<\/span> <span class=\"hljs-keyword\">set<\/span>, 1 warning (0.00 sec)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The <code>SHOW PROFILES<\/code> query fetches all the list of queries in the current session. It has three columns:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>The <strong>Query_ID<\/strong> &#8211; a unique numerical identifier for a query<\/li>\n\n\n\n<li>The <strong>Duration <\/strong>is the time taken to execute a query<\/li>\n\n\n\n<li>The <strong>Query <\/strong>column shows the query that the MySQL server executed<\/li>\n<\/ol>\n\n\n\n<p>To see more information about a particular query, we can run the following:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-wrap-lines\">mysql&gt; SELECT * FROM INFORMATION_SCHEMA.PROFILING WHERE QUERY_ID=4;\n\n+----------+-----+--------------------------------+----------+----------+------------+--&gt;| QUERY_ID | SEQ | STATE                          | DURATION | CPU_USER | CPU_SYSTEM | C&gt;+----------+-----+--------------------------------+----------+----------+------------+--&gt;|        4 |   2 | starting                       | 0.000107 | 0.000046 |   0.000019 |  &gt;|        4 |   3 | Executing hook on transaction  | 0.000005 | 0.000003 |   0.000001 |  &gt;|        4 |   4 | starting                       | 0.000007 | 0.000005 |   0.000002 |  &gt;|        4 |   5 | checking permissions           | 0.000007 | 0.000004 |   0.000002 |  &gt;|        4 |   6 | Opening tables                 | 0.000337 | 0.000238 |   0.000100 |  &gt;|        4 |   7 | init                           | 0.000007 | 0.000005 |   0.000001 |  &gt;|        4 |   8 | System lock                    | 0.000010 | 0.000006 |   0.000003 |  &gt;|        4 |   9 | optimizing                     | 0.000004 | 0.000003 |   0.000001 |  &gt;|        4 |  10 | statistics                     | 0.000011 | 0.000008 |   0.000004 |  &gt;|        4 |  11 | preparing                      | 0.000016 | 0.000011 |   0.000004 |  &gt;|        4 |  12 | executing                      | 0.000802 | 0.000573 |   0.000239 |  &gt;|        4 |  13 | end                            | 0.000017 | 0.000005 |   0.000002 |  &gt;|        4 |  14 | query end                      | 0.000003 | 0.000002 |   0.000001 |  &gt;|        4 |  15 | waiting for handler commit     | 0.000008 | 0.000006 |   0.000002 |  &gt;|        4 |  16 | closing tables                 | 0.000007 | 0.000005 |   0.000002 |  &gt;|        4 |  17 | freeing items                  | 0.000029 | 0.000020 |   0.000009 |  &gt;|        4 |  18 | cleaning up                    | 0.000019 | 0.000013 |   0.000005 |  &gt;+----------+-----+--------------------------------+----------+----------+------------+--&gt;(END)<\/code><\/span><\/pre>\n\n\n<p>The results show some interesting information about how MySQL processed your query and the time each step took. We discussed some of those steps earlier when explaining the MySQL query lifecycle. Closely observe SEQ (Sequence) 9 &#8211; 12.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The slow_query_log<\/h3>\n\n\n\n<p>Another way we can identify slow queries is by inspecting the slow query logs. This built-in feature enables you to log queries that exceed a time limit you set using the <code>long_query_time<\/code> system variable. The default time limit is 10 seconds, i.e., MySQL will log any query that runs longer than 10 seconds. This slow query log, like the INFORMATION_SCHEMA.PROFILING, is not enabled by default. To use it, you must first enable it by setting the <code>slow_query_log<\/code> global variable to <code>'ON'<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">mysql&gt; SET <span class=\"hljs-keyword\">GLOBAL<\/span> slow_query_log = <span class=\"hljs-string\">'ON'<\/span>;\n\nQuery OK, <span class=\"hljs-number\">0<\/span> rows affected (<span class=\"hljs-number\">0.08<\/span> sec)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>To confirm, you could always view a global variable\u2019s value this way:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">mysql&gt; SHOW <span class=\"hljs-keyword\">GLOBAL<\/span> VARIABLES LIKE <span class=\"hljs-string\">'slow_query_log'<\/span>;\n\n+----------------+-------+\n| Variable_name\u00a0 | Value |\n+----------------+-------+\n| slow_query_log | ON\u00a0 \u00a0 |\n+----------------+-------+<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You can change the time limit in seconds by typing the following command:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">mysql&gt; SET <span class=\"hljs-keyword\">GLOBAL<\/span> long_query_time = <span class=\"hljs-number\">60<\/span>;\n\nQuery OK, <span class=\"hljs-number\">0<\/span> rows affected (<span class=\"hljs-number\">0.08<\/span> sec)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You can also change the default location for the slow query log file, usually found at <em>var\/lib\/mysql\/hostname-slow.log<\/em>, to any destination of your choice:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">mysql&gt; SET <span class=\"hljs-keyword\">GLOBAL<\/span> slow_query_log_file = <span class=\"hljs-string\">'\/somepath\/filename.log'<\/span>;\n\nQuery OK, <span class=\"hljs-number\">0<\/span> rows affected (<span class=\"hljs-number\">0.08<\/span> sec)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Once you\u2019ve run any query that exceeds the time limit you configured, it will be logged in the slow query log by the MySQL server. You can always inspect the file to see those slow queries.&nbsp;<\/p>\n\n\n\n<p>To demonstrate this, I have set my slow query log file to the path \u201c\/tmp\/slow_queries.log\u201d.&nbsp; I\u2019m using the default long_query_time, which is 10 seconds. We can force a slow query that runs for 15 seconds using the SLEEP() function as shown below:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">mysql&gt; SELECT SLEEP(<span class=\"hljs-number\">15<\/span>);\n\n+-----------+\n| SLEEP(<span class=\"hljs-number\">15<\/span>) |\n+-----------+\n| \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">0<\/span> |\n+-----------+\n<span class=\"hljs-number\">1<\/span> row <span class=\"hljs-keyword\">in<\/span> <span class=\"hljs-keyword\">set<\/span> (15.00 sec)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Let\u2019s inspect the slow query log and see if the MySQL server logged our new query:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">\u276f sudo cat \/tmp\/slow_queries.log\n\n\/usr\/sbin\/mysqld, Version: <span class=\"hljs-number\">8.0<\/span><span class=\"hljs-number\">.29<\/span><span class=\"hljs-number\">-0<\/span>ubuntu0<span class=\"hljs-number\">.20<\/span><span class=\"hljs-number\">.04<\/span><span class=\"hljs-number\">.3<\/span> ((Ubuntu)). started with:\nTcp port: <span class=\"hljs-number\">3306<\/span>\u00a0 Unix socket: \/<span class=\"hljs-keyword\">var<\/span>\/run\/mysqld\/mysqld.sock\nTime \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 Id Command\u00a0 \u00a0 Argument\n<span class=\"hljs-comment\"># Time: 2022-08-16T15:47:39.323248Z<\/span>\n<span class=\"hljs-comment\"># User@Host: root&#91;root] @ localhost &#91;]\u00a0 Id:\u00a0 \u00a0 17<\/span>\n<span class=\"hljs-comment\"># Query_time: 15.000317\u00a0 Lock_time: 0.000000 Rows_sent: 1\u00a0 Rows_examined: 1<\/span>\nSET timestamp=<span class=\"hljs-number\">1660664844<\/span>;\nSELECT SLEEP(<span class=\"hljs-number\">15<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>For larger log files, MySQL provides the <code>mysqldumpslow<\/code> tool to parse and summarize slow query log files:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">\u276f sudo mysqldumpslow \/tmp\/slow_queries.log\n\nReading mysql slow query log <span class=\"hljs-keyword\">from<\/span> \/tmp\/slow_queries.log\n<span class=\"hljs-attr\">Count<\/span>: <span class=\"hljs-number\">1<\/span>\u00a0 Time=<span class=\"hljs-number\">15.00<\/span>s (<span class=\"hljs-number\">15<\/span>s)\u00a0 Lock=<span class=\"hljs-number\">0.00<\/span>s (<span class=\"hljs-number\">0<\/span>s)\u00a0 Rows=<span class=\"hljs-number\">1.0<\/span> (<span class=\"hljs-number\">1<\/span>), root&#91;root]@localhost\n\u00a0 SELECT SLEEP(N)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If you\u2019re done inspecting, you can turn off the feature by setting the &nbsp;<code>slow_query_log<\/code> global variable to <code>'OFF'<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">mysql&gt; SET <span class=\"hljs-keyword\">GLOBAL<\/span> slow_query_log = <span class=\"hljs-string\">'OFF'<\/span>;\n\nQuery OK, <span class=\"hljs-number\">0<\/span> rows affected (<span class=\"hljs-number\">0.08<\/span> sec)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">The <code>EXPLAIN<\/code> statement<\/h3>\n\n\n\n<p>Another tool MySQL provides for query analysis is the <code>EXPLAIN<\/code> statement. This statement shows us how the MySQL server will execute a plan. It\u2019s quite easy to use. Anytime you want to see how MySQL will execute your query before running it, just prepend the <code>EXPLAIN<\/code> statement to it:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">mysql&gt; EXPLAIN SELECT * FROM customer\\G\n\n*************************** <span class=\"hljs-number\">1.<\/span> row ***************************\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 id: <span class=\"hljs-number\">1<\/span>\n\u00a0 select_type: SIMPLE\n\u00a0 \u00a0 \u00a0 \u00a0 table: customer\n\u00a0 partitions: <span class=\"hljs-keyword\">NULL<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 type: ALL\npossible_keys: <span class=\"hljs-keyword\">NULL<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 key: <span class=\"hljs-keyword\">NULL<\/span>\n\u00a0 \u00a0 \u00a0 key_len: <span class=\"hljs-keyword\">NULL<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 ref: <span class=\"hljs-keyword\">NULL<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 rows: <span class=\"hljs-number\">599<\/span>\n\u00a0 \u00a0 filtered: <span class=\"hljs-number\">100.00<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 Extra: <span class=\"hljs-keyword\">NULL<\/span>\n<span class=\"hljs-number\">1<\/span> row in set, <span class=\"hljs-number\">1<\/span> warning (<span class=\"hljs-number\">0.00<\/span> sec)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Ending the SQL statement with &#8220;\\G&#8221; instead of &#8220;;&#8221; causes MySQL to return the query result in vertical format, which makes it easier to read tables with many long columns.<\/p>\n\n\n\n<p>The above example tells us that the server only needs to scan 599 rows to get the result. A common use case for <code>EXPLAIN<\/code> is analyzing how MySQL will execute joins. Let\u2019s run a query that joins some tables in our sakila database to demonstrate:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-wrap-lines\">mysql&gt; SELECT first_name, last_name, city, country\u00a0FROM customer\u00a0INNER JOIN address USING(address_id)\u00a0INNER JOIN city USING(city_id)\u00a0INNER JOIN country USING(country_id);\n\n+-------------+--------------+----------------------------+---------------+\n| first_name\u00a0 | last_name\u00a0 \u00a0 | city \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | country \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n+-------------+--------------+----------------------------+---------------+\n| VERA\u00a0 \u00a0 \u00a0 \u00a0 | MCCOY\u00a0 \u00a0 \u00a0 \u00a0 | Kabul\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Afghanistan \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| MARIO \u00a0 \u00a0 \u00a0 | CHEATHAM \u00a0 \u00a0 | Batna\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Algeria \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| JUDY\u00a0 \u00a0 \u00a0 \u00a0 | GRAY \u00a0 \u00a0 \u00a0 \u00a0 | Bchar\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Algeria \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| JUNE\u00a0 \u00a0 \u00a0 \u00a0 | CARROLL\u00a0 \u00a0 \u00a0 | Skikda \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Algeria \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| ANTHONY \u00a0 \u00a0 | SCHWAB \u00a0 \u00a0 \u00a0 | Tafuna \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | American Samoa\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| CLAUDE\u00a0 \u00a0 \u00a0 | HERZOG \u00a0 \u00a0 \u00a0 | Benguela \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Angola\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| MARTIN\u00a0 \u00a0 \u00a0 | BALES\u00a0 \u00a0 \u00a0 \u00a0 | Namibe \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Angola\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| BOBBY \u00a0 \u00a0 \u00a0 | BOUDREAU \u00a0 \u00a0 | South Hill \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Anguilla\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| WILLIE\u00a0 \u00a0 \u00a0 | MARKHAM\u00a0 \u00a0 \u00a0 | Almirante Brown\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Argentina \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| JORDAN\u00a0 \u00a0 \u00a0 | ARCHULETA\u00a0 \u00a0 | Avellaneda \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Argentina \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| JASON \u00a0 \u00a0 \u00a0 | MORRISSEY\u00a0 \u00a0 | Baha Blanca\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Argentina \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| KIMBERLY\u00a0 \u00a0 | LEE\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Crdoba \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Argentina \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| MICHEAL \u00a0 \u00a0 | FORMAN \u00a0 \u00a0 \u00a0 | Escobar\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Argentina \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| DARRYL\u00a0 \u00a0 \u00a0 | ASHCRAFT \u00a0 \u00a0 | Ezeiza \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Argentina \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| JULIA \u00a0 \u00a0 \u00a0 | FLORES \u00a0 \u00a0 \u00a0 | La Plata \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Argentina \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| FLORENCE\u00a0 \u00a0 | WOODS\u00a0 \u00a0 \u00a0 \u00a0 | Merlo\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Argentina \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| PERRY \u00a0 \u00a0 \u00a0 | SWAFFORD \u00a0 \u00a0 | Quilmes\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Argentina \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| LYDIA \u00a0 \u00a0 \u00a0 | BURKE\u00a0 \u00a0 \u00a0 \u00a0 | San Miguel de Tucumn \u00a0 \u00a0 \u00a0 | Argentina \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| ERIC\u00a0 \u00a0 \u00a0 \u00a0 | ROBERT \u00a0 \u00a0 \u00a0 | Santa F\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Argentina \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| LEONARD \u00a0 \u00a0 | SCHOFIELD\u00a0 \u00a0 | Tandil \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Argentina \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| WILLIE\u00a0 \u00a0 \u00a0 | HOWELL \u00a0 \u00a0 \u00a0 | Vicente Lpez \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Argentina \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| STEPHANIE \u00a0 | MITCHELL \u00a0 \u00a0 | Yerevan\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Armenia \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| AUDREY\u00a0 \u00a0 \u00a0 | RAY\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Graz \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Austria \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| JILL\u00a0 \u00a0 \u00a0 \u00a0 | HAWKINS\u00a0 \u00a0 \u00a0 | Linz \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Austria\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0---<\/code><\/span><\/pre>\n\n\n<p>Our query is simple. We join our <code>customer<\/code> table with the <code>address<\/code> table using the <code>address_id<\/code> key; we then join the city and the country using their respective ids. Now closely observe the order of our query. Let\u2019s see the plan MySQL used in running our query by prepending the <code>EXPLAIN<\/code> keyword in our query:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">mysql<\/span>&gt; <span class=\"hljs-selector-tag\">EXPLAIN<\/span> <span class=\"hljs-selector-tag\">SELECT<\/span> <span class=\"hljs-selector-tag\">first_name<\/span>, <span class=\"hljs-selector-tag\">last_name<\/span>, <span class=\"hljs-selector-tag\">city<\/span>, <span class=\"hljs-selector-tag\">country<\/span> <span class=\"hljs-selector-tag\">FROM<\/span> <span class=\"hljs-selector-tag\">customer<\/span> <span class=\"hljs-selector-tag\">INNER<\/span> <span class=\"hljs-selector-tag\">JOIN<\/span> <span class=\"hljs-selector-tag\">address<\/span> <span class=\"hljs-selector-tag\">USING<\/span>(<span class=\"hljs-selector-tag\">address_id<\/span>)\u00a0<span class=\"hljs-selector-tag\">INNER<\/span> <span class=\"hljs-selector-tag\">JOIN<\/span> <span class=\"hljs-selector-tag\">city<\/span> <span class=\"hljs-selector-tag\">USING<\/span>(<span class=\"hljs-selector-tag\">city_id<\/span>)\u00a0<span class=\"hljs-selector-tag\">INNER<\/span> <span class=\"hljs-selector-tag\">JOIN<\/span> <span class=\"hljs-selector-tag\">country<\/span> <span class=\"hljs-selector-tag\">USING<\/span>(<span class=\"hljs-selector-tag\">country_id<\/span>)\\<span class=\"hljs-selector-tag\">G<\/span>\n\n*************************** 1. <span class=\"hljs-selector-tag\">row<\/span> ***************************\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">id<\/span>: 1\n\u00a0 <span class=\"hljs-selector-tag\">select_type<\/span>: <span class=\"hljs-selector-tag\">SIMPLE<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">table<\/span>: <span class=\"hljs-selector-tag\">country<\/span>\n\u00a0 <span class=\"hljs-selector-tag\">partitions<\/span>: <span class=\"hljs-selector-tag\">NULL<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">type<\/span>: <span class=\"hljs-selector-tag\">ALL<\/span>\n<span class=\"hljs-selector-tag\">possible_keys<\/span>: <span class=\"hljs-selector-tag\">PRIMARY<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">key<\/span>: <span class=\"hljs-selector-tag\">NULL<\/span>\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">key_len<\/span>: <span class=\"hljs-selector-tag\">NULL<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">ref<\/span>: <span class=\"hljs-selector-tag\">NULL<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">rows<\/span>: 109\n\u00a0 \u00a0 <span class=\"hljs-selector-tag\">filtered<\/span>: 100<span class=\"hljs-selector-class\">.00<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">Extra<\/span>: <span class=\"hljs-selector-tag\">NULL<\/span>\n*************************** 2. <span class=\"hljs-selector-tag\">row<\/span> ***************************\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">id<\/span>: 1\n\u00a0 <span class=\"hljs-selector-tag\">select_type<\/span>: <span class=\"hljs-selector-tag\">SIMPLE<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">table<\/span>: <span class=\"hljs-selector-tag\">city<\/span>\n\u00a0 <span class=\"hljs-selector-tag\">partitions<\/span>: <span class=\"hljs-selector-tag\">NULL<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">type<\/span>: <span class=\"hljs-selector-tag\">ref<\/span>\n<span class=\"hljs-selector-tag\">possible_keys<\/span>: <span class=\"hljs-selector-tag\">PRIMARY<\/span>,<span class=\"hljs-selector-tag\">idx_fk_country_id<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">key<\/span>: <span class=\"hljs-selector-tag\">idx_fk_country_id<\/span>\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">key_len<\/span>: 2\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">ref<\/span>: <span class=\"hljs-selector-tag\">sakila<\/span><span class=\"hljs-selector-class\">.country<\/span><span class=\"hljs-selector-class\">.country_id<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">rows<\/span>: 5\n\u00a0 \u00a0 <span class=\"hljs-selector-tag\">filtered<\/span>: 100<span class=\"hljs-selector-class\">.00<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">Extra<\/span>: <span class=\"hljs-selector-tag\">NULL<\/span>\n*************************** 3. <span class=\"hljs-selector-tag\">row<\/span> ***************************\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">id<\/span>: 1\n\u00a0 <span class=\"hljs-selector-tag\">select_type<\/span>: <span class=\"hljs-selector-tag\">SIMPLE<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">table<\/span>: <span class=\"hljs-selector-tag\">address<\/span>\n\u00a0 <span class=\"hljs-selector-tag\">partitions<\/span>: <span class=\"hljs-selector-tag\">NULL<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">type<\/span>: <span class=\"hljs-selector-tag\">ref<\/span>\n<span class=\"hljs-selector-tag\">possible_keys<\/span>: <span class=\"hljs-selector-tag\">PRIMARY<\/span>,<span class=\"hljs-selector-tag\">idx_fk_city_id<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">key<\/span>: <span class=\"hljs-selector-tag\">idx_fk_city_id<\/span>\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">key_len<\/span>: 2\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">ref<\/span>: <span class=\"hljs-selector-tag\">sakila<\/span><span class=\"hljs-selector-class\">.city<\/span><span class=\"hljs-selector-class\">.city_id<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">rows<\/span>: 1\n\u00a0 \u00a0 <span class=\"hljs-selector-tag\">filtered<\/span>: 100<span class=\"hljs-selector-class\">.00<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">Extra<\/span>: <span class=\"hljs-selector-tag\">Using<\/span> <span class=\"hljs-selector-tag\">index<\/span>\n*************************** 4. <span class=\"hljs-selector-tag\">row<\/span> ***************************\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">id<\/span>: 1\n\u00a0 <span class=\"hljs-selector-tag\">select_type<\/span>: <span class=\"hljs-selector-tag\">SIMPLE<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">table<\/span>: <span class=\"hljs-selector-tag\">customer<\/span>\n\u00a0 <span class=\"hljs-selector-tag\">partitions<\/span>: <span class=\"hljs-selector-tag\">NULL<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">type<\/span>: <span class=\"hljs-selector-tag\">ref<\/span>\n<span class=\"hljs-selector-tag\">possible_keys<\/span>: <span class=\"hljs-selector-tag\">idx_fk_address_id<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">key<\/span>: <span class=\"hljs-selector-tag\">idx_fk_address_id<\/span>\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">key_len<\/span>: 2\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">ref<\/span>: <span class=\"hljs-selector-tag\">sakila<\/span><span class=\"hljs-selector-class\">.address<\/span><span class=\"hljs-selector-class\">.address_id<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">rows<\/span>: 1\n\u00a0 \u00a0 <span class=\"hljs-selector-tag\">filtered<\/span>: 100<span class=\"hljs-selector-class\">.00<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-selector-tag\">Extra<\/span>: <span class=\"hljs-selector-tag\">NULL<\/span>\n(<span class=\"hljs-selector-tag\">END<\/span>)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>So, this is different from the order we expected. According to this result, we can see that MySQL wants to start with the <code>country<\/code> table and then scan the <code>city<\/code> table, the <code>address<\/code> table, and finally the <code>customer<\/code> table. But why is it choosing this route? <\/p>\n\n\n\n<p>The answer is the <em>join optimizer<\/em>. Join optimization is one of the most potent ways MySQL helps optimize queries that involve joins. It does this by arranging the joins in several orders and then estimates the various costs for each order before finally selecting the least expensive option that gives the same result as the initial order of a query.<\/p>\n\n\n\n<p>Back to our last example, by using <code>EXPLAIN<\/code>, we see that MySQL would only need to scan 109 rows in the <code>country<\/code> table before scanning the small number of rows in the other tables using the indexed columns &#8211; 5, 1, and 1 respectively.<\/p>\n\n\n\n<p>We can force MySQL to run our query in the order we intend by using the <code>STRAIGHT_JOIN<\/code> clause. Let\u2019s see information about using that:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">mysql&gt; EXPLAIN SELECT STRAIGHT_JOIN first_name, last_name, city, country FROM customer\u00a0INNER JOIN address\u00a0USING(address_id) INNER JOIN city USING(city_id)\n\n*************************** <span class=\"hljs-number\">1.<\/span> row ***************************\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 id: <span class=\"hljs-number\">1<\/span>\n\u00a0 select_type: SIMPLE\n\u00a0 \u00a0 \u00a0 \u00a0 table: customer\n\u00a0 partitions: <span class=\"hljs-keyword\">NULL<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 type: ALL\npossible_keys: idx_fk_address_id\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 key: <span class=\"hljs-keyword\">NULL<\/span>\n\u00a0 \u00a0 \u00a0 key_len: <span class=\"hljs-keyword\">NULL<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 ref: <span class=\"hljs-keyword\">NULL<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 rows: <span class=\"hljs-number\">599<\/span>\n\u00a0 \u00a0 filtered: <span class=\"hljs-number\">100.00<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 Extra: <span class=\"hljs-keyword\">NULL<\/span>\n*************************** <span class=\"hljs-number\">2.<\/span> row ***************************\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 id: <span class=\"hljs-number\">1<\/span>\n\u00a0 select_type: SIMPLE\n\u00a0 \u00a0 \u00a0 \u00a0 table: address\n\u00a0 partitions: <span class=\"hljs-keyword\">NULL<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 type: eq_ref\npossible_keys: PRIMARY,idx_fk_city_id\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 key: PRIMARY\n\u00a0 \u00a0 \u00a0 key_len: <span class=\"hljs-number\">2<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 ref: sakila.customer.address_id\n\u00a0 \u00a0 \u00a0 \u00a0 rows: <span class=\"hljs-number\">1<\/span>\n\u00a0 \u00a0 filtered: <span class=\"hljs-number\">100.00<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 Extra: <span class=\"hljs-keyword\">NULL<\/span>\n*************************** <span class=\"hljs-number\">3.<\/span> row ***************************\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 id: <span class=\"hljs-number\">1<\/span>\n\u00a0 select_type: SIMPLE\n\u00a0 \u00a0 \u00a0 \u00a0 table: city\n\u00a0 partitions: <span class=\"hljs-keyword\">NULL<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 type: eq_ref\npossible_keys: PRIMARY,idx_fk_country_id\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 key: PRIMARY\n\u00a0 \u00a0 \u00a0 key_len: <span class=\"hljs-number\">2<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 ref: sakila.address.city_id\n\u00a0 \u00a0 \u00a0 \u00a0 rows: <span class=\"hljs-number\">1<\/span>\n\u00a0 \u00a0 filtered: <span class=\"hljs-number\">100.00<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 Extra: <span class=\"hljs-keyword\">NULL<\/span>\n*************************** <span class=\"hljs-number\">4.<\/span> row ***************************\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 id: <span class=\"hljs-number\">1<\/span>\n\u00a0 select_type: SIMPLE\n\u00a0 \u00a0 \u00a0 \u00a0 table: country\n\u00a0 partitions: <span class=\"hljs-keyword\">NULL<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 type: eq_ref\npossible_keys: PRIMARY\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 key: PRIMARY\n\u00a0 \u00a0 \u00a0 key_len: <span class=\"hljs-number\">2<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 ref: sakila.city.country_id\n\u00a0 \u00a0 \u00a0 \u00a0 rows: <span class=\"hljs-number\">1<\/span>\n\u00a0 \u00a0 filtered: <span class=\"hljs-number\">100.00<\/span>\n\u00a0 \u00a0 \u00a0 \u00a0 Extra: <span class=\"hljs-keyword\">NULL<\/span>\n(END)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Judging by the number of rows that the MySQL server will scan on the customer table (599) in this example, we can tell that the plan suggested by the join optimizer is more performant. Note that there are still a few times that the optimizer could be wrong; in such cases, you can check your query against STRAIGHT_JOIN or check if there are opportunities to improve the written query.<\/p>\n\n\n\n<p>The <code>EXPLAIN<\/code> statement can help you identify where you should add indexes to tables so that queries can perform faster by using the indexes to find rows in those tables.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The EXPLAIN ANALYZE statement<\/h3>\n\n\n\n<p>The profiling techniques we have discussed won\u2019t be complete without mentioning <code>EXPLAIN ANALYZE<\/code>.<\/p>\n\n\n\n<p>It works similarly to the <code>EXPLAIN<\/code> statement. The key difference is that the former is more verbose. You get more statistics about the query execution. <\/p>\n\n\n\n<p>Another difference is that <code>EXPLAIN<\/code> doesn\u2019t execute queries, but <code>EXPLAIN ANALYZE<\/code> does. That\u2019s because to fetch the missing statistics, it needs to communicate with the Storage engine. We\u2019ll explain how this communication works in a later section. Meanwhile, let\u2019s demonstrate this profiling technique. To profile a query with the <code>EXPLAIN ANALYZE<\/code> statement, you just need to prepend the query with the text like this:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-wrap-lines\">mysql&gt; EXPLAIN ANALYZE SELECT first_name, last_name, city, country\nFROM customer\nINNER JOIN address USING(address_id)\nINNER JOIN city USING(city_id) INNER JOIN country USING(country_id);\n\n+----------------------------------------------------------------------------------------------------------------------&gt;\n| EXPLAIN\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 &gt;\n+----------------------------------------------------------------------------------------------------------------------&gt;\n| -&gt; Nested loop inner join\u00a0 (cost=690.10 rows=599) (actual time=0.073..8.259 rows=599 loops=1)\n\u00a0 \u00a0 -&gt; Nested loop inner join\u00a0 (cost=480.45 rows=599) (actual time=0.064..5.635 rows=599 loops=1)\n\u00a0 \u00a0 \u00a0 \u00a0 -&gt; Nested loop inner join\u00a0 (cost=270.80 rows=599) (actual time=0.057..3.083 rows=599 loops=1)\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 -&gt; Table scan on customer\u00a0 (cost=61.15 rows=599) (actual time=0.043..0.478 rows=599 loops=1)\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 -&gt; Single-row index lookup on address using PRIMARY (address_id=customer.address_id)\u00a0 (cost=0.25 rows=1) (actual time=0.002..0.002 rows=1 loops=599)\n\u00a0 \u00a0 \u00a0 \u00a0 -&gt; Single-row index lookup on city using PRIMARY (city_id=address.city_id)\u00a0 (cost=0.25 rows=1) (actual time=0.002..0.002 rows=1 loops=599)\n\u00a0 \u00a0 -&gt; Single-row index lookup on country using PRIMARY (country_id=city.country_id)\u00a0 (cost=0.25 rows=1) (actual time=0.002..0.002 rows=1 loops=599)\n|\n+----------------------------------------------------------------------------------------------------------------------&gt;\n(END)<\/code><\/span><\/pre>\n\n\n<p>As you can see, the results show how the MySQL server executed the plan. It introduces a few more statistics:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The actual time to fetch the first row in a table (in milliseconds)<\/li>\n\n\n\n<li>The actual time to fetch all rows in a table (in milliseconds)<\/li>\n\n\n\n<li>The estimated costs of the query<\/li>\n\n\n\n<li>The actual number of rows read<\/li>\n\n\n\n<li>The actual number of loops made<\/li>\n<\/ul>\n\n\n\n<p>The estimated cost of a query\u2019s execution plan is the cumulative sum of the costs of all operations within the plan, such as I\/O or CPU operations.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Inspecting the last_query_cost session variable<\/h3>\n\n\n\n<p>Another quick tool to find out the estimated cost of a query is by inspecting the <code>last_query_cost<\/code> session variable:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">mysql&gt; SHOW STATUS LIKE <span class=\"hljs-string\">'last_query_cost'<\/span>;\n\n+-----------------+------------+\n| Variable_name \u00a0 | Value\u00a0 \u00a0 \u00a0 |\n+-----------------+------------+\n| Last_query_cost | <span class=\"hljs-number\">520.262389<\/span> |\n+-----------------+------------+\n<span class=\"hljs-number\">1<\/span> row <span class=\"hljs-keyword\">in<\/span> <span class=\"hljs-keyword\">set<\/span> (0.00 sec)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The results show the cost of the last compiled query\u2019s plan computed by the query optimizer. It is a useful way to compare two or more versions of your query. In the following example, we run two similar queries and compare the cost for each one. The results show that the execution plan of our first query would cost less:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">mysql&gt; SELECT first_name, last_name, city, country FROM customer INNER JOIN address USING(address_id) INNER JOIN city USING(city_id) INNER JOIN country USING(country_id);\n\n+-------------+--------------+----------------------------+---------------+\n| first_name\u00a0 | last_name\u00a0 \u00a0 | city \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | country \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n+-------------+--------------+----------------------------+---------------+\n| VERA\u00a0 \u00a0 \u00a0 \u00a0 | MCCOY\u00a0 \u00a0 \u00a0 \u00a0 | Kabul\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Afghanistan \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| MARIO \u00a0 \u00a0 \u00a0 | CHEATHAM \u00a0 \u00a0 | Batna\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Algeria \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| JUDY\u00a0 \u00a0 \u00a0 \u00a0 | GRAY \u00a0 \u00a0 \u00a0 \u00a0 | Bchar\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Algeria \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| JUNE\u00a0 \u00a0 \u00a0 \u00a0 | CARROLL\u00a0 \u00a0 \u00a0 | Skikda \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Algeria \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| ANTHONY \u00a0 \u00a0 | SCHWAB \u00a0 \u00a0 \u00a0 | Tafuna \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | American Samoa\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n--skipping--\n<span class=\"hljs-number\">599<\/span> rows <span class=\"hljs-keyword\">in<\/span> <span class=\"hljs-keyword\">set<\/span> (0.06 sec)\n\nmysql&gt; SHOW STATUS LIKE 'last_query_cost';\n\n+-----------------+------------+\n| Variable_name \u00a0 | Value\u00a0 \u00a0 \u00a0 |\n+-----------------+------------+\n| Last_query_cost | 642.952482 |\n+-----------------+------------+\n1 row in <span class=\"hljs-keyword\">set<\/span> (0.00 sec)\n\n\nmysql&gt; SELECT STRAIGHT_JOIN first_name, last_name, city, country FROM customer INNER JOIN address USING(address_id) INNER JOIN city USING(city_id) INNER JOIN country USING(country_id);\n\n+-------------+--------------+----------------------------+---------------+\n| first_name\u00a0 | last_name\u00a0 \u00a0 | city \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | country \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n+-------------+--------------+----------------------------+---------------+\n| MARY\u00a0 \u00a0 \u00a0 \u00a0 | SMITH\u00a0 \u00a0 \u00a0 \u00a0 | Sasebo \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Japan \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| PATRICIA\u00a0 \u00a0 | JOHNSON\u00a0 \u00a0 \u00a0 | San Bernardino \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | United States \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| LINDA \u00a0 \u00a0 \u00a0 | WILLIAMS \u00a0 \u00a0 | Athenai\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Greece\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| BARBARA \u00a0 \u00a0 | JONES\u00a0 \u00a0 \u00a0 \u00a0 | Myingyan \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Myanmar \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n| ELIZABETH \u00a0 | BROWN\u00a0 \u00a0 \u00a0 \u00a0 | Nantou \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | Taiwan\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n--skipping--\n599 rows in <span class=\"hljs-keyword\">set<\/span> (0.01 sec)\n\nmysql&gt; SHOW STATUS LIKE 'last_query_cost';\n\n+-----------------+------------+\n| Variable_name \u00a0 | Value\u00a0 \u00a0 \u00a0 |\n+-----------------+------------+\n| Last_query_cost | 690.099000 |\n+-----------------+------------+\n1 row in <span class=\"hljs-keyword\">set<\/span> (0.00 sec)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Optimize slow queries<\/h2>\n\n\n\n<p>Now that we\u2019ve seen the several ways we can identify and analyze slow queries, the next thing would be to fix the slow-performing queries. Instinctively, the first thing we would do is to check the query itself. The following techniques demonstrate how we can optimize slow queries.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Only retrieve the data you need<\/h3>\n\n\n\n<p>While the server scans all the rows your query asks for, it locks any resources available in the session, and the client cannot interrupt the process without you forcefully quitting the session. Also, the client cannot selectively access data until the server has scanned all the rows.<\/p>\n\n\n\n<p>The query you\u2019re sending may be asking for too much data, which causes the MySQL server to waste time scanning many rows of data. But we can fix it by doing the following:<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Fetch the number of rows you need only (pagination)<\/h4>\n\n\n\n<p>Imagine you\u2019re building a movie database website like IMDB that only needs to fetch about 10 rows of movie information at a time (per page). Usually, you <strong>will not<\/strong> fetch all the data like this:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-wrap-lines\">mysql&gt; SELECT * FROM film;\n\n+---------+-----------------------------+---------------------------------&gt;\n| film_id | title \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | description\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\n+---------+-----------------------------+---------------------------------&gt;\n| \u00a0 \u00a0 \u00a0 1 | ACADEMY DINOSAUR\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Epic Drama of a Feminist And a&gt;\n| \u00a0 \u00a0 \u00a0 2 | ACE GOLDFINGER\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Astounding Epistle of a Databa&gt;\n| \u00a0 \u00a0 \u00a0 3 | ADAPTATION HOLES\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Astounding Reflection of a Lif&gt;\n| \u00a0 \u00a0 \u00a0 4 | AFFAIR PREJUDICE\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Fanciful Documentary of a Fris&gt;\n| \u00a0 \u00a0 \u00a0 5 | AFRICAN EGG \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Fast-Paced Documentary of an A&gt;\n| \u00a0 \u00a0 \u00a0 6 | AGENT TRUMAN\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Intrepid Panorama of a Robot a&gt;\n| \u00a0 \u00a0 \u00a0 7 | AIRPLANE SIERRA \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Touching Saga of a Hunter And &gt;\n| \u00a0 \u00a0 \u00a0 8 | AIRPORT POLLOCK \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Epic Tale of a Moose And a Gir&gt;\n| \u00a0 \u00a0 \u00a0 9 | ALABAMA DEVIL \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Thoughtful Panorama of a Datab&gt;--skipping--\n| \u00a0 \u00a0 991 | WORST BANGER\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Thrilling Drama of a Madman An&gt;\n| \u00a0 \u00a0 992 | WRATH MILE\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Intrepid Reflection of a Techn&gt;\n| \u00a0 \u00a0 993 | WRONG BEHAVIOR\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Emotional Saga of a Crocodile &gt;\n| \u00a0 \u00a0 994 | WYOMING STORM \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Awe-Inspiring Panorama of a Ro&gt;\n| \u00a0 \u00a0 995 | YENTL IDAHO \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Amazing Display of a Robot And&gt;\n| \u00a0 \u00a0 996 | YOUNG LANGUAGE\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Unbelieveable Yarn of a Boat A&gt;\n| \u00a0 \u00a0 997 | YOUTH KICK\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Touching Drama of a Teacher An&gt;\n| \u00a0 \u00a0 998 | ZHIVAGO CORE\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Fateful Yarn of a Composer And&gt;\n| \u00a0 \u00a0 999 | ZOOLANDER FICTION \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Fateful Reflection of a Waitre&gt;\n|\u00a0 \u00a0 1000 | ZORRO ARK \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Intrepid Panorama of a Mad Sci&gt;\n+---------+-----------------------------+---------------------------------&gt;\n(END)<\/code><\/span><\/pre>\n\n\n<p>Doing so would make the MySQL server scan the complete 1000 rows of the table for the little data we need.<\/p>\n\n\n\n<p>&nbsp;We can fix our query using a common database technique known as <strong>pagination<\/strong>. We can use the <code>LIMIT<\/code> and <code>OFFSET<\/code> clauses to specify the number of rows we need and from which row it should start fetching data. The aim of this guide is not to teach you pagination in-depth (we\u2019ll talk about this in another article), but our optimized version of our query would look like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">mysql&gt; SELECT * <span class=\"hljs-keyword\">from<\/span> film LIMIT <span class=\"hljs-number\">10<\/span>;\n\n+---------+------------------+--------------------+--------------+-&gt;\n| film_id | title\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | description\u00a0 \u00a0 \u00a0 \u00a0 | release_year | &gt;\n+---------+------------------+--------------------+--------------+-&gt;\n| \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">1<\/span> | ACADEMY DINOSAUR | A Epic Drama <span class=\"hljs-keyword\">of<\/span>... | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span> | &gt;\n| \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2<\/span> | ACE GOLDFINGER \u00a0 | A Astounding Ep... | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span> | &gt;\n| \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">3<\/span> | ADAPTATION HOLES | A Astounding Re... | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span> | &gt;\n| \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">4<\/span> | AFFAIR PREJUDICE | A Fanciful Docu... | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span> | &gt;\n| \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">5<\/span> | AFRICAN EGG\u00a0 \u00a0 \u00a0 | A Fast-Paced Do... | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span> | &gt;\n| \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">6<\/span> | AGENT TRUMAN \u00a0 \u00a0 | A Intrepid Pano... | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span> | &gt;\n| \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">7<\/span> | AIRPLANE SIERRA\u00a0 | A Touching Saga... | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span> | &gt;\n| \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">8<\/span> | AIRPORT POLLOCK\u00a0 | A Epic Tale <span class=\"hljs-keyword\">of<\/span> ... | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span> | &gt;\n| \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">9<\/span> | ALABAMA DEVIL\u00a0 \u00a0 | A Thoughtful Pa... | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span> | &gt;\n|\u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">10<\/span> | ALADDIN CALENDAR | A Action-Packed... | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span> | &gt;\n+---------+------------------+--------------------+--------------+-&gt;\n(END)\n<span class=\"hljs-number\">10<\/span> rows <span class=\"hljs-keyword\">in<\/span> <span class=\"hljs-keyword\">set<\/span> (0.01 sec)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">mysql&gt; SELECT * <span class=\"hljs-keyword\">from<\/span> film LIMIT <span class=\"hljs-number\">10<\/span> OFFSET <span class=\"hljs-number\">10<\/span>;\n\n+---------+---------------------+--------------------+-------------&gt;\n| film_id | title \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | description\u00a0 \u00a0 \u00a0 \u00a0 | release_year&gt;\n+---------+---------------------+--------------------+-------------&gt;\n|\u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">11<\/span> | ALAMO VIDEOTAPE \u00a0 \u00a0 | A Boring Epistl... | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span>&gt;\n|\u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">12<\/span> | ALASKA PHANTOM\u00a0 \u00a0 \u00a0 | A Fanciful Saga... | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span>&gt;\n|\u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">13<\/span> | ALI FOREVER \u00a0 \u00a0 \u00a0 \u00a0 | A Action-Packed... | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span>&gt;\n|\u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">14<\/span> | ALICE FANTASIA\u00a0 \u00a0 \u00a0 | A Emotional Dra... | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span>&gt;\n|\u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">15<\/span> | ALIEN CENTER\u00a0 \u00a0 \u00a0 \u00a0 | A Brilliant Dra... | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span>&gt;\n|\u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">16<\/span> | ALLEY EVOLUTION \u00a0 \u00a0 | A Fast-Paced Dr... | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span>&gt;\n|\u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">17<\/span> | ALONE TRIP\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Fast-Paced Ch... | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span>&gt;\n|\u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">18<\/span> | ALTER VICTORY \u00a0 \u00a0 \u00a0 | A Thoughtful Dr... | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span>&gt;\n|\u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">19<\/span> | AMADEUS HOLY\u00a0 \u00a0 \u00a0 \u00a0 | A Emotional Dis... | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span>&gt;\n|\u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">20<\/span> | AMELIE HELLFIGHTERS | A Boring Drama ... | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span>&gt;\n+---------+---------------------+--------------------+-------------&gt;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h4 class=\"wp-block-heading\">Fetch only the columns you need<\/h4>\n\n\n\n<p>Our queries in the last example are still not fully optimized. If you observe them, you will notice we fetched our data in all the table columns using <code>SELECT *<\/code> (select all). A better way to have accessed our data would have been by naming the columns we need in our query. Let\u2019s see the columns our <code>film<\/code> table has:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">mysql&gt; SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS\u00a0WHERE TABLE_NAME = <span class=\"hljs-string\">'film'<\/span>;\n\n+----------------------+\n| COLUMN_NAME\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 |\n+----------------------+\n| description\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 |\n| film_id\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 |\n| language_id\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 |\n| last_update\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 |\n| length \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 |\n| original_language_id |\n| rating \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 |\n| release_year \u00a0 \u00a0 \u00a0 \u00a0 |\n| rental_duration\u00a0 \u00a0 \u00a0 |\n| rental_rate\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 |\n| replacement_cost \u00a0 \u00a0 |\n| special_features \u00a0 \u00a0 |\n| title\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 |\n+----------------------+\n<span class=\"hljs-number\">13<\/span> rows <span class=\"hljs-keyword\">in<\/span> <span class=\"hljs-keyword\">set<\/span> (0.00 sec)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>For our movie data application, we might only need the films\u2019 titles, descriptions, ratings, and release years. Here\u2019s how we would ask for such data:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">mysql&gt; SELECT title, description, rating, release_year FROM film LIMIT <span class=\"hljs-number\">10<\/span> OFFSET <span class=\"hljs-number\">10<\/span>;\n\n+---------------------+--------------------+--------+--------------+\n| title \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | description\u00a0 \u00a0 \u00a0 \u00a0 | rating | release_year |\n+---------------------+--------------------+--------+--------------+\n| ALAMO VIDEOTAPE \u00a0 \u00a0 | A Boring Epistl... | G\u00a0 \u00a0 \u00a0 | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span> |\n| ALASKA PHANTOM\u00a0 \u00a0 \u00a0 | A Fanciful Saga... | PG \u00a0 \u00a0 | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span> |\n| ALI FOREVER \u00a0 \u00a0 \u00a0 \u00a0 | A Action-Packed... | PG \u00a0 \u00a0 | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span> |\n| ALICE FANTASIA\u00a0 \u00a0 \u00a0 | A Emotional Dra... | NC<span class=\"hljs-number\">-17<\/span>\u00a0 | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span> |\n| ALIEN CENTER\u00a0 \u00a0 \u00a0 \u00a0 | A Brilliant Dra... | NC<span class=\"hljs-number\">-17<\/span>\u00a0 | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span> |\n| ALLEY EVOLUTION \u00a0 \u00a0 | A Fast-Paced Dr... | NC<span class=\"hljs-number\">-17<\/span>\u00a0 | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span> |\n| ALONE TRIP\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 | A Fast-Paced Ch... | R\u00a0 \u00a0 \u00a0 | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span> |\n| ALTER VICTORY \u00a0 \u00a0 \u00a0 | A Thoughtful Dr... | PG<span class=\"hljs-number\">-13<\/span>\u00a0 | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span> |\n| AMADEUS HOLY\u00a0 \u00a0 \u00a0 \u00a0 | A Emotional Dis... | PG \u00a0 \u00a0 | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span> |\n| AMELIE HELLFIGHTERS | A Boring Drama ... | R\u00a0 \u00a0 \u00a0 | \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-number\">2006<\/span> |\n+---------------------+--------------------+--------+--------------+\n<span class=\"hljs-number\">10<\/span> rows <span class=\"hljs-keyword\">in<\/span> <span class=\"hljs-keyword\">set<\/span> (0.00 sec)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h4 class=\"wp-block-heading\">Fetch the columns you need when joining tables<\/h4>\n\n\n\n<p>Just like the previous example. You can make your joins faster by only asking for the columns you need in the result set. Let\u2019s say you want to fetch only the first and last names of all the customers in our earlier <code>join<\/code> example. We would write our query in this manner:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-wrap-lines\">SELECT first_name, last_name\nFROM customer\nINNER JOIN address USING(address_id)\nINNER JOIN city USING(city_id)\nINNER JOIN country USING(country_id);<\/code><\/span><\/pre>\n\n\n<p>Doing so will prevent the MySQL server from scanning all the columns of the merged tables.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Use caching strategies to prevent fetching the same data repeatedly<\/h4>\n\n\n\n<p>If your application uses the same piece of data frequently, instead of running the query every time the app needs the data, you can cache the data the first time the query runs and simply reuse the data subsequently. <\/p>\n\n\n\n<p>The only exception is if the data changes frequently or when you need your queried information to be up-to-date every time. There used to be an in-built Cache layer in MySQL called \u201c<a href=\"https:\/\/dev.mysql.com\/doc\/refman\/5.7\/en\/query-cache.html\" target=\"_blank\" rel=\"noopener\">The Query Cache<\/a>,\u201d but <a href=\"https:\/\/dev.mysql.com\/blog-archive\/mysql-8-0-retiring-support-for-the-query-cache\/\" target=\"_blank\" rel=\"noopener\">it was deprecated<\/a> for scalability reasons. Luckily, some tools can help cache data, such as <a href=\"https:\/\/memcached.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Memcached<\/a> and <a href=\"https:\/\/redis.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">Redis<\/a>.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Avoid LIKE expressions with leading wildcards<\/h3>\n\n\n\n<p>Wildcards are used in conjunction with <code>LIKE<\/code> clauses. They are characters used to help search for data that match complex criteria.<\/p>\n\n\n\n<p>The position of wildcard characters in the <code>LIKE<\/code> clause can cause unexpected performance behavior. Using leading wildcards has been known to be the culprit most times, and this is because MySQL is not able to utilize indexes efficiently when you have a leading wildcard:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">SELECT * FROM customer WHERE first_name LIKE <span class=\"hljs-string\">'%AB%'<\/span>; <span class=\"hljs-comment\">\/\/ Leading wildcard causes poor performance<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">UNION vs. UNION ALL<\/h3>\n\n\n\n<p>The MySQL query optimizer doesn\u2019t apply as many optimizations to <code>UNION<\/code> queries as other queries, but you can specify clauses such as <code>WHERE<\/code>, <code>LIMIT<\/code>, <code>ORDER BY<\/code>, etc., to help the optimizer. But an even better option would be to use the <code>UNION ALL<\/code> clause if you need to eliminate duplicate rows in your query result. <\/p>\n\n\n\n<p>That\u2019s because omitting <code>ALL<\/code> adds the <code>DISTINCT<\/code> option to the temporary table, which results in the MySQL server making expensive scans on all the rows to determine uniqueness. The key takeaway is to avoid using <code>DISTINCT<\/code> and <code>UNION<\/code> unless necessary.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Optimize JOIN queries<\/h3>\n\n\n\n<p>You should use <code>INNER JOIN<\/code> instead of <code>OUTER JOIN<\/code> where possible. You can also optimize your <code>JOIN<\/code> queries by ensuring that indexes are present on the columns specified in the <code>ON<\/code> and <code>USING<\/code> clauses.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">GROUP BY and ORDER BY<\/h3>\n\n\n\n<p>&nbsp;<code>GROUP BY<\/code> and <code>ORDER BY<\/code> expressions should always refer to columns from a single table so that the server can use indexes for such operations.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Optimize COUNT() queries<\/h3>\n\n\n\n<p>The <code>COUNT()<\/code> function is a special aggregate function used in two ways. First, when you specify a column name or expression in the <code>COUNT()<\/code> function, it counts the number of times that expression has value (i.e. a non-NULL expression).<\/p>\n\n\n\n<p>The second way to use <code>COUNT()<\/code> is for counting the number of rows in a query result using the <code>COUNT(*)<\/code> expression. It ignores the columns in the table and counts rows instead; therefore, it\u2019s best to use this version when you only need to count the number of rows in the query result.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How to help the MySQL optimizer<\/h3>\n\n\n\n<p>There are a few cases where the optimizer may not choose the best execution path. The reasons could be that MySQL does not have sufficient information about the current data and must make \u201ceducated\u201d guesses about the data.<\/p>\n\n\n\n<p>In such cases, there are some methods available to help MySQL:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Using the <code>EXPLAIN<\/code> statement to view information about how MySQL processed your query. I showed you how to do that earlier.<\/li>\n\n\n\n<li>Using <code>FORCE INDEX<\/code>, <code>USE INDEX<\/code>, and <code>IGNORE INDEX<\/code><a href=\"https:\/\/dev.mysql.com\/doc\/refman\/5.7\/en\/index-hints.html\" target=\"_blank\" rel=\"noopener\"> (Index hints)<\/a> for the scanned table to tell MySQL that the table scans are expensive compared to using the given index. Index hints give you&nbsp; total control over how the optimizer chooses indexes for a query execution plan. It\u2019s best to use this as a last resort after trying other optimization techniques discussed. The syntax for using index hints looks like this:<\/li>\n<\/ul>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-wrap-lines\">SELECT * FROM table1, table2 FORCE INDEX (index_of_column)\nWHERE table1.col_name=table2.col_name;<\/code><\/span><\/pre>\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/dev.mysql.com\/doc\/refman\/5.7\/en\/optimizer-hints.html\" target=\"_blank\" rel=\"noopener\">Applying Optimizer Hints<\/a> at the different scope levels: global, query block, table-level, or index-level to control the optimizer\u2019s strategies based on certain criteria. Optimizer hints are usually applied per statement and can be used together with Index hints. They look like comments but are recognized by the query parser. The syntax for adding optimizer hints looks like this:<\/li>\n<\/ul>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">SELECT <span class=\"hljs-comment\">\/*+ NO_RANGE_OPTIMIZATION(t3 PRIMARY, f2_idx) *\/<\/span> f1\n\u00a0 FROM t3 WHERE f1 &gt; <span class=\"hljs-number\">30<\/span> AND f1 &lt; <span class=\"hljs-number\">33<\/span>;\nSELECT <span class=\"hljs-comment\">\/*+ BKA(t1) NO_BKA(t2) *\/<\/span> * FROM t1 INNER JOIN t2 WHERE ...;\nSELECT <span class=\"hljs-comment\">\/*+ NO_ICP(t1, t2) *\/<\/span> * FROM t1 INNER JOIN t2 WHERE ...;\nSELECT <span class=\"hljs-comment\">\/*+ SEMIJOIN(FIRSTMATCH, LOOSESCAN) *\/<\/span> * FROM t1 ...;\nEXPLAIN SELECT <span class=\"hljs-comment\">\/*+ NO_ICP(t1) *\/<\/span> * FROM t1 WHERE ...;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<ul class=\"wp-block-list\">\n<li>Using the <code>ANALYZE TABLE table_name<\/code> to<a href=\"https:\/\/dev.mysql.com\/doc\/refman\/5.7\/en\/analyze-table.html\" target=\"_blank\" rel=\"noopener\"> update the key distributions of the scanned table<\/a>.<\/li>\n\n\n\n<li>Using<a href=\"https:\/\/dev.mysql.com\/doc\/refman\/5.7\/en\/select.html\" target=\"_blank\" rel=\"noopener\"> Global and table-level <code>STRAIGHT_JOIN<\/code><\/a>.<\/li>\n\n\n\n<li><a href=\"https:\/\/dev.mysql.com\/doc\/refman\/5.7\/en\/server-system-variables.html\" target=\"_blank\" rel=\"noopener\">Tuning global or thread-specific system variables<\/a>. For example, you can start the <code>mysqld <\/code>with the <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/5.7\/en\/server-system-variables.html#sysvar_max_seeks_for_key\" target=\"_blank\" rel=\"noopener\"><code>--max-seeks-for-key=1000<\/code><\/a> option or use <code>SET<\/code> <code>max_seeks_for_key=1000<\/code> to tell the optimizer to assume that no key scan causes more than 1,000 key seeks.<\/li>\n\n\n\n<li>Re-writing your query: Sometimes, your query might need refactoring to improve its response time. We discussed some scenarios we could do in the previous sections.<\/li>\n\n\n\n<li>Adding Indexes to your tables: For tables that do not need frequent batch inserts or updates, you might want to use an index. Indexes also speed up <code>JOIN<\/code> operations and help MySQL retrieve data faster, thereby improving your query response time. For smaller tables, adding indexes may not be optimal. Also, you should not index everything, as it increases disk space usage and makes updating or inserting records slow.<\/li>\n\n\n\n<li>Re-designing your schema: To access tables with the best possible speed, you must consider schema optimization. A poorly designed database schema forces MySQL to take a longer and utilize more system resources when processing your queries.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">The next step: schema optimization and indexing<\/h2>\n\n\n\n<p>Query optimization is a crucial subject in relational databases such as MySQL. Understanding how MySQL executes queries allows you to determine where it spends time on your queries. You also understand how the response time can be affected by the quality of the queries you send to the MySQL server.&nbsp;<\/p>\n\n\n\n<p>Query optimization is just one piece of the puzzle in improving MySQL performance. Schema optimization and indexing are other options to consider. In fact, schemas and indexing can affect query speed and vice versa. Our next article will explain how schema optimization and indexing can affect your MySQL database performance and query execution speed.<\/p>\n\n\n\n<p><em>I&#8217;m Elvis Duru. I create helpful content for web developers with a focus on React, Node.js, SQL\/NoSQL Databases, and more! Let&#8217;s connect on<\/em><a href=\"https:\/\/twitter.com\/ElvisDuru\" target=\"_blank\" rel=\"noopener\"><em> <\/em><em>Twitter<\/em><\/a><em>.<\/em><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Further reading<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/dev.mysql.com\/doc\/internals\/en\/optimizer.html\" target=\"_blank\" rel=\"noopener\">MySQL Query Optimizer<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/dev.mysql.com\/doc\/refman\/5.7\/en\/optimizer-hints.html\" target=\"_blank\" rel=\"noopener\">Optimizer Hints<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/dev.mysql.com\/doc\/refman\/5.7\/en\/index-hints.html\" target=\"_blank\" rel=\"noopener\">Index Hints<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/dev.mysql.com\/doc\/refman\/5.6\/en\/index-statistics.html\" target=\"_blank\" rel=\"noopener\">Index Statistics<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/mysqldumpslow.html\" target=\"_blank\" rel=\"noopener\">Summarize Slow Query Log Files<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/explain.html\" target=\"_blank\" rel=\"noopener\">The EXPLAIN statement<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/learning.oreilly.com\/library\/view\/high-performance-mysql\/9781492080503\/ch08.html#query_execution_basics\" target=\"_blank\" rel=\"noopener\">The Query LifeCycle<\/a> &#8211; High Performance SQL by <a href=\"https:\/\/learning.oreilly.com\/search\/?query=author%3A%22Silvia%20Botros%22&amp;sort=relevance&amp;highlight=true\" target=\"_blank\" rel=\"noopener\">Silvia Botros<\/a>, <a href=\"https:\/\/learning.oreilly.com\/search\/?query=author%3A%22Jeremy%20Tinley%22&amp;sort=relevance&amp;highlight=true\" target=\"_blank\" rel=\"noopener\">Jeremy Tinley<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Queries contribute to the performance of a MySQL database. For example, poorly written queries can prevent the optimizer from performing well. This article provides an in-depth guide to drastically improving MySQL performance to educate database engineers on query optimization.<\/p>\n","protected":false},"author":1,"featured_media":35920,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[],"persona":[29],"blog-programming-language":[66],"keyword-cluster":[],"class_list":["post-15457","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development"],"acf":[],"_links":{"self":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts\/15457","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/comments?post=15457"}],"version-history":[{"count":69,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts\/15457\/revisions"}],"predecessor-version":[{"id":35921,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts\/15457\/revisions\/35921"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/media\/35920"}],"wp:attachment":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/media?parent=15457"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/categories?post=15457"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/tags?post=15457"},{"taxonomy":"persona","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/persona?post=15457"},{"taxonomy":"blog-programming-language","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/blog-programming-language?post=15457"},{"taxonomy":"keyword-cluster","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/keyword-cluster?post=15457"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}