提示:下方有源代碼地址,請自行拿取前端
朋友們,又見面了,上篇文章我們講到MySQL分庫分表的方法,這篇文章我們就針對上一篇文章模擬在MySQL中海量數據的優化方法,文章乾貨較多,建議你點贊、評論、收藏、關注起來慢慢看mysql
提示:如下是本篇文章正文內容,案例僅供參考git
我們建一張用戶表,表中的字段有用戶ID、用戶名、地址、記錄建立時間,如圖所示 web
OK,接下來準備寫一個存儲過程插入一百萬條數據面試
CREATE TABLE `t_user` (
`id` int NOT NULL,
`user_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`address` varchar(255) DEFAULT NULL,
`create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DELIMITER ;;
CREATE PROCEDURE user_insert()
BEGIN
DECLARE i INT DEFAULT 0;
WHILE i<1000000
DO
INSERT INTO t_user(id, user_name, address, create_time) VALUES (i, CONCAT('mayun',i), '浙江杭州', now());
SET i=i+1;
END WHILE ;
commit;
END;;
CALL user_insert();
複製代碼
插入完後我們看看數據條數sql
OK,我們看下分頁limit到必定值時的耗時是多少數據庫
能夠看到limit值越大,耗時越長,這還只是一百萬數據,要是千萬級、億級呢?編程
OK不廢話,我們立刻進行分頁優化瀏覽器
能夠看到比起以前 limit 1000000時的0.218s 效率提升了不少緩存
能夠看到比起以前 limit 1000000時的0.218s 效率也一樣提升了不少
能夠看到這種方法效率最高,但依賴於須要知道最大ID,這種適合點擊下一頁查詢(相似於滾動加載數據)的場景
而後能夠開啓多個線程去進行最高效率查詢語句的批量查詢操做 0~10000,10001-20000.... 這樣子的話能夠快速把全量數據查詢出來同步至緩存中。
分頁優化總結: 使用前一次查詢的最大ID進行查詢優化是效率最高的方法,但這種方法只適用於下一頁點擊的這種操做,對於同步全量數據來講建議的方式使用僞列對ID進行分頁,而後開啓多個線程同時查詢,把全量數據加載到緩存,之後面試官問你如何 快速獲取海量數據並加載到緩存 你該知道怎麼回答了吧。
先來看沒索引優化的狀況下的查詢效率
能夠看到這時沒用索引的狀況,用了0.305S接下來看看加了索引後的結果
只須要0.024S,咱們能夠EXPLAIN看下 能夠看到使用了普通索引後查詢效率明顯增長
複合索引何時用?爲何要用? 圍繞着這兩問題,我們先來講說複合索引何時用
咱們這裏建議一個複合索引
MySQL創建複合索引時實際創建了(user_name)、(user_name,address)、(user_name,address,create_time)三個索引,咱們都知道每多一個索引,都會增長寫操做的開銷和磁盤空間的開銷,對於海量數據的表,這但是不小的開銷,因此你會發現咱們在這裏使用複合索引一個頂三個,又能減小寫操做的開銷和磁盤空間的開銷
當咱們select user_name,address,create_time from t_user where user_name=xx and address = xxx時,MySQL能夠直接經過遍歷索引取得數據,無需回表,這減小了不少的隨機IO操做。因此,在真正的實際應用中,這就是覆蓋索引,是複合索引中主要的提高性能的優化手段之一。
能夠看到這條語句沒有使用到索引,是由於當or左右查詢字段只有一個是索引,該索引失效,只有當or左右查詢字段均爲索引時,纔會生效。
3. 使用複合索引時沒有遵循最左匹配原則
ref:這個鏈接類型只有在查詢使用了不是唯一或主鍵的鍵或者是這些類型的部分(好比,利用最左邊前綴)時發生。沒有值說明沒有利用最左前綴原則
再來看個使用了最左前綴的例子 4. 不要讓數據類型出現隱式轉化
能夠看如下兩個例子
5. 不要在索引字段上使用not,<>,!=,同樣會致使索引失效
6. 分解關聯查詢 例如這條語句
能夠分解成
7.小表驅動大表 即小的數據集驅動大的數據集。如:以t_user,t_order兩表爲例,兩表經過 t_user的id字段進行關聯。
當 t_order表的數據集小於t_user表時,用 in 優化 exist,使用 in,兩表執行順序是先查 t_order 表,再查t_user表
select * from t_user where id in (select user_id from t_order)
當 t_user 表的數據集小於 t_order 表時,用 exist 優化 in,使用 exists,兩表執行順序是先查 t_user 表,再查 t_order 表
select * from t_user where exists (select 1 from B where t_order.user_id= t_user.id)
複製代碼
首先了解下事務的隔離級別,數據庫共定義了四種隔離級別:
能夠經過 set transaction isolation level 設置事務隔離級別來提升性能
開啓查詢緩存
在解析一個查詢語句前,若是查詢緩存是打開的,那麼MySQL會檢查這個查詢語句是否命中查詢緩存中的數據。若是當前查詢剛好命中查詢緩存,在檢查一次用戶權限後直接返回緩存中的結果。這種狀況下,查詢不會被解析,也不會生成執行計劃,更不會執行。 MySQL將緩存存放在一個引用表(不要理解成table,能夠認爲是相似於HashMap的數據結構),經過一個哈希值索引,這個哈希值經過查詢自己、當前要查詢的數據庫、客戶端協議版本號等一些可能影響結果的信息計算得來。因此兩個查詢在任何字符上的不一樣(例如:空格、註釋),都會致使緩存不會命中。
若是查詢中包含任何用戶自定義函數、存儲函數、用戶變量、臨時表、mysql庫中的系統表,其查詢結果都不會被緩存。好比函數NOW()或者CURRENT_DATE()會由於不一樣的查詢時間,返回不一樣的查詢結果,再好比包含CURRENT_USER或者CONNECION_ID()的查詢語句會由於不一樣的用戶而返回不一樣的結果,將這樣的查詢結果緩存起來沒有任何的意義。
既然是緩存,就會失效,那查詢緩存什麼時候失效呢?MySQL的查詢緩存系統會跟蹤查詢中涉及的每一個表,若是這些表(數據或結構)發生變化,那麼和這張表相關的全部緩存數據都將失效。正由於如此,在任何的寫操做時,MySQL必須將對應表的全部緩存都設置爲失效。若是查詢緩存很是大或者碎片不少,這個操做就可能帶來很大的系統消耗,甚至致使系統僵死一下子。並且查詢緩存對系統的額外消耗也不只僅在寫操做,讀操做也不例外:
任何的查詢語句在開始以前都必須通過檢查,即便這條SQL語句永遠不會命中緩存
若是查詢結果能夠被緩存,那麼執行完成後,會將結果存入緩存,也會帶來額外的系統消耗
複製代碼
基於此,咱們要知道並非什麼狀況下查詢緩存都會提升系統性能,緩存和失效都會帶來額外消耗,只有當緩存帶來的資源節約大於其自己消耗的資源時,纔會給系統帶來性能提高。但要如何評估打開緩存是否可以帶來性能提高是一件很是困難的事情,也不在本文討論的範疇內。若是系統確實存在一些性能問題,能夠嘗試打開查詢緩存,並在數據庫設計上作一些優化,好比:
. 批量插入代替循環單條插入 . 合理控制緩存空間大小,通常來講其大小設置爲幾十兆比較合適 . 能夠經過SQL_CACHE和SQL_NO_CACHE來控制某個查詢語句是否須要進行緩存 最後的忠告是不要輕易打開查詢緩存,特別是寫密集型應用。若是你實在是忍不住,能夠將query_cache_type設置爲DEMAND,這時只有加入SQL_CACHE的查詢纔會走緩存,其餘查詢則不會,這樣能夠很是自由地控制哪些查詢須要被緩存。 固然查詢緩存系統自己是很是複雜的,這裏討論的也只是很小的一部分,其餘更深刻的話題,好比:緩存是如何使用內存的?如何控制內存的碎片化?事務對查詢緩存有何影響等等,讀者能夠自行閱讀相關資料,這裏權當拋磚引玉吧。 語法解析和預處理
#基礎配置
datadir=/data/datafile
socket=/var/lib/mysql/mysql.sock
log-error=/data/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
character_set_server=utf8
#容許任意IP訪問
bind-address = 0.0.0.0
#是否支持符號連接,即數據庫或表能夠存儲在my.cnf中指定datadir以外的分區或目錄,爲0不開啓
#symbolic-links=0
#支持大小寫
lower_case_table_names=1
#二進制配置
server-id = 1
log-bin = /data/log/mysql-bin.log
log-bin-index =/data/log/binlog.index
log_bin_trust_function_creators=1
expire_logs_days=7
#sql_mode定義了mysql應該支持的sql語法,數據校驗等
#mysql5.0以上版本支持三種sql_mode模式:ANSI、TRADITIONAL和STRICT_TRANS_TABLES。
#ANSI模式:寬鬆模式,對插入數據進行校驗,若是不符合定義類型或長度,對數據類型調整或截斷保存,報warning警告。
#TRADITIONAL模式:嚴格模式,當向mysql數據庫插入數據時,進行數據的嚴格校驗,保證錯誤數據不能插入,報error錯誤。用於事物時,會進行事物的回滾。
#STRICT_TRANS_TABLES模式:嚴格模式,進行數據的嚴格校驗,錯誤數據不能插入,報error錯誤。
sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
#InnoDB存儲數據字典、內部數據結構的緩衝池,16MB已經足夠大了。
innodb_additional_mem_pool_size = 16M
#InnoDB用於緩存數據、索引、鎖、插入緩衝、數據字典等
#若是是專用的DB服務器,且以InnoDB引擎爲主的場景,一般可設置物理內存的60%
#若是是非專用DB服務器,能夠先嚐試設置成內存的1/4
innodb_buffer_pool_size = 4G
#InnoDB的log buffer,一般設置爲 64MB 就足夠了
innodb_log_buffer_size = 64M
#InnoDB redo log大小,一般設置256MB 就足夠了
innodb_log_file_size = 256M
#InnoDB redo log文件組,一般設置爲 2 就足夠了
innodb_log_files_in_group = 2
#共享表空間:某一個數據庫的全部的表數據,索引文件所有放在一個文件中,默認這個共享表空間的文件路徑在data目錄下。默認的文件名爲:ibdata1 初始化爲10M。
#獨佔表空間:每個表都將會生成以獨立的文件方式來進行存儲,每個表都有一個.frm表描述文件,還有一個.ibd文件。其中這個文件包括了單獨一個表的數據內容以及索引內容,默認狀況下它的存儲位置也是在表的位置之中。
#設置參數爲1啓用InnoDB的獨立表空間模式,便於管理
innodb_file_per_table = 1
#InnoDB共享表空間初始化大小,默認是 10MB,改爲 1GB,而且自動擴展
innodb_data_file_path = ibdata1:1G:autoextend
#設置臨時表空間最大4G
innodb_temp_data_file_path=ibtmp1:500M:autoextend:max:4096M
#啓用InnoDB的status file,便於管理員查看以及監控
innodb_status_file = 1
#當設置爲0,該模式速度最快,但不太安全,mysqld進程的崩潰會致使上一秒鐘全部事務數據的丟失。
#當設置爲1,該模式是最安全的,但也是最慢的一種方式。在mysqld 服務崩潰或者服務器主機crash的狀況下,binary log 只有可能丟失最多一個語句或者一個事務。
#當設置爲2,該模式速度較快,也比0安全,只有在操做系統崩潰或者系統斷電的狀況下,上一秒鐘全部事務數據纔可能丟失。
innodb_flush_log_at_trx_commit = 1
#設置事務隔離級別爲 READ-COMMITED,提升事務效率,一般都知足事務一致性要求
#transaction_isolation = READ-COMMITTED
#max_connections:針對全部的帳號全部的客戶端並行鏈接到MYSQL服務的最大並行鏈接數。簡單說是指MYSQL服務可以同時接受的最大並行鏈接數。
#max_user_connections : 針對某一個帳號的全部客戶端並行鏈接到MYSQL服務的最大並行鏈接數。簡單說是指同一個帳號可以同時鏈接到mysql服務的最大鏈接數。設置爲0表示不限制。
#max_connect_errors:針對某一個IP主機鏈接中斷與mysql服務鏈接的次數,若是超過這個值,這個IP主機將會阻止從這個IP主機發送出去的鏈接請求。遇到這種狀況,需執行flush hosts。
#執行flush host或者 mysqladmin flush-hosts,其目的是爲了清空host cache裏的信息。可適當加大,防止頻繁鏈接錯誤後,前端host被mysql拒絕掉
#在 show global 裏有個系統狀態Max_used_connections,它是指從此次mysql服務啓動到如今,同一時刻並行鏈接數的最大值。它不是指當前的鏈接狀況,而是一個比較值。若是在過去某一個時刻,MYSQL服務同時有10
00個請求鏈接過來,而以後再也沒有出現這麼大的併發請求時,則Max_used_connections=1000.請注意與show variables 裏的max_user_connections的區別。#Max_used_connections / max_connections * 100% ≈ 85%
max_connections=600
max_connect_errors=1000
max_user_connections=400
#設置臨時表最大值,這是每次鏈接都會分配,不宜設置過大 max_heap_table_size 和 tmp_table_size 要設置同樣大
max_heap_table_size = 100M
tmp_table_size = 100M
#每一個鏈接都會分配的一些排序、鏈接等緩衝,通常設置爲 2MB 就足夠了
sort_buffer_size = 2M
join_buffer_size = 2M
read_buffer_size = 2M
read_rnd_buffer_size = 2M
#建議關閉query cache,有些時候對性能反而是一種損害
query_cache_size = 0
#若是是以InnoDB引擎爲主的DB,專用於MyISAM引擎的 key_buffer_size 能夠設置較小,8MB 已足夠
#若是是以MyISAM引擎爲主,可設置較大,但不能超過4G
key_buffer_size = 8M
#設置鏈接超時閥值,若是前端程序採用短鏈接,建議縮短這2個值,若是前端程序採用長鏈接,可直接註釋掉這兩個選項,是用默認配置(8小時)
#interactive_timeout = 120
#wait_timeout = 120
#InnoDB使用後臺線程處理數據頁上讀寫I/0請求的數量,容許值的範圍是1-64
#假設CPU是2顆4核的,且數據庫讀操做比寫操做多,可設置
#innodb_read_io_threads=5
#innodb_write_io_threads=3
#經過show engine innodb status的FILE I/O選項可查看到線程分配
#設置慢查詢閥值,單位爲秒
long_query_time = 120
slow_query_log=1 #開啓mysql慢sql的日誌
log_output=table,File #日誌輸出會寫表,也會寫日誌文件,爲了便於程序去統計,因此最好寫表
slow_query_log_file=/data/log/slow.log
##針對log_queries_not_using_indexes開啓後,記錄慢sql的頻次、每分鐘記錄的條數
#log_throttle_queries_not_using_indexes = 5
##做爲從庫時生效,從庫複製中如何有慢sql也將被記錄
#log_slow_slave_statements = 1
##檢查未使用到索引的sql
#log_queries_not_using_indexes = 1
#快速預熱緩衝池
innodb_buffer_pool_dump_at_shutdown=1
innodb_buffer_pool_load_at_startup=1
#打印deadlock日誌
innodb_print_all_deadlocks=1
複製代碼
這些參數可按照本身的實際服務器以及數據庫的大小進行適當調整,主要起參考做用
不少系統一開始並無考慮表字段拆分的問題,由於拆分會帶來邏輯、部署、運維的各類複雜度,通常以整型值爲主的表在千萬級如下,字符串爲主的表在五百萬如下,而事實上不少時候MySQL單表的性能依然有很多優化空間,甚至能正常支撐千萬級以上的數據量:
下面直接看下如何去優化字段
Scale up,這個很少說了,根據MySQL是CPU密集型仍是I/O密集型,經過提高CPU和內存、使用SSD,都能顯著提高MySQL性能
也是目前經常使用的優化,從庫讀主庫寫,通常不要採用雙主或多主引入不少複雜性,儘可能採用文中的其餘方案來提升性能。同時目前不少拆分的解決方案同時也兼顧考慮了讀寫分離
使用緩存
緩存能夠發生在這些層次:
MySQL內部:在系統內核參數優化介紹了相關設置
數據訪問層:好比MyBatis針對SQL語句作緩存,而Hibernate能夠精確到單個記錄,這裏緩存的對象主要是持久化對象Persistence Object
應用服務層:這裏能夠經過編程手段對緩存作到更精準的控制和更多的實現策略,這裏緩存的對象是數據傳輸對象Data Transfer Object
Web層:針對web頁面作緩存
瀏覽器客戶端:用戶端的緩存
能夠根據實際狀況在一個層次或多個層次結合加入緩存。這裏重點介紹下服務層的緩存實現,目前主要有兩種方式:
直寫式(Write Through):在數據寫入數據庫後,同時更新緩存,維持數據庫與緩存的一致性。這也是當前大多數應用緩存框架如Spring Cache的工做方式。這種實現很是簡單,同步好,但效率通常。
回寫式(Write Back):當有數據要寫入數據庫時,只會更新緩存,而後異步批量的將緩存數據同步到數據庫上。這種實現比較複雜,須要較多的應用邏輯,同時可能會產生數據庫與緩存的不一樣步,但效率很是高。
水平拆分:個人上篇文章有講到,這裏再也不贅述。
其實MySQL的優化還有不少,有興趣的能夠讀讀MySQL高性能優化的書,但以上這些是在咱們實際生產環境中比較經常使用的優化手段,掌握這些,不是我吹,能吊打通常的面試官了,好了,這篇文章就到這裏,若是對你有幫助,請點贊、收藏、評論、關注我,謝謝!
路漫漫其修遠兮,吾願與君上下而求索,很是感謝各位帥哥、靚妹的點贊、收藏和評論,咱們下期見。
關注我帶你走進架構師的成長之路
源碼地址:點此查看源碼.