MySQL 常問知識點(簡略)

前言

稍微整理一下吧, 因爲平時關注這方面不多, 所以不會很深入

事務隔離級別

事務中可能遇到的問題

髒讀

同一時間有兩個事務 A 和 B, A 對某條數據進行修改或增加, 在 B 中也體現出來, 但是 A 可能回滾, 導致 B 獲取的數據可能是髒的

可重複讀

有事務 A, 在 A 開始後, 數據庫中的某些數據發送變更, 但是在 A 中讀取相關數據永遠保持在 A 剛啓動的時候不會自己更改, 通常針對數據的更新

不可重複讀

有事務 A, 在 A 開始後, 數據庫中的某些數據發送變更, 而 A 事務中查詢到的相關數據也會隨之更改, 通常針對數據的更新

幻讀

事務A對某些行的內容作了更改,但是還未提交,此時事務B插入了與事務A更改前的記錄相同的記錄行,並且在事務A提交之前先提交了,而這時,在事務A中查詢,會發現好像剛剛的更改對於某些數據未起作用,但其實是事務B剛插入進來的,讓用戶感覺很魔幻,感覺出現了幻覺,這就叫幻讀。

事務隔離級別

讀未提交

不加鎖, 速度快, 但是不加鎖會導致 髒讀/不可重複讀/幻讀 的問題出現

讀提交

一個事務只能督導其他事務已經提交過的數據

可能出現 不可重複讀/幻讀 的問題

可重複讀(默認)

可重複讀是 MySQL 的默認事務隔離級別

在事務啓動之後, 對數據的所有改動都不會體現到事務中, 因此對數據的新增也不會出現在其中

可重複讀可能出現 幻讀 的問題

串行化

是最高的隔離級別, 會在事務過程中涉及到的數據都加上行鎖, 多事務串行執行, 效率很堵但是安全, 同時不存在數據的不一致問題

InnoDB 和 Myisam 的區別

  • InnoDB 支持事務, Myisam 不支持
  • InnoDB 支持行級鎖, Myisam 只支持表級
  • InnoDB 支持外鍵, Myisam 不支持
  • InnoDB 是索引組織表, Myisam 是堆表

默認爲 InnoDB

慢查詢

時間

10s, 但是使用 GORM 這種當大於200ms 就會有調試日誌輸出

查找問題

  • Explain: 可以輸出 MySQL 內部是如何使用索引來處理查詢語句的, 可以作爲找慢查詢的原因時使用
  • 視圖: 創建視圖來查看

解決方法

  • 數據量過大考慮分表
  • 查看是否查詢了額外的數據, 對語句進行分析和重寫
  • 查看是否命中了索引, 優化表結構
  • 如果每次都是重複的查詢數據, 考慮加入緩存
  • 不需要查詢出的列數據, 進行 sql 優化
  • 查詢不需要的數據, 使用LIMIT進行跳過

drop/delete/truncate 的區別

drop

直接刪除表

truncate

清空表數據, 當再次新建時id 從1開始

delete

刪除表裏的數據, 可以加where來進行篩選

連接種類

  • 全連接: join/inner join
  • 左連接: left join
  • 右連接: right join
  • 交叉連接: cross join

三範式

1NF

作爲關係型數據庫, 必須要有主鍵, 每一個字段必須保持原子性, 不能再繼續分割

2NF

作爲關係型數據庫, 所有非主鍵字段完全的依賴他的主鍵

3NF

所有的非主鍵字段和他的主鍵字段之間不能產生傳遞依賴, 必須是直接依賴關係, 傳遞依賴可以再啓用一張表來做關聯

數據庫優化方法

創建高性能索引

根據業務來設計符合要求的索引

優化查詢性能

設計表時要充分考慮表結構和字段

查詢時儘量命中索引

優化服務器設置

適當的修改數據庫配置

負載均衡

存儲過程和觸發器

存儲過程

將重複度高的 sql 語句預先存儲到數據庫中, 調用時通過填充數據來執行 sql, 以提高效率

觸發器

觸發器顧名思義, 就是在數據庫中設置某個條件時自動觸發某些 sql, 因爲會導致效率變慢, 尤其是多條命中時, 所以一般不使用

鎖與死鎖

本數據庫有三種鎖(InnoDB)

表級鎖

表級鎖的使用很少, 因爲他會將整個表鎖住, 效率低, 同時因爲將整個表鎖住, 同一時間也就不會有第二個事務進行修改操作, 避免出現死鎖問題

行級鎖

數據庫本身並不支持行級鎖, InnoDB 是通過引擎層自己實現的, 行級鎖的效率更高, 但是可能會出現死鎖的情況, 在 InnoDB 中, 行鎖在需要對某條數據進行修改時加上, 在事務結束時纔會釋放掉, 而如果有兩個事務以兩個順序對多個數據進行加鎖, 就會導致死鎖, 例如

事務1 事務2
update t set k=k+1 where id = 1 (加鎖行1)
update t set k=k+1 where id = 2 (加鎖行2)
update t set k=k+1 where id = 2 (等待行鎖2釋放)
update t set k=k+1 where id = 1 (等待行鎖1釋放)

我們在對數據庫操作時, 應該儘可能的考慮到鎖衝突問題, 遇到事務必須慎重, 如果有可能加行鎖的操作, 可以考慮以下幾種方式規避

  • 儘量的將加行鎖操作放到事務後面
  • 對於事務中對多個表進行處理時, 約定好使用一致的處理順序
  • 以批方式處理多個數據時, 先對數據進行排序
  • 可以控制事務的併發數量
  • 將鎖提升爲排他鎖, 使用 for update 語句
  • 將鎖提升爲表鎖, 例如在更新時更新條件沒有索引會自動加表鎖

頁面鎖

存儲引擎爲 BerkeleyDB 纔會出現, 沒有使用過

索引

索引並不是越多越好

索引優點: 加快查詢速度(命中索引的前提下)

索引缺點: 索引需要佔據一定的空間和資源, 同時索引會加重插入/刪除/修改數據的負擔

索引可以增加數據的查找速度, 索引是在引擎層進行實現的, 本數據庫提供四種索引:

  • B-Tree: 最常見的索引類型, 大部分引擎都支持
  • HASH: Memory 支持, 使用場景簡單
  • R-Tree: 空間索引, MyISAM 獨有, 常見用於地理空間數據類型
  • Full-text: 全文索引, MyISAM 獨有

對於引擎對索引類型的支持如下

索引 MyISAM InnoDB Memory
B-Tree Y Y Y
HASH N N Y
R-Tree Y N N
Full-Text N Y(5.6+) N

索引類型

普通索引

最基本的索引類型, 沒有唯一性之類的限制

UNIQUE(唯一索引)

唯一的, 不允許重複的索引

複合索引

多個列當做一個索引, 用於組合搜索, 效率比索引合併快

主鍵(特殊)

唯一的, 每個表只能有一列主鍵

單列索引和聯合索引

單列索引

指的就是爲單獨的某個列設置索引

聯合索引

將多個列合到一起作爲一個索引, 創建聯合索引等於創建了從左到右的多個索引. 例如: 創建聯合索引a+b+c 等於創建了三個索引, 分別是 單列索引 a, 聯合索引a+b, 聯合索引 a+b+c

索引合併

當查詢語句的WHEREAND的時候, 如果有多個索引, 會自動查找一個最佳的索引使用

而當查詢語句通過OR連接時, MySQL會使用索引合併技術, 將命中的幾個索引的範圍掃描合併成一個新的索引(只限於單表), 如果數據量不大, 不會將索引合併, 因爲會影響一部分效率

什麼時候需要索引

  • 頻繁的作爲查詢條件的字段應該創建索引
  • 唯一性很差的字段不適合作爲單獨索引, 即使他很頻繁的作爲查詢條件(例如用戶表中的性別字段這種重複性很高的)
  • 更新非常頻繁的字段不適合創建索引
  • 不會出現在WHERE中的字段不應該創建索引
  • 數據量比較大, 比如表中的數據量大於 1w 條
  • 索引選擇性高(不重複的索引值/表記錄數)

選擇索引

  • WHERE子句中出現的列, 在join子句中出現的列可以設置索引
  • 索引列的基數越大, 效果越好
  • 如果一個列長度爲200, 而前10個字符是多數唯一的, 可以考慮對這個列的前10個字符做短索引, 節省索引空間, 也可以提高查詢速度
  • 牢記索引的最左前綴原則(詳見下條)

爲什麼 MySQL 索引使用 B+樹

B-tree

B 樹不管是葉子結點還是非葉子節點, 都會保存數據, 會導致在非葉子節點中可以保存的指針數量變少了, 指針少的時候要保存大量的數據, 增加樹的高度, 導致 IO 操作變多了, 降低查詢性能.

Hash

Hash 沒有順序, 查詢的 IO 複雜度高

二叉樹

二叉樹的高度不均勻, 不能自平衡, 查找效率跟數據相關, 數據越大樹越高, IO 的代價就很高

紅黑樹

數據量越大樹的高度越高, IO 的代價很高

平衡二叉樹

平衡二叉樹的深度很大, 因爲平衡二叉樹一個節點最多兩個子節點, 導致同樣的數據量, 平衡二叉樹的深度比 B+tree 的要深很多, 導致查詢會變慢.

最左前綴

MySQL 一條查詢只能使用一個索引, 當出現多個索引時, 會使用範圍最小的那個索引

InnoDB使用 B+ 樹進行數據存儲, 所以要想命中索引, 必須按照聯合索引當初建立時的順序來, 不然無法命中索引, 另外, 範圍查詢(<, >)也不命中索引

比如說有表 user, 下面有字段 name 是索引

一條語句 SELECT * FROM user WHERE name='ak' AND age=15 分兩步

  1. 從表中尋找 nameak 的數據
  2. 再從結果中尋找 age15的數據

因爲name爲索引, 所以步驟1很快速完成, 但是步驟2就沒有索引, 可以優化

如果這一條語句是很頻繁的查詢, 可以將nameage合併使用聯合索引, 提高這個語句的查詢效率

於是我們將索引修改爲name 和 age, ALTER TABLE user ADD INDEX name_age (name, age)

因爲你在設置聯合索引時, 總歸有個先後, 例如本例就是name+age, 而MySQL的最左查詢會從最左開始解析, 那麼過程如下

重新執行語句 SELECT * FROM user WHERE name='ak' AND age=15

這樣就會將聯合索引生效, 提高效率, 他的執行如下

  1. 找到name='ak',命中了namename+age
  2. 找到age=15, 命中了name+age索引

但是如果我們更改WHERE的順序爲

SELECT * FROM user WHERE age=15 AND name='ak' 則無法命中索引, 這是爲什麼呢?

  1. 找到age=15, 發現索引只有namename+age, 因爲沒有以 age開頭的, 所以本條沒有命中索引
  2. 找到name='ak' 沒有索引

所以就沒有命中索引

數據庫中存在索引但是沒有使用

IN

IN會掃描全表, 慎重使用

NOT IN

IN一樣會掃描全表, 考慮使用 not exists或者其他方式規避

<>

不等於符號, 考慮修改語句, 比如a<>0修改爲a>0 or a<0

IS NULLIS NOT NULL

判斷字段是否爲空, 一般不會應用索引, b 數索引不索引空值, 考慮修改語句, 比如a IS NOT NULL修改爲a>0

命中的數據大於總數50%

命中索引但是命中的數據數量超過了表內數據總數量的50%, 也會直接全表掃描, 因爲此時全表掃描比索引快(大概率是索引的設計有問題)

LIKE

LIKE操作符可以匹配通配符查詢, 使用%可以查詢幾乎任意範圍的匹配, 注意, 以%開頭則不會引用索引, 比如%TEXT%不會引用索引, 而TEXT%可以引用範圍索引

UNION

UNION用來將多個SELECT查詢出的結果集進行合併. 因爲是將多個結果進行合併, 也不會走索引, 但是因爲其運行時是先取出若干個結果, 再去重, 因此可能會佔用大量的資源, 不推薦使用

怎麼判斷是否命中索引

在 sql 前加EXPLAIN, 比如

EXPLAIN SELECT * FROM user WHERE age=15 AND name='ak'

然後查看輸出的字段數據來判斷

分庫分表

什麼是分庫分表

分庫

從單個數據庫拆分成多個數據庫, 將數據分散在多個數據庫中

分表

從單張表拆分成多張表, 將數據散落在多張表中

爲什麼要分庫分表

提升性能, 增加可用性

分庫分表的前提是負載已經大到基本的數據庫結構優化和緩存等機制都使用了還是有效率問題

由於分庫分表會增加邏輯複雜性, 因此不到萬不得已不推薦使用

提升性能

數據量越來越大, 數據庫的查詢 QPS 就越來越高, 數據庫的壓力和讀寫需要的時間也會越來越多, 業務效率的瓶頸就會變成數據庫這裏, 因此就需要對數據庫進行優化

如果數據庫的 QPS 過高了, 就需要考慮進行拆庫, 通過分庫來分擔單個數據庫的連接壓力. 一般的, 單庫的連接數最好不要超過1000

如果單表的數據超過了一定量級, 對這個表進行操作, 速度就會變慢(阿里的<<JAVA 開發手冊>>提到每張表的數據不要超過500萬行或者容量超過2G). 此時需要對錶按照某個規則進行切分, 分爲多個表, 來減少每個表的數據量, 恢復性能.

提高可用性

雞蛋不能放在一個籃子裏, 單個數據庫如果發生了意外, 很有可能會丟失所有數據, 因此, 除了考慮使用 主從 等多節點部署之外, 也可以考慮拆分數據來解決問題, 比如我們的數據庫宕機了, 那麼:

  • 單庫部署的情況下, 數據庫宕機, 會影響100%的數據, 而且數據恢復的耗時可能也很久
  • 如果我們拆分成2個庫, 部署在不同的機器, 如果其中一個宕機, 故障影響就是50%, 還有50%的數據可以繼續提供服務, 同時恢復耗時受數據量影響也會縮短

需要注意, 拆庫並不是無限制的, 這是犧牲了資源來提升性能和可用性, 要取捨, 畢竟資源總是有限的

怎樣分庫分表

三種切分方案

從上一節總結, 切分方案分爲三類

切分方案 解決的問題
只分庫 庫讀寫 QPS 過高, 連接數不足了
只分表 單表數據量過大, 性能瓶頸
分庫+分表 連接數不足+數據量過大

選擇切分方案

關於系統架構, 都有一個共識就是避免過度設計, 只有確定業務量數據巨量, 或者真的遇到了瓶頸, 再考慮數據切分

分表: 可以根據 500w 行的標準按照邏輯分割進行拆分, 例如, 業務每個月插入的數據爲400w 行, 就應該按照每月來進行拆分

分庫: 可以按照每庫1000個數據庫連接按照邏輯進行拆分

如何切分

水平切分: 按照業務維度進行橫向的切分, 也就是按行切分, 例如將用戶表按照用戶等級進行拆分, 將訂單表按照創建月份進行拆分等等

垂直切分: 按照字段進行切分, 比如說訂單表可能存在訂單信息, 賣家買家信息, 支付信息, 可以通過字段拆分成三張表, 訂單/買家/賣家 來減少容量

讀寫分離

讀寫分離也是爲了提高速度和可用性

主從複製

搭建在多臺服務器上的數據庫系統, 將其中一臺當做主數據庫, 其他爲從數據庫, 實現主從同步. 其中主數據庫負責進行寫操作, 從數據庫負責讀操作, 這就將請求分流爲多個部分, 來增加訪問速度, 同時其中一臺崩潰也避免了數據發生丟失

這裏也說了, 讀取是從服務器, 寫入是主服務器, 那麼他們是不同的物理服務器, 怎麼實現的數據一致呢?

我們將主服務器命名爲 A, 從服務器有一個爲 B, 當用戶在A插入數據時, 流程如下:

  1. 修改A本地的數據
  2. 將修改記錄寫入A的日誌系統
  3. 發送給B
  4. B寫入B的日誌系統
  5. BB的日誌系統中讀取記錄, 修改自己的數據

主從同步複製有三種方式:

  • 同步複製: 用戶寫入A, A監控到從數據庫B也修改完成後才返回成功
  • 異步複製(默認): 只要A自己成功就返回成功
  • 半同步複製: A自己和若干個從服務器中有一個成功就返回成功

讀寫分離

開源方案

讀寫分離有幾個開源方案, 目前還活着的就是 mariadb-corporation/MaxScale:一個智能數據庫代理。 (github.com), 雖然是 MariaDB出的, 但是也兼容 MySQL

當配置完成後, 所有數據庫連接都設定爲 MaxScale 的連接, MaxScale自己進行管理和分配即可, 還有 web 頁面可以查看信息, 而且對於調用者來講是無痛的, 開發者不需要關注裏面的實現邏輯, 跟普通調用單機一樣

自己控制

當然你也可以使用直連主從, 通過邏輯來進行讀寫分配, 只是比較 low, 優點是部署方便

什麼是 MySQL 的 XA 事務

在分佈式事務處理中, 遇到一個事務跨越了多張表, 就需要使用XA事務來完成整個事務的正確提交和回滾, 保證全局事務的一致性.

需要提前說明的是, 對於分佈式數據庫架構來講, 都會有一個總控, 來接收用戶的操作, 並通過一定的規則將其分配到具體的某個節點中

XA 事務的過程

例如, 事務的整體 SQL 如下

begin;
insert into `user` values("user1", 18);
update `user` set `age`=19 WHERE `name` = "user101";
commit;

根據步驟來解答

  1. 總控收到begin, 知道要開始事務
  2. 總控收到insert語句, 解析語句, 根據name的值, 計算出應該是分配到哪個節點中, 這裏假設是節點1
  3. 總控向節點1發送語句xa start xid1開啓一個xa事務, 這裏的xid是總控生成的一個全局事務 id, 同時也將insert 語句發送到節點1
  4. 總控收到 update 語句, 同樣的, 先根據name計算出數據保存的節點, 這裏假設是節點2
  5. 此時總控會先發送xa start xid1, 因爲都是屬於一個事務. 所以這裏使用相同的xid, 同時會向節點2發送 原來的 insert 和新的 update 兩個語句. (這裏爲什麼將不屬於他節點的 insert 發送過去, 之後會說)
  6. 總控接受到了commit, 標識這個事務已經結束了, 準備提交
  7. 總控向節點1節點2發送xa end xid1; xa prepare xid1語句, 告訴節點準備提交, 如果數據正常的走完, 那麼節點會回覆成功, 如果任何一個節點返回失敗, 則向節點1節點2發送xa rollbak xid1 進行事務的回滾
  8. 如果都返回成功, 總控會想節點1節點2發送xa commit xid1 最終提交事務

MySQL5.6上 XA 事務的衝突問題

試想一下這樣的場景

  1. 總控已經向節點1節點2發送完了xa prepare xid1, 並且得到了成功的回覆
  2. 總控向節點1發送xa commit xid1 , 並且成功了
  3. 總控向節點2發送xa commit xid1, 此時因爲網絡問題, 節點2出現了問題丟失了與總控的通訊
  4. 當網絡恢復時, 或者節點2上線了, 此時 xa 的事務已經回滾了, 當總控 commit 時, 數據庫實例已經找不到xid1這個事務

這裏的問題是, xa prepare 沒有嚴格的持久化, 當連接斷開時, 這些事務會被回滾, 造成了數據丟失

MySQL5.7 的 XA 可靠性改進

MySQL5.7解決了這個問題, 在連接斷開時這些數據會持久化保存下來

SQL 的四種語言

DDL

數據庫定義語言

  • CREATE(創建)
  • ALTER(修改)
  • DROP(刪除)
  • TRUNCATE(清除)
  • COMMENT(註釋)
  • RENAME(重命名)

DML

數據操縱語言

  • SELECT(查詢)
  • INSERT(新增)
  • UPDATE(更新)
  • DELETE(刪除)
  • MERGE(合併)
  • CALL(存儲過程調用)
  • EXPLAIN PLAN(性能分析)
  • LOCK TABLE(鎖表)

DCL

數據庫控制語言

  • GRANT(授權)
  • REVOKE(取消授權)

TCL

事務控制語言

  • SAVEPOINT(設置保存點)
  • ROLLBACK(回滾)
  • SET TRANSACTION(設置實務)

MySQL 建表的約束條件

  • 主鍵約束: 唯一, 非空
  • 唯一約束: 唯一, 可空, 但是隻能有一個空
  • 檢查約束: 列數據的 範圍/格式 進行限制
  • 默認約束: 列數據的默認值
  • 外鍵約束: 兩表間的關係連接

MySQL 執行查詢的過程

  1. 客戶端通過 TCP 連接發送查詢請求到 MySQL 的連接器, 連接器進行權限驗證和資源分配
  2. 查詢是否存在緩存, 如果命中了緩存, 直接返回結果
  3. 分析和校驗語法是否正確
  4. 優化語句, 處理是否使用索引, 生成執行計劃
  5. 將執行計劃提交給執行器, 將數據保存到結果集中, 同時將數據緩存在緩存中, 將結果返回給客戶端

varcharchar區別

效率上char>varchar

如果確定某個字段的值長度, 可以使用char, 否則使用varchar, 例如md5這種明確知道長度的就是char更好

char

定長字段, 在數據庫設定時就確定了值的大小,

varchar

不定長字段, 申請了最大長度, 而實際上佔用的長度可能比最大長度小, 視值的真正長度而定, 實際佔用的長度爲(值長度+1, 最後1個字符表示本值長度)

存儲過程的優缺點

存儲過程經過預編譯成了代碼塊, 執行效率比較高, 調用也比較方便

但是因爲國內互聯網的環境一般是尋求很快的迭代, 項目的生命週期往往較短, 人員的流動也比較大, 對於存儲過程的管理繁瑣且複雜, 複用性也沒用把代碼寫在服務層那麼好, 所以阿里的<<Java 開發手冊>>也禁止使用存儲過程.

MySQL 的事務日誌

Innodb 的事務日誌包含兩部分, redo logundo log

redo log

redo log是爲了事務的持久化而出現的 log, 當事務執行過程中, 將執行的任務寫入redo中, 當有故障發生時, MySQL 重啓後, 根據redo中的數據進行重做, 防止事務出現問題

undo log

undo是爲了將記錄回滾到某個版本, 事務在未提交之前, 保存了未提交之前的版本數據, 方便事務回滾時對數據進行回覆

MySQL 的 binlog

binlog存儲了所有數據庫的結構變更和表內的數據變更的二進制日誌. 記住是變更, 對數據的查詢並不會記錄在其中. 數據庫的所有記錄可以查看數據庫的通用日誌

事務中可以混合使用存儲引擎嗎

最好不要, 可以正常提交, 但是因爲事務是由引擎實現的, 在回滾時可能因爲數據不一致問題無法回滾

數據量很大的表如何優化查詢速度

  • 考慮分表
  • 優化索引
  • 使用redis進行數據緩存
  • 使用 MySQL 緩存
  • 主從複製, 讀寫分離
  • 從業務上限制查詢的範圍
  • 從業務上規定查詢必須指定條件

數據量很大的錶針對很大的分頁處理

例如, 有表user, 每頁10條數據, 用戶查詢到了第100000頁, sql 語句類似於

SELECT * FROM `user` WHERE age>18 LIMIT 1000000, 10

因爲 MySQL 分頁查詢不是跳過前1000000條數據, 而是讀取到1000010 條數據, 所以導致這個查詢非常的慢

可以將 sql 修改爲

SELECT * FROM `user` WHERE id in (SELECT id FROM `user` WHERE age>18 LIMIT 1000000, 10)

這樣雖然也查詢到了1000010條數據, 但是因爲索引覆蓋了查詢, 導致速度提升

或者說在業務中對超大的分頁進行處理, 比如推測用戶的需求是可能會點擊下一頁, 將下一頁數據提前查詢並存儲到緩存中等

關聯查詢的優化

確定ONUSING子語句中是否含有索引

確保GROUP BYORDER BY 只有一個表中的列, 不然不會使用索引

數據庫結構優化

  • 如果表中有很多列, 考慮將表切分
  • 對於經常聯合查詢的表, 考慮使用中間表來連接提高查詢效率(將原來的聯合查詢修改爲對中間表的查詢)
  • 合理的加入冗餘字段(慎重)

回表

回表查詢指的是, 需要掃描兩次索引樹, 效率比掃描一次索引樹更低

如果查詢條件爲普通索引(輔助索引), 則需要先查找一遍輔助索引樹, 得到對應的簇集索引鍵(主鍵), 再去簇集索引樹中查找對應記錄, 發生回表

普通索引指的是非主鍵索引

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章