談談MySQL中的降序索引 order by id DESC / ASC


今天這篇主要講order by 語句中的多個字段asc desc的問題。mysql5中,索引存儲的排序方式是ASC的,沒有DESC的索引。現在能夠理解為啥order by 默認是按照ASC來排序的了吧?雖然索引是ASC的,但是也可以反向進行檢索,就相當於DESC了。如果您在ORDER BY 語句中使用了 DESC排序,mysql確實會反向進行檢索。在理論上,反向檢索與正向檢索的速度一樣的快。但是在某些操作系統上面,並不支持反向的read-ahead預讀,所以反向檢索會略慢。由於設計的原因,在myisam引擎中,反向的檢索速度比正向檢索要慢得多。如果ORDER BY 子句中同時出現ASC和DESC,會是怎樣的情況呢?

  OEDER BY price ASC, date DESC LIMIT 0,10;

  而且在 (price,date)上有一個組合索引。

  explain之后可以發現,雖然用到了這個索引,但是仍然會用到filesort,說明只是使用到了索引中price的ASC排序。

  看一個實際的例子吧:

  discuz 7.2 gbk版,主題列表:cdb_threads。

  mysql> SHOW CREATE TABLE cdb_threads;

  | cdb_threads | CREATE TABLE `cdb_threads` (

  `tid` mediumint(8) UNSIGNED NOT NULL AUTO_INCREMENT,

  `fid` smallint(6) UNSIGNED NOT NULL DEFAULT '0',

  `iconid` smallint(6) UNSIGNED NOT NULL DEFAULT '0',

  `typeid` smallint(6) UNSIGNED NOT NULL DEFAULT '0',

  `sortid` smallint(6) UNSIGNED NOT NULL DEFAULT '0',

  `readperm` tinyint(3) UNSIGNED NOT NULL DEFAULT '0',

  `price` smallint(6) NOT NULL DEFAULT '0',

  `author` char(15) NOT NULL,

  `authorid` mediumint(8) UNSIGNED NOT NULL DEFAULT '0',

  `subject` char(80) NOT NULL,

  `dateline` int(10) UNSIGNED NOT NULL DEFAULT '0',

  `lastpost` int(10) UNSIGNED NOT NULL DEFAULT '0',

  `lastposter` char(15) NOT NULL,

  `views` int(10) UNSIGNED NOT NULL DEFAULT '0',

  `replies` mediumint(8) UNSIGNED NOT NULL DEFAULT '0',

  `displayorder` tinyint(1) NOT NULL DEFAULT '0',

  `highlight` tinyint(1) NOT NULL DEFAULT '0',

  `digest` tinyint(1) NOT NULL DEFAULT '0',

  `rate` tinyint(1) NOT NULL DEFAULT '0',

  `special` tinyint(1) NOT NULL DEFAULT '0',

  `attachment` tinyint(1) NOT NULL DEFAULT '0',

  `moderated` tinyint(1) NOT NULL DEFAULT '0',

  `closed` mediumint(8) UNSIGNED NOT NULL DEFAULT '0',

  `itemid` mediumint(8) UNSIGNED NOT NULL DEFAULT '0',

  `supe_pushstatus` tinyint(1) NOT NULL DEFAULT '0',

  `sgid` mediumint(8) UNSIGNED NOT NULL DEFAULT '0',

  `recommends` smallint(6) NOT NULL,

  `recommend_add` smallint(6) NOT NULL,

  `recommend_sub` smallint(6) NOT NULL,

  `heats` int(10) UNSIGNED NOT NULL DEFAULT '0',

  `status` smallint(6) UNSIGNED NOT NULL DEFAULT '0',

  PRIMARY KEY (`tid`),

  KEY `digest` (`digest`),

  KEY `displayorder` (`fid`,`displayorder`,`lastpost`),

  KEY `typeid` (`fid`,`typeid`,`displayorder`,`lastpost`),

  KEY `sgid` (`fid`,`sgid`),

  KEY `sortid` (`sortid`),

  KEY `recommends` (`recommends`),

  KEY `heats` (`heats`),

  KEY `authorid` (`authorid`)

  ) ENGINE=InnoDB AUTO_INCREMENT=330109 DEFAULT CHARSET=gbk |

  fid開頭的組合索引有三個:

  KEY `displayorder` (`fid`,`displayorder`,`lastpost`),

  KEY `typeid` (`fid`,`typeid`,`displayorder`,`lastpost`),

  KEY `sgid` (`fid`,`sgid`),

  我們用fid和displayorder字段來做排序。

  先看order by fid ASC,displayorder ASC的情況:

  mysql> EXPLAIN SELECT * FROM cdb_threads WHERE fid IN(1,3,5) ORDER BY fid ASC,displayorder ASC;

  +----+-------------+-------------+-------+--------------------------+--------------+---------+------+-------+-------------+

  | id | select_type | TABLE | type | possible_keys | KEY | key_len | ref | rows | Extra |

  +----+-------------+-------------+-------+--------------------------+--------------+---------+------+-------+-------------+

  | 1 | SIMPLE | cdb_threads | range | displayorder,typeid,sgid | displayorder | 2 | NULL | 12728 | USING WHERE |

  +----+-------------+-------------+-------+--------------------------+--------------+---------+------+-------+-------------+

  1 row IN SET (0.00 sec)

  再看ORDER BY fid DESC, displayorder DESC的情況:

  mysql> EXPLAIN SELECT * FROM cdb_threads WHERE fid IN(1,3,5) ORDER BY fid DESC,displayorder DESC;

  +----+-------------+-------------+-------+--------------------------+--------------+---------+------+-------+-------------+

  | id | select_type | TABLE | type | possible_keys | KEY | key_len | ref | rows | Extra |

  +----+-------------+-------------+-------+--------------------------+--------------+---------+------+-------+-------------+

  | 1 | SIMPLE | cdb_threads | range | displayorder,typeid,sgid | displayorder | 2 | NULL | 12728 | USING WHERE |

  +----+-------------+-------------+-------+--------------------------+--------------+---------+------+-------+-------------+

  1 row IN SET (0.00 sec)

  這兩種情況下,使用到的KEY都是 KEY `displayorder` (`fid`,`displayorder`,`lastpost`), 沒有進行filesort,很完美。

  再來看一個DESC,另外一個ASC的情況:

  mysql> EXPLAIN SELECT * FROM cdb_threads WHERE fid IN(1,3,5) ORDER BY fid DESC,displayorder ASC;

  +----+-------------+-------------+-------+--------------------------+------+---------+------+------+-----------------------------+

  | id | select_type | TABLE | type | possible_keys | KEY | key_len | ref | rows | Extra |

  +----+-------------+-------------+-------+--------------------------+------+---------+------+------+-----------------------------+

  | 1 | SIMPLE | cdb_threads | range | displayorder,typeid,sgid | sgid | 2 | NULL | 6512 | USING WHERE; USING filesort |

  +----+-------------+-------------+-------+--------------------------+------+---------+------+------+-----------------------------+

  1 row IN SET (0.00 sec)

  mysql> EXPLAIN SELECT * FROM cdb_threads WHERE fid IN(1,3,5) ORDER BY fid ASC,displayorder DESC;

  +----+-------------+-------------+-------+--------------------------+------+---------+------+------+-----------------------------+

  | id | select_type | TABLE | type | possible_keys | KEY | key_len | ref | rows | Extra |

  +----+-------------+-------------+-------+--------------------------+------+---------+------+------+-----------------------------+

  | 1 | SIMPLE | cdb_threads | range | displayorder,typeid,sgid | sgid | 2 | NULL | 6512 | USING WHERE; USING filesort |

  +----+-------------+-------------+-------+--------------------------+------+---------+------+------+-----------------------------+

  1 row IN SET (0.00 sec)

  這兩次使用到的key是 KEY `sgid` (`fid`,`sgid`), 由於我們並沒有涉及到sgid,所以只用到了fid的索引。。。 至於displayorder字段怎樣排序,用的是filesort。肯定比直接使用索引要慢多了。

  如果可以搞一個fid ASC, displayorder DESC的組合索引,那就方便多了。事實上mysql不支持這么做啦。

  既然mysql不支持這種方式,那我們只好用其它方法解決這個問題。

  創建一個新的字段,叫做reverse_displayorder。 此字段中保存的值為 displayorder字段的值乘以-1。

  於是 order by fid ASC, displayorder DESC 就可以轉化成 order by fid ASC, reverse_displayorder ASC了。

  如果是mysql 5.0或之后的版本,只要創建一個觸發器(trigger)來自動更新reverse_displayorder的值就可以了,程序都不用大改。

  雖然discuz沒有這樣做,但是MediaWiki確實是這樣設計的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM