簡單(使用整形存儲IP,整形確定比字符串類型操做更快)
用UNSINGED INT存儲IP地址佔用4字節,CHAR(15)則佔用15字節。另外,計算機處理整數類型比字符串類型快。使用INT UNSIGNED而不是CHAR(15)來存儲IPV4地址,經過MySQL函數inet_ntoa和inet_aton來進行轉化。IPv6地址目前沒有轉化函數,須要使用DECIMAL或兩個BIGINT來存儲。mysql
避免NULL;
可爲null的列會使用更多的存儲空間,也須要特殊的處理。當可爲null的列別索引的時候,每一個索引記錄須要一個額外的字節。在MYISAM引擎中,可能會致使固定大小的索引(如只有一個整數列的索引)變成可變大小的索引。
在數據庫優化中,將null改成not null,帶來的性能提高並非很大,可是若是該列上有索引,那麼須要避免設計爲null.
可是也有例外,InnoDB使用單獨的bit存儲Null值,因此對稀疏數據有很好的空間效率,不一樣於MYISAM。sql
範式與反範式
範式的優勢就是方便更新,只須要修改少許的信息。缺點就是須要關聯,代價很高,會使一些索引策略失效。
反範式將有些數據合併,提升查詢的效率,不用由於關聯查詢致使巨大的性能問題。
彙總表的設計思想:就是將一些統計,如count的數據彙總到一張表中,好比彙總每個月的統計信息,那麼若是須要計算季度的信息能夠將月的彙總信息加起來。
緩存表的使用場景正好相反,是爲了處理實時統計的表,將表中的部分列拿出來做爲緩存表,若是主表使用的是innoDB引擎,那麼用MYISAM做爲緩存表的引擎將會獲得更小的索引佔用空間,而且支持fulltext索引。myisam引擎使用前綴壓縮來減小索引的存儲空間,而inoDB不會。
很重要的要提到的是,當咱們在重建彙總表和緩存表的時候,爲了保證以前彙總表或者緩存表的可用性,須要創建新表new,而後將new表命名爲原來的表,將原來的表命名爲old表.這樣當新表數據有問題的時候,方便回滾。數據庫
建立好的索引對應用來講特別重要,由於隨機IO訪問特別慢,若是服務器從存儲中讀取一個數據塊,就爲了其中的一行數據那麼,那麼訪問會超級慢。使用索引引用訪問的行,那麼效率將會提高不少。若是數據存放在機械硬盤,那麼速度會更慢。
索引大大減小了服務器須要掃描的數據量,而且可以避免臨時表,索引可以將隨機IO變爲順序IO。MYSQL支持不少索引,可是最核心的是Btree索引。索引的本質就是數據結構。
我在網絡上看到過不少描述B-tree的博客,可是最終仍是以爲《mysql高性能》描述的比較靠譜和清楚。以下所示:
緩存
如上圖記錄的是mysql innodb引擎的聚簇索引,innodb的聚簇索引的葉子節點包含了行的所有數據,而節點頁只包括了索引列。聚簇索引表明了數據存儲的方式,那麼一張表只能有一個聚簇索引,其主鍵做爲聚簇索引的索引列。就是按照主鍵ID來彙集數據。聚簇索引最大限度的提升了IO密集型應用的性能,至於什麼是IO密集,和CPU密集,自行百度。可是其插入順序會被很大的限制,這就是爲何隨機的字符串做爲主鍵很差的緣由,一樣更新聚簇索引索引列,也就是主鍵的代價會特別高,由於每一個行都會所以而移動位置。當行的主鍵要求必須插入到某個已滿的頁中時,會致使頁分裂的問題。
另一個最重要的問題就是:innodb的二級索引,其葉子節點會存儲引用行的主鍵列,這就是覆蓋因此爲何那麼那麼重要的緣由。
Mysql索引廣泛使用的B+tree,如上圖所示,內部節點不存儲data,只存儲key,而將數據存儲在葉子節點上。咱們看到的葉子節點之間的指針是爲了方便順序查找。服務器
接下來咱們要區分MYISAM索引與innodb索引的區別:
MyISAM引擎使用B+Tree做爲索引結構,葉節點的data域存放的是數據記錄的地址。下圖是MyISAM索引的原理圖:網絡
這裏設表一共有三列,假設咱們以Col1爲主鍵,則圖8是一個MyISAM表的主索引(Primary key)示意。能夠看出MyISAM的索引文件僅僅保存數據記錄的地址。在MyISAM中,主索引和二級索引(Secondary key)在結構上沒有任何區別,只是主索引要求key是惟一的,而輔助索引的key能夠重複。
雖然InnoDB也使用B+Tree做爲索引結構,但具體實現方式卻與MyISAM大相徑庭。
第一個重大區別是InnoDB的數據文件自己就是索引文件。MyISAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。而在InnoDB中,表數據文件自己就是按B+Tree組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,所以InnoDB表數據文件自己就是主索引。數據結構
由於InnoDB的數據文件自己要按主鍵彙集,因此InnoDB要求表必須有主鍵(MyISAM能夠沒有),若是沒有顯式指定,則MySQL系統會自動選擇一個能夠惟一標識數據記錄的列做爲主鍵,若是不存在這種列,則MySQL自動爲InnoDB表生成一個隱含字段做爲主鍵,這個字段長度爲6個字節,類型爲長整形。
第二個與MyISAM索引的不一樣是InnoDB的二級索引data域存儲相應記錄主鍵的值而不是地址。換句話說,InnoDB的全部二級索引都引用主鍵做爲data域。併發
如何選擇合適的索引順序:
將選擇性比較的高的列放到索引的最前面。(經驗法則,可是並非任何狀況下都應該這樣),計算方式以下所示:
函數
應該將where中的列,設置爲索引,而且這個列要有很高的選擇性,好比sex這樣的列就不該該創建爲索引列。並非說,只須要將where中的列創建爲索引,覆蓋索引可以很好的說明這個問題,並且覆蓋索引特別重要,特別重要,特別重要(重要的事情說三遍)工具
覆蓋索引也是二級索引,因此在innodb引擎中二級索引保存了行的主鍵值,因此二級索引確定可以覆蓋主鍵。這個特性很是重要。在覆蓋索引中訪問數據對IO密集型的應用特別有幫助,由於索引更容易存放在內存中,尤爲是MYISAM引擎,採用了前綴壓縮的技術更加節省空間,可是壓縮以後,會影響查找的性能。Innodb不會壓縮。
覆蓋索引,有一個特別重要的使用技巧就是延遲關聯。以下:
在這裏沒法使用覆蓋索引,由於沒有一個索引可以覆蓋*,MYSQL不能再索引中使用Like」%%」,能夠執行like」..%」;採用延遲關聯的策略以下所示:
如何使用覆蓋索引進行排序,避免臨時表
只有當索引列的順序和order by的子句順序徹底一致,而且全部的列的排序方向一致,纔會使用覆蓋索引。若是有關聯,那麼order by中的列所有爲第一個表才能使用索引。
最重要的一點是:必須知足最左前綴的要求。若是在where或者Join,子句中指定了列的常量,也可以知足最左前綴
好比索引列爲(col1,col2,col3),在where中co1=」1」,order by col2,是知足最左前綴的。
MYISAM的前綴壓縮索引,沒法使用二分查找,只能從頭開始掃描。正序的速度還能夠,倒序就不好了。(由於其壓縮依賴前面的行)
咱們老是使mysql可以使用更多的索引列:
在這裏有一個技巧就是使用In,使用in使其可以知足最左前綴的要求,直到遇到第一個範圍查詢,好比索引(sex,age),那麼where sex in(1,0) and age
<20;爲何將age放到後面就是由於age多半是範圍查詢。可是In裏面的列表太大,會一樣帶來性能問題,因此該方法不能濫用。
這跟前面說的選擇性高的放到列首是衝突的,可是咱們必須認識到sex是常常用到的字段,放到索引裏面是理所固然的,那麼其餘範圍比較大的列,如age應該放到其後面。從而可以使用In技巧來知足最左前綴。
避免多個範圍查詢:
mysql只能使用一個範圍查詢,其後面的範圍查詢將沒法使用索引。
關於索引的使用,以前我在阿里雲數據庫社區找到的那個帖子,上面的三個問題都超級經典,我以爲能夠很好的說明問題。列舉以下:
最後一個問題,like」%%」模糊查詢是不會使用索引的,我以爲應該是做者寫錯了。
Q1: SELECT DISTINCT
LoginId,SubId FROM TB WHERE 1 ORDER BY Visit ASC LIMIT 8888, 10
排序方式有多種,Visit 、ID、Count等,是否是有一種排序就要建一個「覆蓋索引」? 還須要單列給Visit 、ID、Count、SubId、LoginId建索引嗎?
A1(俞月):這個SQL等價於: select LoginId, SubId from TB where 1 group by LoginId, SubId ORDER BY Visit ASC LIMIT 8888, 10
單純就這條SQL而言,ORDER BY Visit ASC 是不必的,由於select選出的字段中沒有Visit字段,建議添加組合索引(LoginId,SubId)。
假如SQL是 select LoginId,SubId,Visit from TB where 1 group by LoginId, SubId ORDER BY Visit ASC LIMIT 8888, 10
這裏能夠建議組合索引(LoginId,SubId) 或者(LoginId,SubId,Visit)
將Visit字段添加到索引中,仍舊避免不了排序,上面SQL的執行過程是:
tb
(id
int(11) DEFAULT NULL,LoginId
int(11) DEFAULT NULL,SubId
int(11) DEFAULT NULL,vist
int(11) DEFAULT NULL,idx1
(id
),login_subId
(LoginId
,SubId
),login_subId_vist
(LoginId
,SubId
,vist
)Q2(cyb): 關於分頁 limit 134557, 10優化,網上能找到的資料都是用WHERE id >或 id< 取一些範圍定位,可是不實用啊,例如評分1-5分排序分頁就不能用了
A2(俞月):普通limit M,N的翻頁寫法,每每在越日後翻頁的過程當中速度越慢,緣由 mysql會讀取表中的前M+N條數據,M越大,性能就越差:
select * from t where sellerid=100 limit 100000,20 優化寫法:
select t1.* from t t1, (select id from t sellerid=100 limit 100000,20) t2 where t1.id=t2.id;
優化後的翻頁寫法,先查詢翻頁中須要的N條數據的主鍵id,在根據主鍵id回表查詢所須要的N條數據,此過程當中查詢N條數據的主鍵ID在索引中完成。
這種優化的根本出發點,是減小在數據頁中的掃描量。覆蓋索引,也是一種優化思路,出發點就是直接從二級索引中直接獲取查詢結果。
--------------------------------------------------------------------------------------------------------------------------------------------------------
Q3(cyb): SELECT DISTINCT
LoginId, SubId FROM T_Query WHERE 1 ORDER BY fenshu DESC LIMIT 61630, 10
fenshu 這一列不是主鍵,相似於京東商品、按評價數、按銷量等不一樣方式排序。
該表是基於其它表創建的查詢表,總記錄大約20萬,這個是翻頁查詢SQL
A3(俞月):這條SQL從實現的功能而言,其實不必加ORDER BY fenshu DESC 的。
distinct只能返回它的目標字段,而沒法返回其它字段。 因此在SELECT DISTINCT LoginId, SubId FROM T_Query中取出的是 LoginId,SubId不重複的行。也就是說,必須LoginId和SubId都相同纔會被排除。
作個測試:
mysql> select * from tb;
+------+---------+-------+------+
| id | LoginId | SubId | vist |
+------+---------+-------+------+
| 1 | 123 | 21 | 78 |
| 2 | 43 | 71 | 78 |
| 3 | 43 | 21 | 78 |
| 2 | 43 | 71 | 78 |
| 3 | 43 | 21 | 78 |
| 2 | 43 | 71 | 78 |
| 5 | 73 | 21 | 78 |
| 2 | 55 | 67 | 78 |
| 1 | 98 | 21 | 78 |
+------+---------+-------+------+
9 rows in set (0.01 sec)
mysql> select distinct LoginId,SubId from tb ;
+---------+-------+
| LoginId | SubId |
+---------+-------+
| 43 | 21 |
| 43 | 71 |
| 55 | 67 |
| 73 | 21 |
| 98 | 21 |
| 123 | 21 |
+---------+-------+
6 rows in set (0.00 sec)
mysql> select distinct LoginId,SubId from tb where 1 order by vist;
+---------+-------+
| LoginId | SubId |
+---------+-------+
| 43 | 21 |
| 43 | 71 |
| 55 | 67 |
| 73 | 21 |
| 98 | 21 |
| 123 | 21 |
+---------+-------+
6 rows in set (0.00 sec)
這裏若是建了(LoginId,SubId)便可避免distinct的建立臨時表,避免排序。
mysql> explain select distinct LoginId,SubId from tb;
+----+-------------+-------+-------+------------------------------+-------------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+------------------------------+-------------+---------+------+------+-------------+
| 1 | SIMPLE | tb | index | login_subId,login_subId_vist | login_subId | 10 | NULL | 9 | Using index |
+----+-------------+-------+-------+------------------------------+-------------+---------+------+------+-------------+
1 row in set (0.00 sec)
因此,對於您給出的SQL,個人建議是改寫SQL爲:
SELECT DISTINCT
LoginId, SubId
FROM
T_Query
LIMIT 61630, 10
並添加索引( LoginId, SubId),這裏一方面能夠利用到索引避免臨時表、排序;另外一方面其實也是覆蓋索引。
若是您發現去掉ORDER BY fenshu DESC 不符合您的業務需求,那麼就須要考慮一下distinct的用法是否正確? select出來的結果集是不是您真實須要的。
另外須要提到一點:
您發給個人這張表,索引用法有點問題,建了不少沒必要要的索引。
假如建了(A),(A,B),(A,B,C)三個索引,其實(A),(A,B)都是不須要的。
--------------------------------------------------------------------------------------------------------------------------------------------------------
Q4(華夏一劍): mysql如何實現like '%民生%' 這樣的有效索引查詢。
一、我建立一個表: CREATE TABLE tb_news
( id
bigint(20) NOT NULL auto_increment, title
varchar(100) default NULL, content
mediumtext, keywords
varchar(50) default NULL, PRIMARY KEY (id
) ) ENGINE=InnoDB DEFAULT CHARSET=gb2312
二、同時建立索引: create index tb_news_title on tb_news(title);
三、有10萬條數據,實現按標題title的模糊查詢如: select id,title from news where title like '%民生%';
四、我知道: select id,title from news where title like '民生%'; 索引是有效的。但是select id,title from news where title like '%民生%'; 索引就無效了。
有什麼好的辦法解決標題title的模糊查詢。
A4(俞月):在您給的例子中,select id, title from tb_news where title like '%mal%'; 是能夠走上索引的,而且是覆蓋索引。
innodb表的二級索引上存儲了主鍵值,上面的SQL語句只須要查詢id(主鍵字段)和title,因此掃描二級索引字段就能夠獲取到結果,不要再返回主鍵索引讀取數據了。
mysql> explain select id, title from tb_news where title like '%mal%';
+----+-------------+---------+-------+---------------+---------------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+-------+---------------+---------------+---------+------+------+--------------------------+
| 1 | SIMPLE | tb_news | index | NULL | tb_news_title | 203 | NULL | 5 | Using where; Using index |
+----+-------------+---------+-------+---------------+---------------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
而相似這種,select * from tb_news where title like '%mal%';會走全表掃描。
mysql> explain select * from tb_news where title like '%mal%';
+----+-------------+---------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | tb_news | ALL | NULL | NULL | NULL | NULL | 5 | Using where |
+----+-------------+---------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
經過覆蓋索引能夠得到性能上的必定優化,可是在數據量特別大,請求頻繁的業務場景下不要在數據庫進行模糊查詢;
非得使用數據庫的話 ,建議不要在生產庫進行查詢,能夠在只讀節點進行查詢,避免查詢形成主業務數據庫的資源消耗完,致使故障;
可使用MySQL自帶的全文檢索,或者一些開源的搜索引擎技術,好比sphinx.
Mysql是如何進行查詢的,見下圖:
致使一個查詢可能跟咱們認爲那樣不同的主要緣由就是,mysql的查詢優化器很複雜。Mysql服務器和客戶端以前的通訊採用半雙工的協議,意思是比如兩我的傳球,球在某個時間點,只可能被一我的控制。一端發消息後,另外一端徹底接受到消息才能響應。
查詢緩存必需要要求查詢和緩存中的查詢每一個字節都同樣,這跟hibernate二級緩存的sql緩存是同樣的。
查詢優化器都能作什麼事情呢?
查詢優化器是根據要執行成本,來選擇更好的查詢計劃。可是其依靠的統計信息可能不全或者不許確,有可能獲得不是最優的效果,觸發函數,存儲過程,就沒法考慮。
優化器能夠定義表關聯的順序,MYSQL查詢使用循環嵌套的方式來執行查詢,那麼先執行大表的查找,再關聯小表,循環的次數會更少。
能夠將外join,轉化爲內部join.
優化count(),Min(),MAX();索引很能幫助來進行這類統計,好比尋找最小值,只須要找到最左端的值。特別重要的是,在沒有where條件的狀況下,count(「*」)直接從存儲引擎獲取,myISAM將其做爲一個變量存起來。
對in列表優化,會先對in中的列表進行排序,而後使用二分法來驗證是否知足某行是否知足該in條件。
存儲引擎給查詢執行引擎提供了基本的底層接口,存儲引擎共有的特性則由服務器層實現,好比時間,日期,函數,視圖,觸發器等。
使用explaing工具很是重要,其結果中的type表示聯結表的類型,全表掃描,索引掃描,範圍掃描,惟一索引掃描,常數引用,查詢的速度依次變的更快。
可是不少優化的策略並非絕對的,若是那個帖子上寫的很絕對,那他通常都在瞎扯,我以爲須要用explain進行分析。 一樣將某個很複雜的查詢進行分解,將會減小鎖的競爭。讓應用層作更多的事,或者更擅長的事,也是一個很好的策略。將某些邏輯處理交給應用層處理也頗有幫助。