Mysql index principle and slow query optimization

background

MySQL has become the first choice for most companies with low-cost and rich Internet resources. Although the performance is excellent, how to better use the so-called "good horse with good saddle" has become a compulsory course for development engineers. We often see requirements such as "proficient in MySQL", "SQL statement optimization" and "understanding database principle" from the job description. We know that in general application systems, the read-write ratio is about 10:1, and there are few performance problems in insert operations and general update operations. The most common and most prone to problems are some complex query operations, so the optimization of query statements is obviously the top priority.

Since July 2013, I have been doing the optimization of slow query in meituan core business system department. There are more than ten systems in total, and hundreds of slow query cases have been solved and accumulated. With the increasing complexity of the business, the problems encountered are strange and unimaginable. This paper aims to explain the principle of database index and how to optimize slow query from the perspective of development engineers.

Reflections on a slow query

select
   count(*) 
from
   task 
where
   status=2 
   and operator_id=20839 
   and operate_time>1371169729 
   and operate_time<1371174603 
   and type=2;

System users responded that a function was getting slower and slower, so the engineer found the SQL above.

And found me with interest, "this SQL needs to be optimized. Add indexes to every field for me".

I was surprised and asked, "why do you need to index every field?"

"It will be faster to index all the query fields", the engineer was full of confidence.

"In this case, a joint index can be built, because it is the leftmost prefix matching, so the operate_time needs to be put last, and other relevant queries need to be brought, so a comprehensive evaluation needs to be done."

"Joint index? Leftmost prefix matching? Comprehensive evaluation?" The engineer was lost in thought.

In most cases, we know that indexing can improve query efficiency, but how to build an index? What is the order of indexes? Many people only know about it. In fact, it is not difficult to understand these concepts, and the principle of index is far less complex than expected.

MySQL index principle

Index purpose

The purpose of index is to improve query efficiency. It can be compared with dictionary. If we want to look up the word "mysql", we must locate the letter M, then find the letter y from bottom to bottom, and then find the remaining sql. If there is no index, you may need to look through all the words to find what you want. What if I want to find the word beginning with m? Or the word that begins with ze? Do you think this can't be done without an index?

Index principle

In addition to dictionaries, examples of indexes can be seen everywhere in life, such as train number tables at railway stations, catalogues of books, etc. Their principles are the same. We filter out the final desired results by constantly narrowing the range of data we want to obtain, and turn random events into sequential events, that is, we always lock the data through the same search method.

Database is the same, but it is obviously much more complex, because it is faced with not only equivalent query, but also range query (>, <, between, in), fuzzy query (like), union query (or), etc. How should the database deal with all the problems? Let's recall the example of the dictionary. Can we divide the data into segments and then query them in segments? In the simplest way, if 1000 pieces of data are divided into the first segment from 1 to 100, the second segment from 101 to 200, and the third segment from 201 to 300... In this way, you can find the third segment for the 250 pieces of data, and 90% of the invalid data is removed at once. But if it's a record of 10 million, how many paragraphs are better? Students with a little basic algorithm will think of the search tree. Its average complexity is lgN and has good query performance. But here we ignore a key problem. The complexity model is based on the same operation cost every time. The implementation of the database is relatively complex, and the data is saved on the disk. In order to improve the performance, we can read part of the data into the memory for calculation every time, because we know that the cost of accessing the disk is about 100000 times that of accessing the memory, Therefore, a simple search tree is difficu lt to meet complex application scenarios.

Disk IO and read ahead

As mentioned earlier, disk IO and pre reading are briefly introduced here. Disk data reading depends on mechanical movement. The time spent reading data each time can be divided into three parts: seek time, rotation delay and transmission time. Seek time refers to the time required for the magnetic arm to move to the specified track. Mainstream disks are generally less than 5ms; Rotation delay is the disk speed we often hear about. For example, 7200 revolutions of a disk means 7200 revolutions per minute, that is, 120 revolutions per second. The rotation delay is 1/120/2 = 4.17ms; Transmission time refers to the time to read or write data from or to the disk. It is generally a few tenths of a millisecond, which can be ignored relative to the first two times. So the time to access a disk, that is, the time to io on a disk is about 5+4.17 = 9ms, which sounds pretty good, but you should know that a 500 MIPS machine can execute 500 million instructions per second, because the instructions rely on the nature of electricity. In other words, the time to execute an IO can execute 400000 instructions, and the database is prone to 100000 or even tens of millions of data, Nine milliseconds at a time is obviously a disaster. The following figure is a comparison diagram of computer hardware delay for your reference:

various-system-software-hardware-latencies

Considering that disk IO is a very expensive operation, the computer operating system has made some optimization. During an IO, not only the data of the current disk address, but also the adjacent data are read into the memory buffer, because the principle of local pre reading tells us that when the computer accesses the data of an address, the data adjacent to it will also be accessed quickly. The data read by each IO is called a page. The specific data size of a page is related to the operating system, generally 4k or 8k, that is, when we read the data in a page, there is actually only one io. This theory is very helpful for the data structure design of the index.

Indexed data structure

We talked about the example of index in life, the basic principle of index, the complexity of database, and the relevant knowledge of operating system. The purpose is to let you know that any kind of data structure is not produced out of thin air, and there must be its background and use scenario. Now let's summarize what we need this data structure to do, which is actually very simple, That is: control the disk IO times to a small order of magnitude, preferably a constant order of magnitude, every time you look for data. So we think that if a highly controllable multi-channel search tree can meet the demand? In this way, b + tree came into being.

Detailed explanation of b + tree

b + tree

As shown in the figure above, it is a b + tree. For the definition of b + tree, see B + tree , here are just some key points. The light blue block is called a disk block. You can see that each disk block contains several data items (shown in dark blue) and pointers (shown in yellow). For example, disk block 1 contains data items 17 and 35, including pointers P1, P2 and P3. P1 represents the disk block smaller than 17, P2 represents the disk block between 17 and 35, and P3 represents the disk block greater than 35. Real data exists in leaf nodes, i.e. 3, 5, 9, 10, 13, 15, 28, 29, 36, 60, 75, 79, 90 and 99. Non leaf nodes do not store real data, but only data items that guide the search direction. For example, 17 and 35 do not really exist in the data table.

Search process of b + tree

As shown in the figure, if you want to find the data item 29, first load the disk block 1 from the disk into the memory. At this time, an IO occurs. Use the binary search in the memory to determine that the 29 is between 17 and 35, lock the P2 pointer of disk block 1, and the memory time is negligible because it is very short (compared with the IO of the disk), Load disk block 3 from disk to memory through the disk address of P2 pointer of disk block 1, and the second IO occurs. When 29 is between 26 and 30, lock the P2 pointer of disk block 3, load disk block 8 to memory through the pointer, and the third IO occurs. At the same time, do binary search in memory to find 29, and end the query. There are three IOS in total. The real situation is that the three-tier b + tree can represent millions of data. If millions of data searches only need three IO, the performance improvement will be huge. If there is no index and each data item needs one IO, then a total of millions of IO are required. Obviously, the cost is very high.

b + tree properties

1. Through the above analysis, we know that the number of IO depends on the height h of the number of b +. Assuming that the data in the current data table is N and the number of data items in each disk block is m, there is h = ㏒ (m+1)N. when the amount of data N is certain, the larger m, the smaller h; M = size of disk block / size of data item. The size of disk block, that is, the size of a data page, is fixed. If the space occupied by data item is smaller, the number of data items is more, and the height of tree is lower. This is why each data item, that is, the index field should be as small as possible. For example, int takes up 4 bytes, which is half less than bigint8 bytes. This is why the b + tree requires that the real data be placed in the leaf node instead of the inner node. Once placed in the inner node, the data items of the disk block will decrease significantly, resulting in the increase of the tree. When the data item is equal to 1, it will degenerate into a linear table.

2. When the data item of the b + tree is a composite data structure, such as (name,age,sex), the b + number establishes the search tree from left to right. For example, when data such as (Zhang San, 20,F) is retrieved, the b + tree will give priority to comparing the name to determine the search direction in the next step. If the name is the same, then compare age and sex in turn to get the retrieved data; However, when there is no name data like (20,F), the b + tree does not know which node to check next, because name is the first comparison factor when establishing the search tree. You must search according to name first to know where to query next. For example, when retrieving data such as (Zhang San, F), the b + tree can use name to specify the search direction, but the next field age is missing, so we can only find the data whose name is equal to Zhang San, and then match the data whose gender is F. this is a very important property, that is, the leftmost matching feature of the index.

Slow query optimization

The principle of MySQL indexing is a boring thing. We only need to have a perceptual understanding, and we don't need to understand it very thoroughly and deeply. Let's look back at the slow query we talked about at the beginning. After understanding the indexing principle, do you have any ideas? First summarize the basic principles of indexing:

Several principles of indexing

1. The leftmost prefix matching principle is a very important principle. mysql will match to the right until it meets the range query (>, <, between and like). For example, a = 1 and B = 2 and C > 3 and d = 4. If the index of (a,b,c,d) order is established, D can not be indexed. If the index of (a,b,d,c) order is established, it can be used. The order of a,b,d can be adjusted arbitrarily.

2. = and in can be out of order. For example, a = 1 and b = 2 and c = 3. The establishment of (a,b,c) indexes can be in any order. The query optimizer of mysql will help you optimize them into a form that can be recognized by the index.

3. Try to select the column with high discrimination as the index. The formula of discrimination is count(distinct col)/count(*), which indicates the proportion of fields that are not repeated. The larger the proportion, the fewer records we scan, and the discrimination of the unique key is 1. Some status and gender fields may have a discrimination of 0 in front of big data. Someone may ask, what is the empirical value of this proportion? It is also difficult to determine this value in different usage scenarios. Generally, the fields that need to be join ed are required to be more than 0.1, that is, an average of 10 records per scan.

4. The index column cannot participate in the calculation. Keep the column "clean", such as from_unixtime(create_time) = '2014-05-29' can't use the index. The reason is very simple. All the field values in the data table are stored in the b + tree, but when searching, you need to apply functions to all elements to compare. Obviously, the cost is too high. So the statement should be written as create_time = unix_timestamp(’2014-05-29’).

5. Try to expand the index instead of creating a new index. For example, if there is already an index of a in the table and you want to add an index of (a,b), you only need to modify the original index.

Back to the beginning of the slow query

According to the leftmost matching principle, the index of the initial sql statement should be status and operator_id,type,operate_ Joint index of time; Including status and operator_ The order of ID and type can be reversed, so I will say that all relevant queries in this table will be found and analyzed comprehensively; For example, there are the following queries:

select * from task where status = 0 and type = 12 limit 10;
select count(*) from task where status = 0 ;

Then the index establishment (status,type,operator_id,operate_time) is very correct, because it can cover all situations. This makes use of the leftmost matching principle of the index

Query optimization artifact - explain command

I believe you are not unfamiliar with the explain command. For specific usage and field meaning, please refer to the official website explain-output Here, we need to emphasize that rows is the core indicator, and most of the small rows statements must execute very quickly (with exceptions, which will be discussed below). Therefore, the optimization statements are basically optimizing rows.

Basic steps of slow query optimization

0. Run it first to see if it is really slow. Pay attention to setting SQL_NO_CACHE

1. Check the where condition sheet and lock the minimum return record table. The meaning of this sentence is to apply the where of the query statement to the table with the smallest number of records returned in the table. Start to query each field of a single table to see which field has the highest discrimination

2.explain to check whether the execution plan is consistent with 1 expectation (query from the table with few locked records)

3. The sql statement in the form of order by limit gives priority to the sorted table

4. Understand the usage scenarios of the business party

5. When adding an index, refer to several principles of index building

6. If the observation results do not meet the expectations, continue to analyze from 0

Several slow query cases

The following examples explain in detail how to analyze and optimize slow queries.

Complex statement writing

In many cases, we write SQL only to realize functions, which is only the first step. Different statement writing methods often have essential differences in efficiency, which requires us to have a very clear understanding of mysql execution plan and indexing principles. Please see the following statement:

select
   distinct cert.emp_id 
from
   cm_log cl 
inner join
   (
      select
         emp.id as emp_id,
         emp_cert.id as cert_id 
      from
         employee emp 
      left join
         emp_certificate emp_cert 
            on emp.id = emp_cert.emp_id 
      where
         emp.is_deleted=0
   ) cert 
      on (
         cl.ref_table='Employee' 
         and cl.ref_oid= cert.emp_id
      ) 
      or (
         cl.ref_table='EmpCertificate' 
         and cl.ref_oid= cert.cert_id
      ) 
where
   cl.last_upd_date >='2013-11-07 15:03:00' 
   and cl.last_upd_date<='2013-11-08 16:00:00';

0. Run it first, 53 records for 1.87 seconds, and there is no aggregation statement, which is relatively slow

53 rows in set (1.87 sec)

1.explain

+----+-------------+------------+-------+---------------------------------+-----------------------+---------+-------------------+-------+--------------------------------+
| id | select_type | table      | type  | possible_keys                   | key                   | key_len | ref               | rows  | Extra                          |
+----+-------------+------------+-------+---------------------------------+-----------------------+---------+-------------------+-------+--------------------------------+
|  1 | PRIMARY     | cl         | range | cm_log_cls_id,idx_last_upd_date | idx_last_upd_date     | 8       | NULL              |   379 | Using where; Using temporary   |
|  1 | PRIMARY     | <derived2> | ALL   | NULL                            | NULL                  | NULL    | NULL              | 63727 | Using where; Using join buffer |
|  2 | DERIVED     | emp        | ALL   | NULL                            | NULL                  | NULL    | NULL              | 13317 | Using where                    |
|  2 | DERIVED     | emp_cert   | ref   | emp_certificate_empid           | emp_certificate_empid | 4       | meituanorg.emp.id |     1 | Using index                    |
+----+-------------+------------+-------+---------------------------------+-----------------------+---------+-------------------+-------+--------------------------------+

Briefly describe the execution plan. First, mysql is based on idx_last_upd_date index scan cm_ 379 records were obtained from the log table; Then, 63727 records are scanned by looking up the table, which is divided into two parts. derived represents the construction table, that is, the non-existent table, which can be simply understood as the result set formed by a statement, and the following number represents the ID of the statement. derived2 indicates that the query with ID = 2 constructed a virtual table and returned 63727 records. Let's take a look at what the statement with ID = 2 has done and returned such a large amount of data. First, scan the whole table for 13317 records in the employee table, and then use the index emp_certificate_empid Association EMP_ In the certificate table, rows = 1 indicates that only one record is locked for each association, which is more efficient. After obtaining, and cm_ 379 records of log are associated according to rules. From the execution process, we can see that too much data is returned, and most of the returned data is cm_log can't be used because cm_log only locks 379 records.

How to optimize it? You can see that we still need to communicate with cm after running_ Log as a join, so can we talk to cm before_ What about joining? Careful analysis of the sentence is not difficult to find that its basic idea is if cm_ Ref of log_ If table is EmpCertificate, it is associated with emp_certificate table, if ref_table is the employee table associated with employee. We can completely split it into two parts and connect it with union. Note that union is used here instead of union all because the primitive sentence has "distinct" to obtain the unique record, and union just has this function. If there is no distinct in the primitive sentence and there is no need for de duplication, we can directly use union all, because the de duplication action required to use union will affect the SQL performance.

The optimized statements are as follows:

select
   emp.id 
from
   cm_log cl 
inner join
   employee emp 
      on cl.ref_table = 'Employee' 
      and cl.ref_oid = emp.id  
where
   cl.last_upd_date >='2013-11-07 15:03:00' 
   and cl.last_upd_date<='2013-11-08 16:00:00' 
   and emp.is_deleted = 0  
union
select
   emp.id 
from
   cm_log cl 
inner join
   emp_certificate ec 
      on cl.ref_table = 'EmpCertificate' 
      and cl.ref_oid = ec.id  
inner join
   employee emp 
      on emp.id = ec.emp_id  
where
   cl.last_upd_date >='2013-11-07 15:03:00' 
   and cl.last_upd_date<='2013-11-08 16:00:00' 
   and emp.is_deleted = 0

4. There is no need to understand the business scenario, but the modified statement and the statement before the transformation need to keep the result consistent

5. The existing index can meet the requirements, and there is no need to build an index

6. Experiment with the modified sentence. It only takes 10ms, which is reduced by nearly 200 times!

+----+--------------+------------+--------+---------------------------------+-------------------+---------+-----------------------+------+-------------+
| id | select_type  | table      | type   | possible_keys                   | key               | key_len | ref                   | rows | Extra       |
+----+--------------+------------+--------+---------------------------------+-------------------+---------+-----------------------+------+-------------+
|  1 | PRIMARY      | cl         | range  | cm_log_cls_id,idx_last_upd_date | idx_last_upd_date | 8       | NULL                  |  379 | Using where |
|  1 | PRIMARY      | emp        | eq_ref | PRIMARY                         | PRIMARY           | 4       | meituanorg.cl.ref_oid |    1 | Using where |
|  2 | UNION        | cl         | range  | cm_log_cls_id,idx_last_upd_date | idx_last_upd_date | 8       | NULL                  |  379 | Using where |
|  2 | UNION        | ec         | eq_ref | PRIMARY,emp_certificate_empid   | PRIMARY           | 4       | meituanorg.cl.ref_oid |    1 |             |
|  2 | UNION        | emp        | eq_ref | PRIMARY                         | PRIMARY           | 4       | meituanorg.ec.emp_id  |    1 | Using where |
| NULL | UNION RESULT | <union1,2> | ALL    | NULL                            | NULL              | NULL    | NULL                  | NULL |             |
+----+--------------+------------+--------+---------------------------------+-------------------+---------+-----------------------+------+-------------+
53 rows in set (0.01 sec)

Clear application scenarios

The purpose of this example is to subvert our cognition of column discrimination. Generally, we believe that the higher the discrimination of columns, the easier it is to lock fewer records. However, in some special cases, this theory has limitations.

select
   * 
from
   stage_poi sp 
where
   sp.accurate_result=1 
   and (
      sp.sync_status=0 
      or sp.sync_status=2 
      or sp.sync_status=4
   );

0. Let's see how long it takes. 951 pieces of data take 6.22 seconds. It's really slow.

951 rows in set (6.22 sec)

1. explain first, the number of rows reaches 3.61 million, and type = ALL indicates full table scanning.

+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows    | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
|  1 | SIMPLE      | sp    | ALL  | NULL          | NULL | NULL    | NULL | 3613155 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+

2. All fields should be queried to return the number of records, because it is a single table query 0, and 951 records have been made.

3. Let explain's rows approach 951 as close as possible.

Take a look at exact_ Number of records with result = 1:

select count(*),accurate_result from stage_poi  group by accurate_result;
+----------+-----------------+
| count(*) | accurate_result |
+----------+-----------------+
|     1023 |              -1 |
|  2114655 |               0 |
|   972815 |               1 |
+----------+-----------------+

We see accurate_ The discrimination of the field result is very low. The whole table has only three values - 1,0,1. In addition, the index cannot lock a small amount of data.

Take another look at sync_status field:

select count(*),sync_status from stage_poi  group by sync_status;
+----------+-------------+
| count(*) | sync_status |
+----------+-------------+
|     3080 |           0 |
|  3085413 |           3 |
+----------+-------------+

Similarly, the degree of discrimination is also very low, and according to theory, it is not suitable for indexing.

After analyzing the problem, it seems that this table cannot be optimized. The discrimination between the two columns is very low. Even if the index is added, it can only adapt to this situation. It is difficult to do universal optimization, such as sync_status 0 and 3 are evenly distributed, so the locked records are also at the level of millions.

4. Find the business party to communicate and see the usage scenario. This is how the business party uses the SQL statement. It will scan the qualified data every five minutes, and sync the data after processing_ The status field becomes 1. The number of qualified records in five minutes will not be too many, about 1000. After understanding the usage scenarios of the business side, it becomes simple to optimize this SQL, because the business side ensures the imbalance of data. If you add an index, you can filter out most of the unnecessary data.

5. According to the indexing rules, use the following statements to establish the index

alter table stage_poi add index idx_acc_status(accurate_result,sync_status);

6. Observe the expected results and find that it only takes 200ms, which is more than 30 times faster.

952 rows in set (0.20 sec)

Let's review the process of analyzing the problem. Single table query is relatively easy to optimize. Most of the time, we only need to index the fields in the where condition according to the rules. If it's just this kind of "mindless" optimization, it's obvious that some columns with very low discrimination and columns that should not be indexed will also be indexed, which will affect the insertion Update performance has a serious impact, and it may also affect other query statements. Therefore, the use scenario of SQL in step 4 is very critical. Only when we know this business scenario can we better assist us in better analyzing and optimizing query statements.

Statement that cannot be optimized

select
   c.id,
   c.name,
   c.position,
   c.sex,
   c.phone,
   c.office_phone,
   c.feature_info,
   c.birthday,
   c.creator_id,
   c.is_keyperson,
   c.giveup_reason,
   c.status,
   c.data_source,
   from_unixtime(c.created_time) as created_time,
   from_unixtime(c.last_modified) as last_modified,
   c.last_modified_user_id  
from
   contact c  
inner join
   contact_branch cb 
      on  c.id = cb.contact_id  
inner join
   branch_user bu 
      on  cb.branch_id = bu.branch_id 
      and bu.status in (
         1,
      2)  
   inner join
      org_emp_info oei 
         on  oei.data_id = bu.user_id 
         and oei.node_left >= 2875 
         and oei.node_right <= 10802 
         and oei.org_category = - 1  
   order by
      c.created_time desc  limit 0 ,
      10;

Still a few steps.

0. Let's see how long the statement runs. It took 13 seconds for 10 records, which is unbearable.

10 rows in set (13.06 sec)

1.explain

+----+-------------+-------+--------+-------------------------------------+-------------------------+---------+--------------------------+------+----------------------------------------------+
| id | select_type | table | type   | possible_keys                       | key                     | key_len | ref                      | rows | Extra                                        |
+----+-------------+-------+--------+-------------------------------------+-------------------------+---------+--------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | oei   | ref    | idx_category_left_right,idx_data_id | idx_category_left_right | 5       | const                    | 8849 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | bu    | ref    | PRIMARY,idx_userid_status           | idx_userid_status       | 4       | meituancrm.oei.data_id   |   76 | Using where; Using index                     |
|  1 | SIMPLE      | cb    | ref    | idx_branch_id,idx_contact_branch_id | idx_branch_id           | 4       | meituancrm.bu.branch_id  |    1 |                                              |
|  1 | SIMPLE      | c     | eq_ref | PRIMARY                             | PRIMARY                 | 108     | meituancrm.cb.contact_id |    1 |                                              |
+----+-------------+-------+--------+-------------------------------------+-------------------------+---------+--------------------------+------+----------------------------------------------+

From the perspective of execution plan, mysql first checks org_ emp_ Scan 8849 records in info table, and then index idx_ userid_ Branch status association_ User table and index idx_branch_id associated contact_branch table, and the last primary key is associated with the contact table.

rows returns very little, and you can't see any exceptions. When we look at the statement, we find that there is an order by + limit combination behind it. Is it because of too much sorting? So we simplify the SQL, remove the following order by and limit, and see how many records are used to sort.

select
  count(*)
from
   contact c  
inner join
   contact_branch cb 
      on  c.id = cb.contact_id  
inner join
   branch_user bu 
      on  cb.branch_id = bu.branch_id 
      and bu.status in (
         1,
      2)  
   inner join
      org_emp_info oei 
         on  oei.data_id = bu.user_id 
         and oei.node_left >= 2875 
         and oei.node_right <= 10802 
         and oei.org_category = - 1  
+----------+
| count(*) |
+----------+
|   778878 |
+----------+
1 row in set (5.19 sec)

It is found that 778878 records are locked before sorting. If sorting for 700000 result sets will be disastrous. No wonder it is so slow. Can we change our thinking and create according to the contact first_ Time sorting, will it be faster to join again?

So it can be transformed into the following sentence, or straight_join to optimize:

select
   c.id,
   c.name,
   c.position,
   c.sex,
   c.phone,
   c.office_phone,
   c.feature_info,
   c.birthday,
   c.creator_id,
   c.is_keyperson,
   c.giveup_reason,
   c.status,
   c.data_source,
   from_unixtime(c.created_time) as created_time,
   from_unixtime(c.last_modified) as last_modified,
   c.last_modified_user_id   
from
   contact c  
where
   exists (
      select
         1 
      from
         contact_branch cb  
      inner join
         branch_user bu        
            on  cb.branch_id = bu.branch_id        
            and bu.status in (
               1,
            2)      
         inner join
            org_emp_info oei           
               on  oei.data_id = bu.user_id           
               and oei.node_left >= 2875           
               and oei.node_right <= 10802           
               and oei.org_category = - 1      
         where
            c.id = cb.contact_id    
      )    
   order by
      c.created_time desc  limit 0 ,
      10;

Verify that the effect is expected to increase by more than 13000 times in 1ms!

10 rows in set (0.00 sec)

I thought it was done, but we missed a detail in the previous analysis. In theory, the cost of sorting before joining is the same as that of joining before sorting. Why is it increased so much because there is a limit! The general execution process is as follows: mysql first gets the top 10 records by index, and then goes to join filter. When it finds that there are less than 10 records, go to 10 records again and join again. Obviously, when there are a lot of data filtered by the inner join, it will be a disaster. In extreme cases, one piece of data in the inner layer cannot be found, and mysql is foolish to take 10 records at a time, Almost traversed this data table!

Under SQL test with different parameters:

select
   sql_no_cache   c.id,
   c.name,
   c.position,
   c.sex,
   c.phone,
   c.office_phone,
   c.feature_info,
   c.birthday,
   c.creator_id,
   c.is_keyperson,
   c.giveup_reason,
   c.status,
   c.data_source,
   from_unixtime(c.created_time) as created_time,
   from_unixtime(c.last_modified) as last_modified,
   c.last_modified_user_id    
from
   contact c   
where
   exists (
      select
         1        
      from
         contact_branch cb         
      inner join
         branch_user bu                     
            on  cb.branch_id = bu.branch_id                     
            and bu.status in (
               1,
            2)                
         inner join
            org_emp_info oei                           
               on  oei.data_id = bu.user_id                           
               and oei.node_left >= 2875                           
               and oei.node_right <= 2875                           
               and oei.org_category = - 1                
         where
            c.id = cb.contact_id           
      )        
   order by
      c.created_time desc  limit 0 ,
      10;
Empty set (2 min 18.99 sec)

2 min 18.99 sec! Much worse than before. Due to the nested loop mechanism of mysql, it is basically impossible to optimize in this case. This statement can only be handed over to the application system to optimize its own logic.

Through this example, we can see that not all statements can be optimized, but often when we optimize, some extreme cases fall during the regression of SQL use cases, which will cause more serious consequences than before. Therefore, first: don't expect all statements to be optimized through SQL. Second: don't be too confident and optimize only for specific cases, ignoring more complex situations.

That's all for the analysis of slow query cases. The above are only some typical cases. In the optimization process, we have encountered more than 1000 rows of "garbage SQL" involving 16 table join s, online and offline database differences that lead to the application being directly dragged to death by slow queries, varchar equivalence comparison without single quotation marks, and Cartesian product queries that directly kill the slave database. No matter how many cases are, they are only the accumulation of experience. If we are familiar with the internal principles of query optimizer and index, it will be particularly simple to analyze these cases.

Words written later

This paper introduces the MySQL index principle and some methodologies to optimize slow query based on a case of slow query; The typical cases encountered are analyzed in detail. In fact, after doing sentence optimization for such a long time, I found that any database level optimization is not equal to the optimization of application system. MySQL can be used to support Google/FaceBook/Taobao applications, but it may not even support your personal website. Apply the recent popular words: "query is easy, optimization is not easy, and write and cherish!"

reference:

1. High performance MySQL 2 Data structure and algorithm analysis

Keywords: Database MySQL SQL

Added by marms on Mon, 21 Feb 2022 06:53:35 +0200