千萬級別的數據庫優化

前言

平時在寫一些小web系統時,咱們總會對mysql不覺得然。然而真正的系統易用應該講數據量展望拓展到千萬級別來考慮。所以,今天下午實在是無聊的慌,本身隨手搭建一個千萬級的數據庫,而後對數據庫進行一些簡單的CRUD來看看大數據狀況下的CRUD效率。html

結果發現,曾經簡單的操做,在數據量大的時候仍是會形成操做效率低下的。所以先寫下這篇文章,往後不斷更新紀錄一下本身工做學習到的Mysql優化技巧。mysql


搭建千萬級數據庫

首先,須要一個測試環境。一開始想到的是寫一個SImple JDBC程序然進行簡單的數據INSERT。結果發現單線程狀況下,每次INSERT了一百多萬條的時候效率就變得很是的低下。可是程序也沒報OUT MEMORY之類的異常。初步判斷應該是單一線程不斷的瘋狂建立PrepareStatement對象CG沒來得及清理形成內存逐漸被吃緊的緣由。web

後來改進了一下,用多線程的機制。建立十個進程,每一個負責一萬條數據的插入。這效率一會兒提高了好幾倍。然而好景不長,很快的Java程序報錯:OUT MEMORY。內存溢出了,CG沒來得及清理。sql

這可把我給急的了。插入的太快內存CPU吃緊,插入的太慢又失去了建立測試環境「快」的初衷。後來想了下,既然是要批量插入數據,那麼不是能夠簡單的寫一段數據庫存儲過程嗎?數據庫

因而,先創建一張測試表,就叫goods表吧。先寫sql語句建立表:swift

 
 
  1.  
    CREATE TABLE goods (
  2.  
    id serial,
  3.  
    NAME VARCHAR (10),
  4.  
    price DOUBLE
  5.  
    ENGINE MYISAM DEFAULT CHARACTER
  6.  
    SET utf8 COLLATE utf8_general_ci AUTO_INCREMENT ROW_FORMAT COMPACT;

接下來根據表結構寫一段存儲過程讓數據庫自行重複插入數據:緩存

 
 
  1.  
    begin
  2.  
    declare int default ;
  3.  
    dd:loop
  4.  
    insert into goods values
  5.  
    (null,'商品1',20),
  6.  
    (null,'商品2',18),
  7.  
    (null,'商品3',16),
  8.  
    (null,'商品4',4),
  9.  
    (null,'商品5',13),
  10.  
    (null,'商品6',1),
  11.  
    (null,'商品7',11),
  12.  
    (null,'商品8',12),
  13.  
    (null,'商品9',13),
  14.  
    (null,'商品0',12);
  15.  
    commit;
  16.  
    set i+10 ;
  17.  
    if 10000000 then leave dd;
  18.  
    end if;
  19.  
    end loop dd ;
  20.  
    end

寫完後運行一下,ok千萬級別的數據庫立刻就插入進去了。網絡


再談數據庫優化

既然有了數據如今開始進入數據庫優化環節。多線程

1、分頁查詢的優化

首先咱們經常涉及到的CRUD操做莫過於分頁操做。 對於普通的分頁操做咱們經常是這樣子函數

select * from goods limit 100,1000; 

這樣固然沒有任何的問題,可是當咱們的數據量很是大,假如我要查看的是第八百萬條數據呢?對應的sql語句爲:

select * from goods limit 8000000,1000; 

Mysql執行時間爲 1.5秒左右。那麼咱們能夠作一些什麼優化嗎?

上述的sql語句形成的效率低下緣由不外乎:

大的分頁偏移量會增長使用的數據,MySQL會將大量最終不會使用的數據加載到內存中。就算咱們假設大部分網站的用戶只訪問前幾頁數據,但少許的大的分頁偏移量的請求也會對整個系統形成危害

那麼我要怎樣來優化呢?若是咱們的id爲自增的。也就是說每一條記錄的id爲上一條id + 1那麼,分頁查找咱們可使用id進行範圍查找替代傳統的limit。

例如上述的sql語句能夠代替爲:

select * from goods where id > 8000000 limit 1000; 

上述sql的到一樣的執行結果,執行時間卻只有0.04秒。提高了40倍左右。

結論:對應於自增id的表,若是數據量很是大的分頁查找,能夠觀察id的分佈規律計算出其id的範圍經過範圍查找來實現分頁效果。

2、索引優化

談到數據庫效率,大部分人的第一想法應該就是創建索引。沒錯,正確的創建索引能夠很好的提高效率。

關於索引,這是一個很大的話題我就不打算在這篇文章歸納起來了。推薦一篇美團技術博客關於索引的文章。這篇文章很好的概述了索引的使用場景。主要要注意最左前綴匹配原則,而且將索引創建在區分度高的列。區分度的計算公式爲:

count(distinct col)/count(*) 

所以像個人模擬數據中即便創建了索引效率也提高不了多少,由於區分度很是的低。

總結一些索引會失效的狀況,咱們在實際的開發中應該儘可能避免:

  1. like查詢是以%開頭,不會使用索引
  2. WHERE條件中有or,即便其中有條件帶索引也不會使用
  3. !=,not in ,not exist不會使用索引
  4. WHERE字句的查詢條件裏使用了函數或計算
  5. 複合索引若是單獨使用,只有複合索引裏第一個字段有效

結論:索引很重要,也是一個大話題。推薦看看那篇美團技術博客的文章能夠學習到不少。有些看似簡單的函數操做若是放在SQL語句中卻會致使索引失效,嚴重的影響效率,所以推薦將一些操做放到客戶端中進行計算而不是SQL語句中。索引的使用狀況可使用EXPLAIN進行查看。

3、談談COUNT(*)

查詢一張表有多少條記錄經常使用語句爲:

SELECT COUNT(*) FROM `goods`; 

有些人認爲這裏使用了星號可能效率不如直接使用COUNT(COL)來的高,因此他們認爲對於goods表(存在邏輯主鍵)更高效的語句應該是這樣的:

SELECT COUNT(id) FROM `goods`; 

可是其實兩條執行的時間是同樣的。由於COUNT(*)默認走最短的索引。因爲id是這裏最短的索引因此COUNT(*)等價於COUNT(id)。

結論:若是表中的最短索引很長,並且須要COUNT(*)操做,不放添加一個冗餘的索引在一個比較短的列上,這樣能夠大大加大索引的速度。而且記住:COUNT(*)走的永遠是最優的。

4、varchar 不是越大越好

有人認爲varchar在的大小是按數據實際大小存儲的,因此爲了防止長度溢出就一開始就將長度定義的很長。可是事實是:

  • VARCHAR在硬盤佔用上確實是按實際大小佔用
  • 但若是涉及到臨時表,是按後面的數字分配內存的
  • 在VARCHAR列創建索引,ken_len也是按照後面數字分配的

結論:varchar按需取長,防止臨時表佔滿內存溢出至磁盤致使速度降低。

5、聯合查詢與單表查詢的選擇

Mysql有不少聯合查詢的方式,諸如left join、inner join等等。

可是這些聯合查詢其實效率是很低的,如今考慮兩張表一張爲job表 數據量大約10萬 + ,另一張是job的分析表數據量較少,想經過job表中的job_name查詢全部工做的工做分析狀況。其中在兩張表的列 job_name 均創建了索引。如今若是用聯合查詢:

 
 
  1.  
    SELECT FROM `job` LEFT JOIN `job_analysis` ON a.job_name b.job_name;
  2.  
    -- 運行時間: 0.93s
  3.  
    -- EXPLAIN 結果:
  4.  
    1 SIMPLE a ALL 169497
  5.  
    1 SIMPLE b ref job_name job_name 212 jobs.a.job_name 1

能夠看到使用left join的查詢只有一張表使用了索引,而另一張表卻要ALL去遍歷。這對數據不是很大的時候還好,對數據量上百萬 千萬簡直是噩夢。

誠然這種狀況下使用單表屢次效率並不能更高(至少一次ALL + 一次走索引)但數據量大仍是要選擇單表屢次可能更優,由於單表屢次查詢有利於後面對數據的分庫分表,且屢次查詢能夠支持部分的緩存操做以及分爲屢次減小數據庫鎖的競爭。

摘自《高性能MYSQL》

事實上,用分解關聯查詢的方式重構查詢有以下優點:

  1. 讓緩存的效率更高。許多應用程序能夠很方便的緩存單表查詢對應的結果對象。另外對於MYSQL的查詢緩存來講,若是關聯中的某個表發生了變化,那麼就沒法使用查詢緩存了,而拆分後,若是某個表不多改變,那麼基於該表的查詢就能夠重複利用查詢緩存結果了。
  2. 將查詢分解後,執行單個查詢就能夠減小鎖的競爭。
  3. 在應用層作關聯,能夠更容易對數據庫進行拆分,更容易作到高性能和可擴展。
  4. 查詢自己效率也可能會有提高。
  5. 能夠減小冗餘記錄的查詢。在應用層作關聯查詢,意味着對於某條記錄應用只需查詢一次,而在數據庫中作關聯查詢,則可能須要重複地訪問一部分數據(冗餘數據引發)。從這點看,這樣的重構還可能會減小網絡和內存的消耗。
  6. 更進一步,這樣作至關於在應用中實現了哈希關聯,而不是使用MySQL的嵌套循環關聯。某些場景哈希關聯的效率要高的多。

結論:恰當的時候選擇恰當的方法,遵循如下原則:

  • 數據量小時,聯合查詢比較簡便,10萬記錄以上不建議
  • 數據量大時,單表屢次查詢好處多多。
相關文章
相關標籤/搜索