今天這篇主要講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確實是這樣設計的。mysql