9

I am reading High performance MySQL and I am a little confused about deferred join.

The book says that the following operation cannot be optimized by index(sex, rating) because the high offset requires them to spend most of their time scanning a lot of data that they will then throw away.

mysql> SELECT <cols> FROM profiles WHERE sex='M' ORDER BY rating LIMIT 100000, 10;

While a deferred join helps minimize the amount of work MySQL must do gathering data that it will only throw away.

  SELECT <cols> FROM profiles INNER JOIN (
  SELECT <primary key cols> FROM profiles
  WHERE x.sex='M' ORDER BY rating LIMIT 100000, 10
  ) AS x USING(<primary key cols>);

Why a deferred join will minimize the amount of gathered data.

user1659464
  • 313
  • 1
  • 3
  • 9
  • So people can really ask good questions still? – Hanky Panky Jul 22 '15 at 06:14
  • @Hanky웃Panky Yes, it seems so. First one I've seen in a while. – Vatev Jul 22 '15 at 06:28
  • 1
    This approach will avoid fetching the data for columns other than `sex`,`rating` and the primary key ones for 100k rows, and only fetch them for the 10 rows that are returned. – Vatev Jul 22 '15 at 06:42

2 Answers2

1

The example you presented assumes that InnoDB is used. Let's say that the PRIMARY KEY is just id.

INDEX(sex, rating)

is a "secondary key". Every secondary key (in InnoDB) includes the PK implicitly, so it is really an ordered list of (sex, rating, id) values. To get to the "data" (<cols>), it uses id to drill down the PK BTree (which contains the data, too) to find the record.

Fast Case: Hence,

SELECT id FROM profiles
    WHERE x.sex='M' ORDER BY rating LIMIT 100000, 10

will do a "range scan" of 100010 'rows' in the index. This will be quite efficient for I/O, since all the information is consecutive, and nothing is wasted. (No, it is not smart enough to jump over 100000 rows; that would be quite messy, especially when you factor in the transaction_isolation_mode.) Those 100010 rows probably fit in about 1000 blocks of the index. Then it gets the 10 values of id.

With those 10 ids, it can do 10 joins ("NLJ" = "Nested Loop Join"). It is rather likely that the 10 rows are scattered around the table, possibly requiring 10 hits to the disk.

Let's "count the disk hits" (ignoring non-leaf nodes in the BTrees, which are likely to be cached anyway): 1000 + 10 = 1010. On ordinary disks, this might take 10 seconds.

Slow Case: Now let's look at the original query (SELECT <cols> FROM profiles WHERE sex='M' ORDER BY rating LIMIT 100000, 10;). Let's continue to assume INDEX(sex, rating) plus the implicit id on the end.

As before, it will index scan through the 100010 rows (est. 1000 disk hits). But as it goes, it is too dumb to do what was done above. It will reach over into the data to get the <cols>. This often (depending on caching) requires a random disk hit. This could be upwards of 100010 disk hits (if the table is huge and caching is not very useful).

Again, 100000 are tossed and 10 are delivered. Total 'cost': 100010 disk hits (worst case), which might take 17 minutes.

Keep in mind that there are 3 editions of High performance MySQL; they were written over the past 13 or so years. You are probably using a much newer version of MySQL than they covered. I do not happen to know if the optimizer has gotten any smarter in this area. These, if available to you, may give clues:

EXPLAIN FORMAT=JSON SELECT ...;
OPTIMIZER TRACE...

My favorite "Handler" trick for studying how things work may be helpful:

FLUSH STATUS;
SELECT ...
SHOW SESSION STATUS LIKE 'Handler%'.

You are likely to see numbers like 100000 and 10, or small multiples of such. But, keep in mind that a fast range scan of the index counts as 1 per row, and so does a slow random disk hit for a big set of <cols>.

Overview: To make this technique work, the subquery need a "covering" index, with the columns correctly ordered.

"Covering" means that (sex, rating, id) contains all the columns touched. (We are assuming that <cols> contains other columns, perhaps bulky ones that won't work in an INDEX.)

"Correct" ordering of the columns: The columns are in just the right order to get all the way through the query. (See also my cookbook.)

  • First come any WHERE columns compared with = to constants. (sex)
  • Then comes the entire ORDER BY, in order. (rating)
  • Finally it is 'covering'. (id)
Rick James
  • 135,179
  • 13
  • 127
  • 222
  • what do you mean by "what was done above"? and I do think the original query don't need to access ... i don't know why need to access to columns.. – inherithandle May 29 '20 at 10:47
  • @inherithandle - Simply removing the sentence with "done above" may make it more readable. I did some further research, but could not find a way to "prove" whether the extra 100000 sets of `` was being lugged around unnecessarily. (`Handler_read*` did not help, but did show diffs between MariaDB and Oracle.). – Rick James May 30 '20 at 00:47
  • @RickJames what do you mean "especially when you factor in the transaction_isolation_mode.", will transaction_isolation_mode use more memory or run slower because of mvcc? – spike 王建 Jan 07 '21 at 06:18
0

From the description below from official (https://dev.mysql.com/doc/refman/5.7/en/limit-optimization.html):

If you combine LIMIT row_count with ORDER BY, MySQL stops sorting as soon as it has found the first row_count rows of the sorted result, rather than sorting the entire result. If ordering is done by using an index, this is very fast. If a filesort must be done, all rows that match the query without the LIMIT clause are selected, and most or all of them are sorted, before the first row_count are found. After the initial rows have been found, MySQL does not sort any remainder of the result set.

We can see that they should have no difference.

But the percona suggest this, and give test data. But give no reason, I think there maybe exist some "bug" in mysql when deal with this kind of case. So we just regard this as a useful experience.

haibo cu
  • 144
  • 1
  • 6
  • The quote there is quite sloppy. "all rows ... are found" refers to the use of a "priority sort". This technique _looks_ at all the rows, does _only_ enough of the sorting to get `LIMIT` rows. – Rick James May 30 '20 at 01:05