MySQL 能夠分爲 Server 層和存儲引擎層兩部分。mysql
Server層包括鏈接器、查詢緩存、分析器、優化器、執行器等,涵蓋MySQL的大多數核心服務功能,以及全部的內置函數(如日期、時間、數學和加密函數等),全部跨存儲引擎的功能都在這一層實現,好比存儲過程、觸發器、視圖等。算法
存儲引擎層負責數據的存儲和提取。其架構模式是插件式的,支持 InnoDB、MyISAM、Memory 等多個存儲引擎。如今最經常使用的存儲引擎是InnoDB,它從MySQL 5.5.5版本開始成爲了默認存儲引擎。create table 語句中使用 engine=memory,來指定使用內存引擎建立表。sql
如今最經常使用的存儲引擎是InnoDB,它從MySQL 5.5.5版本開始成爲了默認存儲引擎。create table 語句中使用 engine=memory, 來指定使用內存引擎建立表。數據庫
第一步,鏈接器鏈接到數據庫,鏈接器負責跟客戶端創建鏈接、獲取權限、維持和管理鏈接。後端
鏈接命令通常是這麼寫的:mysql -h$ip -P$port -u$user -p$password帳號密碼錯誤會報錯:Access denied for user數組
鏈接完成後,若是沒有後續的動做,這個鏈接就處於空閒狀態,能夠在show processlist命令中看到它。文本中這個圖是show processlist的結果,其中的Command列顯示爲"Sleep"的這一行,就表示如今系統裏面有一個空閒鏈接。緩存
客戶端若是太長時間沒動靜,鏈接器就會自動將它斷開。這個時間是由參數wait timeout控制的,默認值是8小時。安全
斷開後再執行sql會報錯:Lost connection to MySQL server during query
創建鏈接的過程一般是比較複雜的,因此建議在使用中要儘可能減小創建鏈接的動做,也就是儘可能使用長鏈接。性能優化
可是 MySQL 在執行過程當中臨時使用的內存是管理在鏈接對象裏面的。這些資源會在鏈接斷開的時候才釋放。因此若是長鏈接累積下來,可能致使內存佔用太大,被系統強行殺掉(OOM),從現象看就是 MySQL 異常重啓。網絡
怎麼解決這個問題呢?能夠考慮如下兩種方案。
第二步,查詢語句會先查詢緩存,以前執行過的語句及其結果可能會以 key-value 對的形式,被直接緩存在內存中。key 是查詢的語句,value 是查詢的結果。
可是查詢緩存利大於弊,由於查詢緩存的失效很是頻繁,只要有對一個表的更新,這個表上全部的查詢緩存都會被清空。
除非是靜態配置表才適合用查詢緩存。能夠將參數 query_cache_type 設置成DEMAND,這樣對於默認的 SQL 語句都不使用查詢緩存。SQL_CACHE 顯式指定使用查詢緩存。
select SQL_CACHE * from T where ID=10;
可是,MySQL 8.0版本完全刪除了查詢緩存功能。
第三步,分析語句,先是詞法分析,找出select,表名,列名等關鍵字;而後是語法分析,判斷語法是否正確。表名列名不對的sql,會在語法分析時報錯。
語法錯誤:ERROR 1064 (42000): You have an error in your SQL syntax;
第四步,決定使用哪一個索引,join的時候決定各個表的鏈接順序。
第五步,先判斷對當前表是否有權限(若是命中查詢緩存,會在返回結果時驗證權限)。
ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T'
如:select * from T where ID=10; 執行過程
慢查詢日誌中有一行 rows_examined 字段,表示這個語句執行過程當中掃描了多少行。這個值就是在執行器每次調用引擎獲取數據行的時候累加的。可是引擎掃描行數跟 rows_examined 並非徹底相同的。
對一個200G的大表作全表掃描,而內存只有16G,會不會把數據庫主機的內存用光了?
實際上,MySQL不是取到所有數據再返回客戶端。取數據和發數據的流程是這樣的:
MySQL 客戶端發送請求後,接收服務端返回結果的方式有兩種:
另外一種是不緩存,讀一個處理一個。若是用 API 開發,對應的就是 mysql_use_result 方法。
MySQL 客戶端默認採用第一種方式,而若是加上–quick 參數,就會使用第二種不緩存的方式。採用不緩存的方式時,若是本地處理得慢,就會致使服務端發送結果被阻塞,所以會讓服務端變慢。
MySQL 是「邊讀邊發的」。這就意味着,若是客戶端接收得慢,會致使 MySQL 服務端因爲結果發不出去,這個事務的執行時間變長。
對於正常的線上業務來講,若是一個查詢的返回結果不會不少的話,都建議使用 mysql_store_result 這個接口,直接把查詢結果保存到本地內存。
更新語句一樣會走鏈接器,查詢緩存(清空該表緩存),分析器,優化器這一套流程,與查詢流程不同的是,更新流程還涉及兩個重要的日誌模塊,redo log(重作日誌)和 binlog(歸檔日誌)。
若是每一次的更新操做都須要寫進磁盤,而後磁盤也要找到對應的那條記錄,而後再更新,整個過程 IO 成本、查找成本都很高。
MySQL採用了WAL技術,全稱是 Write-Ahead Logging,的關鍵點就是先寫日誌,再寫磁盤。
具體來講,當有一條記錄須要更新的時候,InnoDB 引擎就會先把記錄寫到 redo log裏面,並更新內存,這個時候更新就算完成了。同時,InnoDB 引擎會在適當的時候,將這個操做記錄更新到磁盤裏面,而這個更新每每是在系統比較空閒的時候作。
可是若是 InnoDB 的 redo log 寫滿了。這時候系統會中止全部更新操做,把 checkpoint 往前推動(對應的全部髒頁都 flush 到磁盤上),redo log 留出空間能夠繼續寫。
一旦一個查詢請求須要在執行過程當中先 flush 掉一個髒頁時,這個查詢就可能要比平時慢了。因爲刷髒頁的邏輯會佔用 IO 資源並可能影響到了更新語句,要儘可能避免這種狀況,就要合理地設置 innodb_io_capacity 的值,而且平時要多關注髒頁比例,不要讓它常常接近 75%。髒頁比例是經過 Innodb_buffer_pool_pages_dirty/Innodb_buffer_pool_pages_total 獲得的,具體的命令參考下面代碼:
mysql> select VARIABLE_VALUE into @a from global_status where VARIABLE_NAME = 'Innodb_buffer_pool_pages_dirty';
select VARIABLE_VALUE into @b from global_status where VARIABLE_NAME = 'Innodb_buffer_pool_pages_total';
select @a/@b;在 InnoDB 中,innodb_flush_neighbors 參數就是用來控制這個行爲的,值爲 1 的時候會有「連坐」機制,值爲 0 時表示不找鄰居,本身刷本身的。固態硬盤建議設置爲0。
InnoDB 的 redo log 是能夠配置的固定大小,好比能夠配置爲一組 4 個文件,每一個文件的大小是 1GB,總共就能夠記錄 4GB 的操做。從頭開始寫,寫到末尾就又回到開頭循環寫,以下面這個圖所示。若是redo log 設置的過小,磁盤壓力很小,可是數據庫出現間歇性的性能下跌。
write pos 是當前記錄的位置,一邊寫一邊後移,寫到第 3 號文件末尾後就回到 0 號文件開頭。checkpoint 是當前要擦除的位置,也是日後推移而且循環的,擦除記錄前要把記錄更新到數據文件。
write pos 和 checkpoint 之間的是還空着的部分,能夠用來記錄新的操做。若是 write pos 追上 checkpoint,這時候就得停下來先擦掉一些記錄,把 checkpoint 推動一下。
有了 redo log,InnoDB 就能夠保證即便數據庫發生異常重啓,以前提交的記錄都不會丟失,這個能力稱爲crash-safe。
redo log buffer :插入數據的過程當中,生成的日誌都得先保存起來,但又不能在還沒 commit 的時候就直接寫到 redo log 文件裏。因此,redo log buffer 就是一塊內存,用來先存 redo 日誌的。也就是說,在執行第一個 insert 的時候,數據的內存被修改了,在執行 commit 的時候 redo log buffer 才寫入了日誌。
爲了控制 redo log 的寫入策略,innodb_flush_log_at_trx_commit 參數,它有三種可能取值:
InnoDB 有一個後臺線程,每隔 1 秒,就會把 redo log buffer 中的日誌,調用 write 寫到文件系統的 page cache,而後調用 fsync 持久化到磁盤。也就是說,一個沒有提交的事務的 redo log,也是可能已經持久化到磁盤的。
還有兩種場景也會把沒有提交的redo log 寫到硬盤。
redo log 是 InnoDB 引擎特有的日誌,而 Server 層也有本身的日誌,稱爲 binlog(歸檔日誌)。
binlog 的三種格式對比:
statement:記錄到 binlog 裏的是語句原文,最後會有 COMMIT;可能會致使主備不一致,由於limit 、等sql 執行時可能主備優化器選擇的索引不同,排序也不同。now()執行的結果也不同。
row :記錄了操做的事件每一條數據的變化狀況,最後會有一個 XID event。缺點是太佔空間。
mixed:同時使用兩種格式,由數據庫判斷具體某條sql使用哪一種格式。可是有選擇錯誤的狀況。
這兩種日誌有如下三點不一樣。
redo log 和 binlog 是怎麼關聯起來的?
它們有一個共同的數據字段,叫 XID。崩潰恢復的時候,會按順序掃描 redo log:
處於 prepare 階段的 redo log 加上完整 binlog,重啓也能恢復,由於 binlog 完整了,那麼從庫就同步過去了,爲了保證主從一致,有完整的 binlog 就算成功。
事務執行過程當中,先把日誌寫到 binlog cache,事務提交的時候,再把 binlog cache 寫到 binlog 文件中。
write 和 fsync 的時機,是由參數 sync_binlog 控制的:
比較常見的是將其設置爲 100~1000 中的某個數值。對應的風險是:若是主機發生異常重啓,會丟失最近 N 個事務的 binlog 日誌。
好比:update T set c=c+1 where ID=2;
這裏給出這個 update 語句的執行流程圖,圖中淺色框表示是在 InnoDB 內部執行的,深色框表示是在執行器中執行的。其實就是把redo log 和binlog 作兩階段提交,爲了讓兩份日誌之間的邏輯一致。
保存必定時間的binlog,同時系統會按期作整庫備份。
當須要恢復到指定的某一秒時,
redo log 用於保證 crash-safe 能力。innodb_flush_log_at_trx_commit 這個參數設置成 1 的時候,表示每次事務的 redo log 都直接持久化到磁盤。這個參數建議設置成 1,這樣能夠保證 MySQL 異常重啓以後數據不丟失。
binlog用於備份恢復和從庫同步。sync_binlog 這個參數設置成 1 的時候,表示每次事務的 binlog 都持久化到磁盤。這個參數也建議設置成 1,這樣能夠保證 MySQL 異常重啓以後 binlog 不丟失。
一主一備結構,須要注意主備切換,備庫設置只讀,避免切換bug形成雙寫不一致問題(設置 readonly 對超級用戶是無效的,同步更新的線程有超級權限,因此還能寫入同步數據)。
雙主結構,要避免循環更新問題,由於MySQL 在 binlog 中記錄了這個命令第一次執行時所在實例的 server id。因此能夠規定兩個庫的 server id 必須不一樣,每一個庫在收到從本身的主庫發過來的日誌後,先判斷 server id,若是跟本身的相同,表示這個日誌是本身生成的,就直接丟棄這個日誌。
能夠在備庫上執行 show slave status 命令,它的返回結果裏面會顯示 seconds_behind_master,用於表示當前備庫延遲了多少秒。每一個事務的 binlog 裏面都有一個時間字段,用於記錄主庫上寫入的時間; 備庫取出當前正在執行的事務的時間字段的值,計算它與當前系統時間的差值,獲得 seconds_behind_master。
主備延遲最直接的表現是,備庫消費中轉日誌(relay log)的速度,比主庫生產 binlog 的速度要慢。
主備延遲的來源
考慮到主備切換,主備機器通常都同樣了,可是還可能備庫讀的壓力太大,
一主多從,或者經過binlog輸出到外部系統(好比Hadoop),讓外部系統提供部分統計查詢能力。
在官方的 5.6 版本以前,MySQL 只支持單線程複製,由此在主庫併發高、TPS 高時就會出現嚴重的主備延遲問題。
並行複製策略有按表並行分發策略,按行並行分發策略,可是按行分發在決定線程分發的時候,須要消耗更多的計算資源。這兩個方案其實都有一些約束條件:
官方 MySQL5.6 版本,支持了並行複製,只是支持的粒度是按庫並行。相比於按表和按行分發,這個策略有兩個優點:
MariaDB 的並行複製策略,僞模擬主庫併發度,主庫 redo log 組提交 (group commit) 優化,同一組提交會記錄commit_id,備庫把同一個commit_id分發到多個worker執行。
官方的 MySQL5.7 版本,由參數 slave-parallel-type 來控制並行複製策略:
MySQL 5.7.22 版本里,MySQL 增長了一個新的並行複製策略,基於 WRITESET 的並行複製。對於事務涉及更新的每一行,計算出這一行的 hash 值,組成集合 writeset。若是兩個事務沒有操做相同的行,也就是說它們的 writeset 沒有交集,就能夠並行。
讀寫分離有兩種方案:
主從延遲的狀況下怎麼辦?
配合 semi-sync 方案;半同步複製:
ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、、隔離性、持久性)。
當數據庫上有多個事務同時執行的時候,就可能出現髒讀(dirty read)、不可重複讀(non-repeatable read)、幻讀(phantom read)的問題,爲了解決這些問題,就有了「隔離級別」的概念。
幻讀:仍然指的是一個事務中讀了兩次,結果不一樣,可是與不可重複讀不一樣的是,這裏不一樣是由於別的事物作了插入操做,而是讀的條件是一個範圍的條件,這樣第二次會多讀到一條數據。
不可重複讀重點在於update和delete,而幻讀的重點在於insert。
即便把全部的記錄都加上鎖,仍是阻止不了新插入的記錄,也就是說行鎖解決不了幻讀問題,行鎖只能鎖住行,可是新插入記錄這個動做,要更新的是記錄之間的「間隙」。所以,爲了解決幻讀問題,InnoDB 只好引入新的鎖,也就是間隙鎖 (Gap Lock)。
當執行 select * from t where d=5 for update 的時候,就不止是給數據庫中已有的 6 個記錄加上了行鎖,還同時加了 7 個間隙鎖。這樣就確保了沒法再插入新的記錄。
間隙鎖和行鎖合稱 next-key lock,每一個 next-key lock 是前開後閉區間。也就是說,表 t 初始化之後,若是用 select * from t for update 要把整個表全部記錄鎖起來,就造成了 7 個 next-key lock,分別是(-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, +supremum]。
間隙鎖和 next-key lock 的引入,解決了幻讀的問題,但同時也帶來了一些「困擾」。間隙鎖的引入,可能會致使一樣的語句鎖住更大的範圍,這實際上是影響了併發度的。
SQL 標準的事務隔離級別包括:讀未提交read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和串行化(serializable )。隔離級別越高,效率越低。
在實現上,數據庫裏面會建立一個視圖,訪問的時候以視圖的邏輯結果爲準。
在 MySQL 裏,有兩個「視圖」的概念:
MySQL 默認隔離級別是可重複讀,Oracle 默認隔離級別是「讀提交」。
將啓動參數 transaction-isolation 的值設置成 READ-UNCOMMITTED、READ-COMMITTED、REPEATABLE-READ 、SERIALIZABLE。
能夠用 show variables 來查看當前的值。
每條記錄在更新的時候都會同時記錄一條回滾操做。同一條記錄在系統中能夠存在多個版本,這就是數據庫的(MVCC)。
MVCC的全稱是「多版本併發控制」。爲了查詢一些正在被另外一個事務更新的行,而且能夠看到它們被更新以前的值,不用等待另外一個事務釋放鎖。
InnoDB會給數據庫中的每一行增長三個字段,它們分別是DB_TRX_ID(事務版本號)、DB_ROLL_PTR(建立時間)、DB_ROW_ID(惟一id)。
InnoDB 裏面每一個事務有一個惟一的事務 ID,叫做 transaction id。它是在事務開始的時候向 InnoDB 的事務系統申請的,是按申請順序嚴格遞增的。
InnoDB 利用了「全部數據都有多個版本」的這個特性,實現了「秒級建立快照」的能力。
B+Tree葉結點上,始終存儲的是最新的數據(多是還未提交的數據)。而舊版本數據,經過UNDO記錄存儲在回滾段(Rollback Segment)裏。每一條記錄都會維護一個ROW HEADER元信息,存儲有建立這條記錄的事務ID,一個指向UNDO記錄的指針。經過最新記錄和UNDO信息,能夠還原出舊版本的記錄。
假設一個值從 1 被按順序改爲了 二、三、4,在回滾日誌裏面就會有相似下面的記錄。
當前值是 4,可是在查詢這條記錄的時候,不一樣時刻啓動的事務會有不一樣的 read-view。同一條記錄在系統中能夠存在多個版本,就是數據庫的多版本併發控制(MVCC)。對於 read-view A,要獲得 1,就必須將當前值依次執行圖中全部的回滾操做獲得。這些回滾信息記錄在undo log 裏。
當系統裏沒有比這個回滾日誌更早的 read-view 的時候會刪除老的undo log。
儘可能不要使用長事務,長事務意味着系統裏面會存在很老的事務視圖。會有很大的undo log日誌佔用空間。並且長事務還會佔據鎖資源,也可能拖垮整個庫。
能夠在 information_schema 庫的innodb_trx 這個表中查詢長事務,好比下面這個語句,用於查找持續時間超過 60s 的事務。能夠監控這個表,設置長事務閾值報警或者直接kill。
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60
能夠經過 SET MAX_EXECUTION_TIME 命令來控制每一個語句執行的最長時間,避免單個語句意外執行太長時間。
確認是否有沒必要要的只讀事務。
若是使用的是 MySQL 5.6 或者更新版本,把 innodb_undo_tablespaces 設置成 2或更大的值)。若是真的出現大事務致使undo log過大,這樣設置後清理起來更方便。
Hash表 + 鏈表,查詢新增都很快,可是隻適用於只有等值查詢的場景,不能區間查詢, Memcached 及其餘一些 NoSQL 引擎在用。
有序數組,等值查詢和範圍查詢場景中的性能就都很是優秀,二分查找O(log(N)),可是更新的效率很低,因此只適用於靜態存儲引擎。
平衡二叉樹,更新和查詢都比較快。
還有跳躍表,LSM樹等。
爲了讓一個查詢儘可能少地讀磁盤,就須要使用多叉樹。MySQL採用的是B+樹,因爲索引不止存在內存中,還要寫到磁盤上。二叉樹的樹高過高,100萬數據,就有20層,在機械硬盤時代,從磁盤隨機讀一個數據塊須要 10 ms 左右的尋址時間。就要花費200ms的尋址時間,就太慢了。MySQL B+樹 的一層節點數量在1200左右,只須要1-3次磁盤IO就能夠了,由於InnoDB存儲引擎的最小儲存單元頁(Page),一個頁的大小是16K。通常來講主鍵id爲bigint類型,長度8字節,指針6字節,那麼16284/14 = 1170。因此一次IO最多讀取1170個節點。
相對於B樹,B+樹把全部的數據都放在了葉子節點上,這樣雖然每次都須要查詢葉子節點,但也不過兩三層,若是幹節點也放數據,那幹節點就變大了,一次就讀取不了1200節點了,層高會變大不少。
而且MySQL把B+樹的全部葉子節點的數據用指針連起來了,這樣作區間查詢是很是快的。
主鍵索引的葉子節點存的是整行數據。在 InnoDB 裏,主鍵索引也被稱爲聚簇索引(clustered index)。
非主鍵索引的葉子節點內容是主鍵的值。在 InnoDB 裏,非主鍵索引也被稱爲二級索引(secondary index)。
查詢語句,若是走主鍵索引,會直接獲得數據,若是走非主鍵索引,查到主鍵後,還須要回主鍵索引再查一次數據。這個過程稱爲回表。(覆蓋索引不須要回表)
分爲聚簇索引和非聚簇索引的緣由:更新數據的時候,因爲數據的地址變了,須要更改索引,可是因爲數據只跟主鍵索引綁定,索引只須要更新聚簇索引,固然還有被更新列涉及到的索引也要更新。若是全部全部都跟數據綁定,雖然省掉了回表的過程,可是每次更新,須要更新全部的索引,得不償失。
B+ 樹爲了維護索引有序性,在插入新值的時候須要作必要的維護。
好比按順序插入1-499,501-1000,索引都在一頁,再插入一個500,根據 B+ 樹的算法,這時候須要申請一個新的數據頁,而後挪動部分數據(501到1000的數據)過去。這個過程稱爲頁分裂。在這種狀況下,性能天然會受影響。
除了影響性能外,頁分裂操做還影響數據頁的利用率。本來放在一個頁的數據,如今分到兩個頁中,總體空間利用率下降大約 50%。
固然有分裂就有合併。當相鄰兩個頁因爲刪除了數據,利用率很低以後,會將數據頁作合併。合併的過程,能夠認爲是分裂過程的逆過程。
因此通常建表規範都要求用自增主鍵,避免頁分裂,固然也有特殊狀況,使用別的字段當作主鍵。
而且索引可能由於刪除,或者頁分裂等緣由,致使數據頁有空洞,重建索引的過程會建立一個新的索引,把數據按順序插入,這樣頁面的利用率最高,也就是索引更緊湊、更省空間。
alter table T drop index k;
alter table T add index(k);
可是不能重建主鍵索引,不管是刪除主鍵仍是建立主鍵,都會將整個表重建。可使用 alter table T engine=InnoDB 重建表。
若是執行的語句是 select ID from T where k between 3 and 5,這時只須要查 ID 的值,而 ID 的值已經在 k 索引樹上了,所以能夠直接提供查詢結果,不須要回表。也就是說,在這個查詢裏面,索引 k 已經「覆蓋了」查詢需求,稱爲覆蓋索引。
因爲覆蓋索引能夠減小樹的搜索次數,顯著提高查詢性能,因此使用覆蓋索引是一個經常使用的性能優化手段。
若是有根據身份證號查詢市民信息的需求,只要在身份證號字段上創建索引就夠了。若是如今有一個高頻請求,要根據市民的身份證號查詢他的姓名,再創建一個(身份證號、姓名)的聯合索引就是覆蓋索引,省去了回表環節。
若是爲每一種查詢都設計一個索引,索引是否是太多了。
B+ 樹這種索引結構,能夠利用索引的「最左前綴」,來定位記錄。
爲了直觀地說明這個概念,用(name,age)這個聯合索引來分析。
能夠看到,索引項是按照索引定義裏面出現的字段順序排序的。
當邏輯需求是查到全部名字是「張三」的人時,能夠快速定位到 ID4,而後向後遍歷獲得全部須要的結果。
若是要查的是全部名字第一個字是「張」的人,SQL 語句的條件是"where name like ‘張 %’"。這時,也可以用上這個索引,查找到第一個符合條件的記錄是 ID3,而後向後遍歷,直到不知足條件爲止。
能夠看到,不僅是索引的所有定義,只要知足最左前綴,就能夠利用索引來加速檢索。這個最左前綴能夠是聯合索引的最左 N 個字段,也能夠是字符串索引的最左 M 個字符。
使用前綴索引,定義好長度,就能夠作到既節省空間,又不用額外增長太多的查詢成本。
在創建索引時關注的是區分度,區分度越高越好。由於區分度越高,意味着重複的鍵值越少。所以,能夠經過統計索引上有多少個不一樣的值來判斷要使用多長的前綴。
可使用下面這個語句,算出這個列上有多少個不一樣的值:
select count(distinct email) as L from SUser;
使用前綴索引就用不上覆蓋索引對查詢性能的優化了,這是在選擇是否使用前綴索引時須要考慮的一個因素。
那麼對於身份證號,一共 18 位,其中前 6 位是地址碼,因此同一個縣的人的身份證號前 6 位通常會是相同的。該怎麼存儲,怎麼設計索引呢?
第一種方式是使用倒序存儲。身份證號的最後 6 位沒有地址碼這樣的重複邏輯。
select field_list from t where id_card = reverse('input_id_card_string');select field_list from t where id_card = reverse('input_id_card_string');
第二種方式是使用 hash 字段。在表上再建立一個整數字段,來保存身份證的校驗碼,同時在這個字段上建立索引。
alter table t add id_card_crc int unsigned, add index(id_card_crc);而後每次插入新記錄的時候,都同時用 crc32() 這個函數獲得校驗碼填到這個新字段。因爲校驗碼可能存在衝突,因此查詢語句 where 部分要判斷 id_card 的值是否精確相同。
select field_list from t where id_card_crc=crc32('input_id_card_string') and id_card='input_id_card_string'
最左前綴的時候,那些不符合最左前綴的部分,會怎麼樣呢?
若是如今有一個需求:檢索出表中「名字第一個字是張,並且年齡是 10 歲的全部男孩」。那麼,SQL 語句是這麼寫的:
mysql> select * from tuser where name like '張 %' and age=10 and ismale=1;
這個語句在搜索索引樹的時候,只能用 「張」,找到第一個知足條件的記錄 ID3。
而後須要判斷其餘條件是否知足。
在 MySQL 5.6 以前,只能從 ID3 開始一個個回表。到主鍵索引上找出數據行,再對比字段值。
而 MySQL 5.6 引入的索引下推優化(index condition pushdown),能夠在索引遍歷過程當中,對索引中包含的字段先作判斷,直接過濾掉不知足條件的記錄,減小回表次數。
當須要更新一個數據頁時,若是數據頁在內存中就直接更新,而若是這個數據頁尚未在內存中的話,在不影響數據一致性的前提下,InooDB 會將這些更新操做緩存在 change buffer 中,這樣就不須要從磁盤中讀入這個數據頁了。在下次查詢須要訪問這個數據頁的時候,將數據頁讀入內存,而後執行 change buffer 中與這個頁有關的操做。經過這種方式就能保證這個數據邏輯的正確性。雖然是隻更新內存,可是在事務提交的時候,把 change buffer 的操做也記錄到 redo log 裏了,因此崩潰恢復的時候,change buffer 也能找回來。
須要說明的是,雖然名字叫做 change buffer,實際上它是能夠持久化的數據。也就是說,change buffer 在內存中有拷貝,也會被寫入到磁盤上。
將 change buffer 中的操做應用到原數據頁,獲得最新結果的過程稱爲 merge。除了訪問這個數據頁會觸發 merge 外,系統有後臺線程會按期 merge。在數據庫正常關閉(shutdown)的過程當中,也會執行 merge 操做。
顯然,若是可以將更新操做先記錄在 change buffer,減小讀磁盤,語句的執行速度會獲得明顯的提高。並且,數據讀入內存是須要佔用 buffer pool 的,因此這種方式可以避免佔用內存,提升內存利用率。
惟一索引的更新就不能使用 change buffer,實際上也只有普通索引可使用。
change buffer 用的是 buffer pool 裏的內存,所以不能無限增大。change buffer 的大小,能夠經過參數 innodb_change_buffer_max_size 來動態設置。這個參數設置爲 50 的時候,表示 change buffer 的大小最多隻能佔用 buffer pool 的 50%。
若是要在這張表中插入一個新記錄 (4,400) 的話,InnoDB 的處理流程是怎樣的。
第一種狀況是,這個記錄要更新的目標頁在內存中。這時,InnoDB 的處理流程以下:
對於普通索引來講,找到 3 和 5 之間的位置,插入這個值,語句執行結束。
這個判斷只會耗費微小的 CPU 時間。不是重點
第二種狀況是,這個記錄要更新的目標頁不在內存中。這時,InnoDB 的處理流程以下:
將數據從磁盤讀入內存涉及隨機 IO 的訪問,是數據庫裏面成本最高的操做之一。change buffer 由於減小了隨機磁盤訪問,因此對更新性能的提高是會很明顯的。
change buffer 適用於寫多讀少的業務,好比帳單類、日誌類的系統。由於會記錄不少change buffer(寫的時候) 纔會merge(讀的時候)
反過來,讀多寫少的業務,幾乎每次把更新記錄在change buffer 後,就會當即出發merge,這樣隨機訪問 IO 的次數不會減小,反而增長了change buffer 的維護代價。
因此,對於身份證號這類字段,若是業務已經保證不會寫入重複數據,不須要數據庫作約束,加普通索引比加主鍵索引要好,若是全部的更新後面,都立刻伴隨着對這個記錄的查詢,那麼應該關閉 change buffer。而在其餘狀況下,change buffer 都能提高更新性能。
在實際使用中,能夠發現,普通索引和 change buffer 的配合使用,對於數據量大的表的更新優化仍是很明顯的,特別是在使用機械硬盤時。
change buffer 和 redo log 對比
insert into t(id,k) values(id1,k1),(id2,k2);
這條更新語句作了以下操做:
後續的更新操做
因此,若是要簡單地對比這兩個機制在提高更新性能上的收益的話,redo log 主要節省的是隨機寫磁盤的 IO 消耗(轉成順序寫),而 change buffer 主要節省的則是隨機讀磁盤的 IO 消耗。
優化器結合是否掃描行數、是否使用臨時表、是否排序等因素進行綜合判斷。
MySQL 在真正開始執行語句以前,並不能精確地知道知足條件的記錄有多少條,而只能根據統計信息來估算記錄數。
這個統計信息就是索引的「區分度」。顯然,一個索引上不一樣的值越多,這個索引的區分度就越好。而一個索引上不一樣的值的個數,稱之爲「基數」(cardinality)。也就是說,這個基數越大,索引的區分度越好。
可使用 show index 方法,看到一個索引的基數。
MySQL 採樣統計的方法得到基數,InnoDB 默認會選擇 N 個數據頁,統計這些頁面上的不一樣值,獲得一個平均值,而後乘以這個索引的頁面數,就獲得了這個索引的基數。當變動的數據行數超過 1/M 的時候,會自動觸發從新作一次索引統計。analyze table t 命令,能夠用來從新統計索引信息。
在 MySQL 中,有兩種存儲索引統計的方式,能夠經過設置參數 innodb_stats_persistent 的值來選擇:
其實索引統計只是一個輸入,對於一個具體的語句來講,優化器還要判斷,執行這個語句自己要掃描多少行。
rows 這個字段表示的是預計掃描行數。
少數狀況下優化器會選錯索引,第一種方法能夠採用 force index 強行選擇一個索引。
但其實使用 force index 最主要的問題仍是變動的及時性。由於選錯索引的狀況仍是比較少出現的,因此開發的時候一般不會先寫上 force index。而是等到線上出現問題的時候,纔會再去修改 SQL 語句、加上 force index。可是修改以後還要測試和發佈,對於生產系統來講,這個過程不夠敏捷。
因此,數據庫的問題最好仍是在數據庫內部來解決。既然優化器放棄了使用索引 a,說明 a 還不夠合適,因此第二種方法就是,能夠考慮修改語句,引導 MySQL 使用指望的索引。好比,在這個例子裏,顯然把「order by b limit 1」 改爲 「order by b,a limit 1」 ,語義的邏輯是相同的。
以前優化器選擇使用索引 b,是由於它認爲使用索引 b 能夠避免排序(b 自己是索引,已是有序的了,若是選擇索引 b 的話,不須要再作排序,只須要遍歷),因此即便掃描行數多,也斷定爲代價更小。
如今 order by b,a 這種寫法,要求按照 b,a 排序,就意味着使用這兩個索引都須要排序。所以,掃描行數成了影響決策的主要條件,因而此時優化器選了只須要掃描 1000 行的索引 a。
固然,這種修改並非通用的優化手段,可能修改語義這件事兒不太好,能夠用 limit 100 讓優化器意識到,使用 b 索引代價是很高的。實際上是根據數據特徵誘導了一下優化器,也不具有通用性。
select from (select from t where (a between 1 and 1000) and (b between 50000 and 100000) order by b limit 100)alias limit 1;
第三種方法是:在有些場景下,能夠新建一個更合適的索引,來提供給優化器作選擇,或刪掉誤用的索引。
對索引字段作函數操做,可能會破壞索引值的有序性,所以優化器就決定放棄走樹搜索功能。
條件字段函數操做
select count(*) from tradelog where month(t_modified)=7;同理 where id+1=1000 也不會用索引,改爲 where id =1000 - 1 會用索引。
隱式類型轉換
select * from tradelog where tradeid=110717; (tradeid 是varchar)等同於 select * from tradelog where CAST(tradid AS signed int) = 110717;
隱式字符編碼轉換
select * from trade_detail where tradeid=$L2.tradeid.value;$L2.tradeid.value 的字符集是 utf8mb4。字符集 utf8mb4 是 utf8 的超集,因此當這兩個類型的字符串在作比較的時候,MySQL 內部的操做是,先把 utf8 字符串轉成 utf8mb4 字符集,再作比較。
至關於 select * from trade_detail where CONVERT(traideid USING utf8mb4)=$L2.tradeid.value;
顧名思義,全局鎖就是對整個數據庫實例加鎖。MySQL 提供了一個加全局讀鎖的方法,命令是 Flush tables with read lock (FTWRL)。當須要讓整個庫處於只讀狀態的時候,可使用可使用這個命令,以後其餘線程的如下語句會被阻塞:數據更新語句(數據的增刪改)、數據定義語句(包括建表、修改表結構等)和更新類事務的提交語句。
全局鎖的典型使用場景是,作全庫邏輯備份。也就是把整庫每一個表都 select 出來存成文本。
經過 FTWRL 確保不會有其餘線程對數據庫作更新,而後對整個庫作備份。在備份過程當中整個庫徹底處於只讀狀,這是很危險的。可是不加鎖,備份的數據會有不一致的問題。
能夠拿到一個一致性視圖來備份,官方自帶的邏輯備份工具是 mysqldump。當 mysqldump 使用參數–single-transaction 的時候,導數據以前就會啓動一個事務,來確保拿到一致性視圖。而因爲 MVCC 的支持,這個過程當中數據是能夠正常更新的。
那爲何還須要FTWRL呢,由於一致性讀是好,但前提是引擎要支持這個隔離級別。對於 MyISAM 這種不支持事務的引擎,就須要使用 FTWRL 命令了。
既然要全庫只讀,爲何不使用 set global readonly=true 的方式呢?確實 readonly 方式也可讓全庫進入只讀狀態,但仍是建議用 FTWRL 方式,主要有兩個緣由:
MySQL 裏面表級別的鎖有兩種:一種是表鎖,一種是元數據鎖(meta data lock,MDL)。
表鎖的語法是 lock tables … read/write。與 FTWRL 相似,能夠用 unlock tables 主動釋放鎖,也能夠在客戶端斷開的時候自動釋放。須要注意,lock tables 語法除了會限制別的線程的讀寫外,也限定了本線程接下來的操做對象。
對於 InnoDB 這種支持行鎖的引擎,通常不使用 lock tables 命令來控制併發,畢竟鎖住整個表的影響面仍是太大。
另外一類表級的鎖是 MDL(metadata lock)。MDL 不須要顯式使用,在訪問一個表的時候會被自動加上。MDL 的做用是,保證讀寫的正確性。能夠想象一下,若是一個查詢正在遍歷一個表中的數據,而執行期間另外一個線程對這個表結構作變動,刪了一列,那麼查詢線程拿到的結果跟表結構對不上,確定是不行的。
所以,在 MySQL 5.5 版本中引入了 MDL,當對一個表作增刪改查操做的時候,加 MDL 讀鎖;當要對錶作結構變動操做的時候,加 MDL 寫鎖。
有幾個請求在讀寫表,會加上MDL讀鎖,而後修改表字段的請求會被blocked,請求MDL寫鎖,這個時候,後面的所有讀寫請求都會被MDL寫鎖 blocked,若是查詢語句頻繁,並且客戶端有重試機制,也就是說超時後會再起一個新 session 再請求的話,這個庫的線程很快就會爆滿。
那麼如何安全的給表加字段呢?
首先要解決長事務,事務不提交,就會一直佔着 MDL 鎖。在 MySQL 的 information_schema 庫的 innodb_trx 表中,能夠查到當前執行中的事務。若是要作 DDL 變動的表恰好有長事務在執行,要考慮先暫停 DDL,或者 kill 掉這個長事務。
其次,在 alter table 語句裏面設定等待時間,若是在這個指定的等待時間裏面可以拿到 MDL 寫鎖最好,拿不到也不要阻塞後面的業務語句,先放棄。以後開發人員或者 DBA 再經過重試命令重複這個過程。
ALTER TABLE tbl_name NOWAIT add column ...
ALTER TABLE tbl_name WAIT N add column ...
MyISAM 引擎就不支持行鎖。不支持行鎖意味着併發控制只能使用表鎖,對於這種引擎的表,同一張表上任什麼時候刻只能有一個更新在執行,這就會影響到業務併發度。InnoDB 是支持行鎖的,這也是 MyISAM 被 InnoDB 替代的重要緣由之一。
在 InnoDB 事務中,行鎖是在須要的時候才加上的,但並非不須要了就馬上釋放,而是要等到事務結束時才釋放。這個就是兩階段鎖協議。
若是事務中須要鎖多個行,要把最可能形成鎖衝突、最可能影響併發度的鎖儘可能日後放。
當併發系統中不一樣線程出現循環資源依賴,涉及的線程都在等待別的線程釋放資源時,就會致使這幾個線程都進入無限等待的狀態,稱爲死鎖。這裏用數據庫中的行鎖舉個例子。
這時候,事務 A 在等待事務 B 釋放 id=2 的行鎖,而事務 B 在等待事務 A 釋放 id=1 的行鎖。 事務 A 和事務 B 在互相等待對方的資源釋放,就是進入了死鎖狀態。當出現死鎖之後,有兩種策略:
一種策略是,直接進入等待,直到超時。這個超時時間能夠經過參數 innodb_lock_wait_timeout 來設置。
設置時間長,等待時間太長;設置時間短,有的長事務,不是死鎖的也會結束。
另外一種策略是,發起死鎖檢測,發現死鎖後,主動回滾死鎖鏈條中的某一個事務,讓其餘事務得以繼續執行。將參數 innodb_deadlock_detect 設置爲 on,表示開啓這個邏輯。
每一個新來的被堵住的線程,都要判斷會不會因爲本身的加入致使了死鎖,這是一個時間複雜度是 O(n) 的操做。會耗費大量的CPU資源。
使用 show processlist 命令查看 Waiting for table metadata lock 的示意圖。
這個狀態表示的是,如今有一個線程正在表 t 上請求或者持有 MDL 寫鎖,把 select 語句堵住了。
經過查詢 sys.schema_table_lock_waits 這張表,就能夠直接找出形成阻塞的 process id,把這個鏈接用 kill 命令斷開便可。
經過 sys.innodb_lock_waits 查行鎖
select * from t sys.innodb_lock_waits where locked_table='test'.'t'
G
![]()
這個信息很全,4 號線程是形成堵塞的罪魁禍首。而幹掉這個罪魁禍首的方式,就是 KILL QUERY 4 或 KILL 4。實際上,這裏 KILL 4 纔有效。
MyISAM 引擎把一個表的總行數存在了磁盤上,所以執行 count(*) 的時候會直接返回這個數,效率很高;
InnoDB 引擎就麻煩了,執行 count(*) 的時候,須要把數據一行一行地從引擎裏面讀出來,而後累積計數。由於多版本併發控制(MVCC)的緣由,InnoDB 表「應該返回多少行」也是不肯定的。
count() 是一個聚合函數,對於返回的結果集,一行行地判斷,若是 count 函數的參數不是 NULL,累計值就加 1,不然不加。最後返回累計值。
因此,count(*)、count(主鍵 id) 和 count(1) 都表示返回知足條件的結果集的總行數;而 count(字段),則表示返回知足條件的數據行裏面,參數「字段」不爲 NULL 的總個數。
按照效率排序的話,count(字段) < count(主鍵id) < count(1) < count(*),因此建議,儘可能使用count(*)。
MySQL 會給每一個線程分配一塊內存用於快速排序,稱爲 sort_buffer。
explain 結果裏的 Extra 這個字段中的「Using filesort」表示的就是須要排序。
sort_buffer_size,就是 MySQL 爲排序開闢的內存(sort_buffer)的大小。若是要排序的數據量小於 sort_buffer_size,排序就在內存中完成。但若是排序數據量太大,內存放不下,則不得不利用磁盤臨時文件輔助排序。
創建聯合索引,甚至覆蓋索引,能夠避免排序過程。
直接使用 join 語句,MySQL 優化器可能會選擇表 t1 或 t2 做爲驅動表,改用 straight_join 讓 MySQL 使用固定的鏈接方式執行查詢,這樣優化器只會按照指定的方式去 join。
select * from t1 straight_join t2 on (t1.a=t2.a);
在這條語句裏,被驅動表 t2 的字段 a 上有索引,join 過程用上了這個索引,所以效率是很高的。稱之爲「Index Nested-Loop Join」,簡稱 NLJ。
若是被驅動表 t2 的字段 a 上沒有索引,那每次到 t2 去匹配的時候,就要作一次全表掃描。這個效率很低。這個算法叫作「Simple Nested-Loop Join」的算法,簡稱 BNL。
因此在判斷要不要使用 join 語句時,就是看 explain 結果裏面,Extra 字段裏面有沒有出現「Block Nested Loop」字樣。
在決定哪一個表作驅動表的時候,應該是兩個表按照各自的條件過濾,過濾完成以後,計算參與 join 的各個字段的總數據量,數據量小的那個表,就是「小表」,應該做爲驅動表。
Multi-Range Read 優化,這個優化的主要目的是儘可能使用順序讀盤。由於大多數的數據都是按照主鍵遞增順序插入獲得的,因此能夠認爲,若是按照主鍵的遞增順序查詢的話,對磁盤的讀比較接近順序讀,可以提高讀性能。
select * from t1 where a>=1 and a<=100;
Batched Key Access(BKA) 算法。這個 BKA 算法,其實就是對 NLJ 算法的優化。
NLJ 算法執行的邏輯是:從驅動表 t1,一行行地取出 a 的值,再到被驅動表 t2 去作 join。也就是說,對於表 t2 來講,每次都是匹配一個值。這時,MRR 的優點就用不上了。
既然如此,就把表 t1 的數據取出來一部分,先放到一個臨時內存。這個臨時內存就是 join_buffer。
表定義裏面出現了一個 AUTO_INCREMENT=2,表示下一次插入數據時,若是須要自動生成自增值,會生成 id=2。
實際上,表的結構定義存放在後綴名爲.frm 的文件中,可是並不會保存自增值。
InnoDB 引擎的自增值,實際上是保存在了內存裏,MySQL 8.0 版本後,纔有了「自增值持久化」的能力。
自增值修改機制
若是插入數據時 id 字段指定了具體的值 X ,就直接使用語句裏指定的值 Y。
新的自增值生成算法是:從 auto_increment_offset 開始,以 auto_increment_increment 爲步長,持續疊加,直到找到第一個大於 X 的值,做爲新的自增值。
自增值的修改時機
因此,sql執行報錯了,自增值已經改變了,惟一鍵衝突是致使自增主鍵 id 不連續的第一種緣由。一樣地,事務回滾也會產生相似的現象,這就是第二種緣由。
批量插入的時候,因爲系統預先不知道要申請多少個自增 id,因此就先申請一個,而後兩個,而後四個,直到夠用。這是主鍵 id 出現自增 id 不連續的第三種緣由。
一、主鍵id
再申請下一個 id 時,獲得的值保持不變。因此到最大值以後,再申請id,因爲id不變,因此插入會報主鍵衝突,若是數據量比較大,主鍵id應該用 bigint unsigned。默認是無符號整型 (unsigned int) ,4 個字節232-1(4294967295)。
二、系統row_id
若是建立的 InnoDB 表沒有指定主鍵,那麼 InnoDB 會建立一個不可見的,長度爲 6 個字節的 row_id。InnoDB 維護了一個全局的 dict_sys.row_id 值,全部無主鍵的 InnoDB 表,每插入一行數據,都把當前的 dict_sys.row_id 值做爲要插入數據的 row_id,而後把 dict_sys.row_id 的值加 1。
實際上,在代碼實現時 row_id 是一個長度爲 8 字節的無符號長整型 (bigint unsigned)。可是,InnoDB 在設計時,給 row_id 留的只是 6 個節的長度,這樣寫到數據表中時只放了最後 6 個字節,因此 row_id 能寫到數據表中的值,就有兩個特徵:
248-1到 264 之間,row_id 會是0,264 以後會從0開始。
在 InnoDB 邏輯裏,申請到 row_id=N 後,就將這行數據寫入表中;若是表中已經存在 row_id=N 的行,新寫入的行就會覆蓋原有的行。
覆蓋數據,就意味着數據丟失,影響的是數據可靠性;報主鍵衝突,是插入失敗,影響的是可用性。而通常狀況下,可靠性優先於可用性。
三、Xid
redo log 和 binlog 相配合的時候,提到了有一個共同的字段叫做 Xid。它在 MySQL 中是用來對應事務的。
MySQL 內部維護了一個全局變量 global_query_id,每次執行語句的時候將它賦值給 Query_id,而後給這個變量加 1。若是當前語句是這個事務執行的第一條語句,那麼 MySQL 還會同時把 Query_id 賦值給這個事務的 Xid。
而 global_query_id 是一個純內存變量,重啓以後就清零了。因此就知道了,在同一個數據庫實例中,不一樣事務的 Xid 也是有可能相同的。
可是 MySQL 重啓以後會從新生成新的 binlog 文件,這就保證了,同一個 binlog 文件裏,Xid 必定是唯一的。
可是 global_query_id 定義的長度是 8 個字節,這個自增值的上限是 264-1。理論上也是可能重複的。
四、trx_id
Xid 是由 server 層維護的。InnoDB 內部使用 Xid,就是爲了可以在 InnoDB 事務和 server 之間作關聯。可是,InnoDB 本身的 trx_id,是另外維護的。
InnoDB 內部維護了一個 max_trx_id 全局變量,每次須要申請一個新的 trx_id 時,就得到 max_trx_id 的當前值,而後並將 max_trx_id 加 1。
InnoDB 數據可見性的核心思想是:每一行數據都記錄了更新它的 trx_id,當一個事務讀到一行數據的時候,判斷這個數據是否可見的方法,就是經過事務的一致性視圖與這行數據的 trx_id 作對比。
對於正在執行的事務,能夠從 information_schema.innodb_trx 表中看到事務的 trx_id。
update 和 delete 語句除了事務自己,還涉及到標記刪除舊數據,也就是要把數據放到 purge 隊列裏等待後續物理刪除,這個操做也會把 max_trx_id+1, 所以在一個事務中至少加 2; InnoDB 的後臺操做,好比表的索引信息統計這類操做,也是會啓動內部事務的,所以你可能看到,trx_id 值並非按照加 1 遞增的。
只讀事務會分配一個特殊的,比較大的id,把當前事務的 trx 變量的指針地址轉成整數,再加上 248,使用這個算法,就能夠保證如下兩點:
加上248是爲了保證只讀事務顯示的 trx_id 值比較大,正常狀況下就會區別於讀寫事務的 id。理論狀況下也可能只讀事務與讀寫事務相等,可是沒有影響。
max_trx_id 會持久化存儲,重啓也不會重置爲 0,那麼從理論上講,只要一個 MySQL 服務跑得足夠久,就可能出現 max_trx_id 達到 248-1 的上限,而後從 0 開始的狀況。當達到這個狀態後,MySQL 就會持續出現一個髒讀的 bug。由於後續的trx_id確定比末尾那些trx_id大,能看到這些數據。
五、thread_id
系統保存了一個全局變量 thread_id_counter,每新建一個鏈接,就將 thread_id_counter 賦值給這個新鏈接的線程變量。定義的大小是 4 個字節,所以達到 232-1 後,它就會重置爲 0,而後繼續增長。可是,在 show processlist 裏不會看到兩個相同的 thread_id。由於 MySQL 設計了一個惟一數組的邏輯,給新線程分配 thread_id 的時候,邏輯代碼是這樣的:
do {
new_id= thread_id_counter++;
} while (!thread_ids.insert_unique(new_id).second);
delete 語句誤刪數據行:Flashback工具過閃回把數據恢復回來。 原理是修改 binlog 的內容,拿回原庫重放。而可以使用這個方案的前提是,須要確保 binlog_format=row 和 binlog_row_image=FULL。
如何預防:把 sql_safe_updates 參數設置爲 on。,delete 或者 update 語句必須有where條件,不然執行會報錯。
誤刪庫 / 表:全量備份,加增量日誌,在應用日誌的時候,須要跳過 12 點誤操做的那個語句的 binlog:
若是實例使用了 GTID 模式,就方便多了。假設誤操做命令的 GTID 是 gtid1,那麼只須要執行 set gtid_next=gtid1;begin;commit; 先把這個 GTID 加到臨時實例的 GTID 集合,以後按順序執行 binlog 的時候,就會自動跳過誤操做的語句。
如何加速恢復:使用 mysqlbinlog 命令時,加上一個–database 參數,用來指定誤刪表所在的庫。
在 start slave 以前,先經過執行 change replication filter replicate_do_table = (tbl_name) 命令,就可讓臨時庫只同步誤操做的表;
延遲複製備庫,通常的主備複製結構存在的問題是,若是主庫上有個表被誤刪了,這個命令很快也會被髮給全部從庫,進而致使全部從庫的數據表也都一塊兒被誤刪了。延遲複製的備庫是一種特殊的備庫,經過 CHANGE MASTER TO MASTER_DELAY = N 命令,能夠指定這個備庫持續保持跟主庫有 N 秒的延遲。
好比把 N 設置爲 3600,這就表明了若是主庫上有數據被誤刪了,而且在 1 小時內發現了這個誤操做命令,這個命令就尚未在這個延遲複製的備庫執行。這時候到這個備庫上執行 stop slave,再經過以前介紹的方法,跳過誤操做命令,就能夠恢復出須要的數據。
預防誤刪庫 / 表的方法,制定操做規範。這樣作的目的,是避免寫錯要刪除的表名。
delete 命令其實只是把記錄的位置,或者數據頁標記爲了「可複用」,但磁盤文件的大小是不會變的。也就是說,經過 delete 命令是不能回收表空間的。這些能夠複用,而沒有被使用的空間,看起來就像是「空洞」。
實際上,不止是刪除數據會形成空洞,插入數據也會。若是數據是隨機插入的,就可能形成索引的數據頁分裂。更新索引上的值,能夠理解爲刪除一箇舊的值,再插入一個新值。不難理解,這也是會形成空洞的。
也就是說,通過大量增刪改的表,都是多是存在空洞的。因此,若是可以把這些空洞去掉,就能達到收縮表空間的目的。而重建表,就能夠達到這樣的目的。
使用 alter table A engine=InnoDB 命令來重建表。MySQL 會自動完成轉存數據、交換表名、刪除舊錶的操做。
重建表的時候,InnoDB 不會把整張表佔滿,每一個頁留了 1/16 給後續的更新用。也就是說,其實重建表以後不是「最」緊湊的。
一、mysqldump 方法
使用 mysqldump 命令將數據導出成一組 INSERT 語句。你可使用下面的命令:
mysqldump -h$host -P$port -u$user --add-locks=0 --no-create-info --single-transaction --set-gtid-purged=OFF db1 t --where="a>900" --result-file=/client_tmp/t.sql
而後能夠經過下面這條命令,將這些 INSERT 語句放到 db2 庫裏去執行。
mysql -h127.0.0.1 -P13000 -uroot db2 -e "source /client_tmp/t.sql"
二、導出 CSV 文件
直接將結果導出成.csv 文件。MySQL 提供了下面的語法,用來將查詢結果導出到服務端本地目錄:
select * from db1.t where a>900 into outfile '/server_tmp/t.csv';
而後用下面的 load data 命令將數據導入到目標表 db2.t 中。
load data infile '/server_tmp/t.csv' into table db2.t;
三、物理拷貝方法
直接拷貝文件是不行的,須要在數據字典中註冊。
MySQL 5.6 版本引入了可傳輸表空間(transportable tablespace) 的方法,,能夠經過導出 + 導入表空間的方式,實現物理拷貝表的功能。