1. 程式人生 > >索引列上計算引起的索引失效及優化措施以及注意事項

索引列上計算引起的索引失效及優化措施以及注意事項

兩個示例

例子一

表結構

DROP TABLE IF EXISTS `account`;
CREATE TABLE IF NOT EXISTS `account` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `account` int(10) unsigned NOT NULL,
  `password` char(32) NOT NULL,
  `ip` char(15) NOT NULL,
  `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `time` (`time`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

比如要統計2012年08月15日註冊的會員數:

SELECT count(id) FROM account WHERE DATEDIFF("2012-08-15",time)=0

 例子二

表結構

DROP TABLE IF EXISTS `active`;
CREATE TABLE IF NOT EXISTS `user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `userid` int(10) unsigned NOT NULL,
  `lastactive` int(10) unsigned NOT NULL,
  PRIMARY KEY (`id`),
  KEY `lastactive` (`lastactive`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

統計最近3分鐘的活躍使用者

SELECT count(id) FROM user WHERE unix_timstamp()-lastactive < 180


以上兩個例子中,雖然都建有索引,但是SQL執行中卻不走索引,而採用全表掃描。

原因揭密

SQL語句where中如果有functionName(colname)或者某些運算,則MYSQL無法使用基於colName的索引。使用索引需要直接查詢某個欄位。

索引失效的原因是索引是針對原值建的二叉樹,將列值計算後,原來的二叉樹就用不上了;

為了解決索引列上計算引起的索引失效問題,將計算放到索引列外的表示式上。

解決辦法

例子一:SELECT count(id) FROM account WHERE time between "2012-08-15 00:00:00" and "2012-08-15 23:59:59"

例子二:SELECT count(id) FROM user WHERE lastactive > unix_timstamp() - 180

相關內容

1、如果對時間欄位進行查詢,可以將時間設定為int unsigned型別,存取UNIX時間戳。因為整型比較速度快
2、當我們執行查詢的時候,MySQL只能使用一個索引。
3、MySQL只有對以下操作符才使用索引: < , <= , = , > , >= , BETWEEN , IN ,以及某些時候的LIKE。可以在LIKE操作中使用索引的情形是指另一個運算元不是以萬用字元( % 或者 _ )開頭的情形。例如, “SELECT peopleid FROM people WHERE firstname LIKE 'Mich%';” 這個查詢將使用索引,但 “SELECT peopleid FROM people WHERE firstname LIKE '%ike';” 這個查詢不會使用索引。

不得不說

建立索引、優化查詢以便達到更好的查詢優化效果。但實際上,MySQL有時並不按我們設計的那樣執行查詢。MySQL是根據統計資訊來生成執行計劃的,這就涉及索引及索引的刷選率,表資料量,還有一些額外的因素。

Each table index is queried, and the best index is used unless the optimizer believes that it is more efficient to use a table scan. At one time, a scan was used based on whether the best index spanned more than 30% of the table, but a fixed percentage no longer determines the choice between using an index or a scan. The optimizer now is more complex and bases its estimate on additional factors such as table size, number of rows, and I/O block size.

簡而言之,當MYSQL認為符合條件的記錄在30%以上,它就不會再使用索引,因為mysql認為走索引的代價比不用索引代價大,所以優化器選擇了自己認為代價最小的方式。事實也的確如此

例項檢測

表結構

DROP TABLE IF EXISTS `active`;
CREATE TABLE IF NOT EXISTS `active` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `userid` int(10) unsigned NOT NULL,
  `lastactive` int(10) unsigned NOT NULL,
  PRIMARY KEY (`id`),
  KEY `lastactive` (`lastactive`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

插入資料

insert into active values
(null,10000, unix_timestamp("2012-08-20 15:10:02")),
(null,10001, unix_timestamp("2012-08-20 15:10:02")),
(null,10002, unix_timestamp("2012-08-20 15:10:03")),
(null,10003, unix_timestamp("2012-08-20 15:10:03")),
(null,10004, unix_timestamp("2012-08-20 15:10:03")),
(null,10005, unix_timestamp("2012-08-20 15:10:04")),
(null,10006, unix_timestamp("2012-08-20 15:10:04")),
(null,10007, unix_timestamp("2012-08-20 15:10:05")),
(null,10008, unix_timestamp("2012-08-20 15:10:06"))

explain select * from active where lastactive > unix_timestamp()-3;

上面這句索引起作用。


但是我在測試中,因為插入的日期與我測試的當前日期相差不少時間。所以我改寫為以下內容:

explain select * from active where lastactive > unix_timestamp("2012-08-20 15:10:06") - 3;

但是資料顯示,TYPE為ALL,key為NULL。也就是說索引不起作用。

我在改寫以下語句測試:

explain select * from active where lastactive > unix_timestamp("2012-08-20 15:10:06");

上面這個語句,索引又起作用了。

一個疑惑

正好手頭上有一個12016條記錄的資料,證實一下“當MYSQL認為符合條件的記錄在30%以上,它就不會再使用索引”的結論。經過測試,在總記錄12016條記錄的表中,查詢小於1854條記錄時走索引,大於該記錄時不走索引。符合條件的記錄在15.4%。這....,30%的資料可能有待確認,正如上面說的那樣,MySQL的優化器是考慮多方面因素,並選擇自己認為代價最小的方式。

mysql自己判斷是否使用索引,如果你自己確信使用索引可以提高效率,你也可以強行實用索引force index(index_name)