MySQL 索引使用的注意事項
- 更新頻繁的列不應設置索引
- 數據量小的表不要使用索引
- 重複數據多的字段不應設爲索引(比如性別,只有男和女,一般來說:重複的數據超過百分之15就不該建索引)
- 首先應該考慮對where 和 order by 涉及的列上建立索引
說說分庫與分表設計
垂直分表
:垂直分表在日常開發和設計中比較常見,通俗的說法叫做“大表拆小表”,拆分是基於關係型數據庫中的“列”(字段)進行的。通常情況,某個表中的字段比較多,可以新建立一張“擴展表”,將不經常使用或者長度較大的字段拆分出去放到“擴展表”中
。在字段很多的情況下,拆分開確實更便於開發和維護(筆者曾見過某個遺留系統中,一個大表中包含100多列的)。某種意義上也能避免“跨頁”的問題(MySQL底層都是通過“數據頁”來存儲的,“跨頁”問題可能會造成額外的性能開銷),拆分字段的操作建議在數據庫設計階段
就做好。如果是在發展過程中拆分,則需要改寫以前的查詢語句,會額外帶來一定的成本和風險,建議謹慎。垂直分庫
:垂直分庫在“微服務”盛行的今天已經非常普及了。基本的思路就是按照業務模塊來劃分出不同的數據庫
,而不是像早期一樣將所有的數據表都放到同一個數據庫中。系統層面的“服務化”拆分操作,能夠解決業務系統層面的耦合和性能瓶頸,有利於系統的擴展維護。而數據庫層面的拆分,道理也是相通的。與服務的“治理”和“降級”機制類似,我們也能對不同業務類型的數據進行“分級”管理、維護、監控、擴展等。水平分表
:水平分表也稱爲橫向分表,比較容易理解,就是將表中不同的數據行按照一定規律分佈到不同的數據庫表中,這些表保存在同一個數據庫中,這樣來降低單表數據量,優化查詢性能
。最常見的方式就是通過主鍵或者時間等字段進行Hash和取模後拆分。水平分表,能夠降低單表的數據量,一定程度上可以緩解查詢性能瓶頸。但本質上這些表還保存在同一個庫中,所以庫級別還是會有IO瓶頸。所以,一般不建議採用這種做法。水平分庫
:水平分庫分表與上面講到的水平分表的思想相同,唯一不同的就是將這些拆分出來的表保存在不同的數據中
。這也是很多大型互聯網公司所選擇的做法。某種意義上來講,有些系統中使用的“冷熱數據分離”(將一些使用較少的歷史數據遷移到其他的數據庫中。而在業務功能上,通常默認只提供熱點數據的查詢),也是類似的實踐。在高併發和海量數據的場景下,分庫分表能夠有效緩解單機和單庫的性能瓶頸和壓力,突破IO、連接數、硬件資源的瓶頸。當然,投入的硬件成本也會更高。同時,這也會帶來一些複雜的技術問題和挑戰(例如:跨分片的複雜查詢,跨分片事務等)。
分庫與分錶帶來的分佈式困境與應對之策
- 事務問題:使用分佈式事務。
- 查詢問題:使用匯總表。
- ID唯一性
- 使用全局唯一 ID:GUID。
- 爲每個分片指定一個 ID 範圍。
- 分佈式 ID 生成器。
說說 SQL 優化之道
- 負向條件查詢不能使用索引,
像“!=”、“not in”、“not exists”都不是好習慣,可以用 “in” 查詢代替
。 - 前導模糊查詢
“like '%xxx'” 不能使用索引
,後導模糊查詢可以使用前綴索引。 - 數據區分度不大的字段不宜使用索引,比如性別。
- 調用函數之後的字段不能使用索引,例如
select from order where YEAR(date) < = '2020'
。 - 如果業務大部分是單條查詢,使用Hash索引性能更好。因爲 B-Tree 索引的時間複雜度是 O(log(n)),Hash 索引的時間複雜度是 O(1)。
- 如果明確知道只有一條結果返回,
limit 1 能夠提高效率
。 - 把計算放到業務層而不是數據庫層,節省數據庫的CPU。
如果只返回部分需要的列,不要使用 “select *”
,能夠大大的節省數據傳輸量,與數據庫的內存使用量。
MySQL 遇到的死鎖問題
- 死鎖場景描述:用戶A訪問表T1(鎖住了表T1),然後又試圖訪問表T2;用戶B訪問表T2(鎖住了表T2),然後試圖訪問表T1;這時用戶A由於用戶B已經鎖住了表T2,必須等待用戶B釋放T2的鎖才能進行下一步,而用戶B也需要等待用戶A釋放T1的鎖才能進行下一步,於是產生了死鎖。
- 這種死鎖比較常見,是由於程序的BUG產生的。仔細分析程序的邏輯,對於數據庫的多表操作時,
儘量按照相同的順序進行處理,儘量避免同時鎖定兩個資源
,如操作A和B兩張表時,總是按先A後B的順序處理,必須同時鎖定兩個資源時,要保證在任何時刻都應該按照相同的順序來鎖定資源。
存儲引擎的 InnoDB 與 MyISAM
對比項 | MyISAM | InnoDB |
---|---|---|
主外鍵 | 不支持 | 支持 |
事務 | 不支持 | 支持 |
表級鎖和行級鎖 | 表鎖,即使操作一條記錄也會鎖住整個表,不適合高併發操作 | 行鎖,操作時只鎖住某一行,不對其他行有影響,適合高併發操作 |
緩存 | 只緩存索引,不緩存真實數據 | 都緩存,對內存要求較高,而且內存大小對性能有決定性的影響 |
表空間 | 小 | 大 |
關注點 | 性能 | 事務 |
數據庫索引的原理
- 索引的目的:加快查詢速度。
- 索引的數據結構:哈希、有序數組、B-Tree
爲什麼要用 B-Tree
- 對比 二叉樹 索引,樹高不高,所以減少了IO磁盤的讀取,從而提高了性能。
- 對比 Hash 索引,Hash索引不支持範圍查詢、排序查詢。而且如果相同Hash索引值相同的數據太多的話,性能並不一定會比B-Tree高。
- 對比 有序數組 索引,
聚集索引與非聚集索引的區別
- 聚集索引即聚簇索引,
葉子結點存的直接就是數據
,InnoDB的主鍵索引就是聚簇索引。 - 非聚簇索引,葉子結點存的是主鍵的值,非聚簇索引查詢所有數據需要先查詢主鍵的值,再
回表查詢回到主鍵索引查詢所有數據
。
limit 20000 加載很慢怎麼解決
- limit 20000 一次從磁盤加載到內存的數據頁太多,會造成查詢很慢的情況。
- 可以按照主鍵排序,一次查詢1000條,像這樣:
select * from T order by id where id > {id} limit 1000
。 - 其中 id 一開始取值 0,後面都取前一次查詢 id 的最大值,連續查詢20次,就可以查詢前20000行數據了。
選擇合適的分佈式主鍵方案
- 可以使用分佈式 Redis 生成主鍵 ID,Redis 是單線程的可以保證生成唯一 ID,可以利用 Redis 原子操作 INCR 和 INCRBY 來實現。
選擇合適的數據存儲方案
- 關係型數據庫 MySQL
- 內存鍵值型數據庫 Redis
- 文檔數據庫 MongoDB
ObjectId 規則
- ObjectId 使用
12字節
的存儲空間,是一個由24個16進制數字組成
的字符串。示例:d98f7w6gryth4r68yr65tey4
前四位
是時間戳,可以提供秒級別的唯一性。接下來三位
是所在主機的唯一標識符,通常是機器主機名的散列值。接下來兩位
是產生 ObjectId 的 PID,確保同一臺機器上併發產生的 ObjectId 是唯一的。前九位
保證了同一秒鐘不同機器的不同進程產生的 ObjectId 時唯一的。最後三位
是自增計數器,確保相同進程同一秒鐘產生的 ObjectId 是唯一的。
聊聊 MongoDB 使用場景
網站實時數據
:mongoDB非常適合實時的插入,更新與查詢,並具備網站實時數據存儲所需的複製及高度伸縮性。數據緩存
:由於性能很高,MongoDB 也適合作爲信息基礎設施的緩存層。在系統重啓之後,由MongoDB搭建的持久化緩存層可以避免下層的數據源過載。大尺寸、低價值數據存儲
:使用傳統的關係型數據庫存儲一些數據時可能會比較昂貴,在此之前,很多時候程序員往往會選擇傳統的文件進行存儲。高伸縮性場景
:MongoDB 非常適合由數十或數百臺服務器組成的數據庫。MongoDB 的路線圖中已經包含對 MapReduce 引擎的內置支持。對象或 JSON 數據存儲
:MongoDB 的 BSON 數據格式非常適合文檔化格式的存儲及查詢。
倒排索引(×)
聊聊 ElasticSearch 使用場景
- ElasticSearch是一個
分佈式,高性能、高可用、可伸縮的搜索和分析系統
。 - 分佈式的搜索引擎和數據分析引擎。