1.數據存儲
1) MySQL 索引使用的注意事項
(1)。索引不會包含有NULL的列
只要列中包含有NULL值,都將不會被包含在索引中,復合索引中只要有一列含有NULL值,那么這一列對于此符合索引就是無效的。
(2)。使用短索引
對串列進行索引,如果可以就應該指定一個前綴長度。例如,如果有一個char(255)的列,如果在前10個或20個字符內,多數值是唯一的,那么就不要對整個列進行索引。短索引不僅可以提高查詢速度而且可以節省磁盤空間和I/O操作。
(3)。索引列排序
mysql查詢只使用一個索引,因此如果where子句中已經使用了索引的話,那么order by中的列是不會使用索引的。因此數據庫默認排序可以符合要求的情況下不要使用排序操作,盡量不要包含多個列的排序,如果需要最好給這些列建復合索引。
(4).like語句操作
一般情況下不鼓勵使用like操作,如果非使用不可,注意正確的使用方式。like ‘%aaa%’不會使用索引,而like ‘aaa%’可以使用索引。
(5)。不要在列上進行運算
(6)。不使用NOT IN 、《》、!=操作,但《,《=,=,》,》=,BETWEEN,IN是可以用到索引的
(7)。索引要建立在經常進行select操作的字段上。
這是因為,如果這些列很少用到,那么有無索引并不能明顯改變查詢速度。相反,由于增加了索引,反而降低了系統的維護速度和增大了空間需求。
(8)。索引要建立在值比較唯一的字段上。
(9)。對于那些定義為text、image和bit數據類型的列不應該增加索引。因為這些列的數據量要么相當大,要么取值很少。
(10)。在where和join中出現的列需要建立索引。
(11).where的查詢條件里有不等號(where column != …),mysql將無法使用索引。
(12)。如果where字句的查詢條件里使用了函數(如:where DAY(column)=…),mysql將無法使用索引。
(13)。在join操作中(需要從多個數據表提取數據時),mysql只有在主鍵和外鍵的數據類型相同時才能使用索引,否則及時建立了索引也不會使用。
2) 說說反模式設計
簡單的來說,反模式是指在對經常面對的問題經常使用的低效,不良,或者有待優化的設計模式/方法。甚至,反模式也可以是一種錯誤的開發思想/理念。在這里我舉一個最簡單的例子:在面向對象設計/編程中,有一條很重要的原則, 單一責任原則(Single responsibility principle)。其中心思想就是對于一個模塊,或者一個類來說,這個模塊或者這個類應該只對系統/軟件的一個功能負責,而且該責任應該被該類完全封裝起來。當開發人員需要修改系統的某個功能,這個模塊/類是最主要的修改地方。相對應的一個反模式就是上帝類(God Class),通常來說,這個類里面控制了很多其他的類,同時也依賴其他很多類。整個類不光負責自己的主要單一功能,而且還負責了其他很多功能,包括一些輔助功能。很多維護老程序的開發人員們可能都遇過這種類,一個類里有幾千行的代碼,有很多功能,但是責任不明確單一。單元測試程序也變復雜無比。維護/修改這個類的時間要遠遠超出其他類的時間。很多時候,形成這種情況并不是開發人員故意的。很多情況下主要是由于隨著系統的年限,需求的變化,項目的資源壓力,項目組人員流動,系統結構的變化而導致某些原先小型的,符合單一原則類慢慢的變的臃腫起來。最后當這個類變成了維護的噩夢(特別是原先熟悉的開發人員離職后),重構該類就變成了一個不容易的工程。
3) 說說分庫與分表設計
垂直分表在日常開發和設計中比較常見,通俗的說法叫做“大表拆小表”,拆分是基于關系型數據庫中的“列”(字段)進行的。通常情況,某個表中的字段比較多,可以新建立一張“擴展表”,將不經常使用或者長度較大的字段拆分出去放到“擴展表”中。在字段很多的情況下,拆分開確實更便于開發和維護(筆者曾見過某個遺留系統中,一個大表中包含100多列的)。某種意義上也能避免“跨頁”的問題(MySQL、MSSQL底層都是通過“數據頁”來存儲的,“跨頁”問題可能會造成額外的性能開銷,拆分字段的操作建議在數據庫設計階段就做好。如果是在發展過程中拆分,則需要改寫以前的查詢語句,會額外帶來一定的成本和風險,建議謹慎。
垂直分庫在“微服務”盛行的今天已經非常普及了。基本的思路就是按照業務模塊來劃分出不同的數據庫,而不是像早期一樣將所有的數據表都放到同一個數據庫中。系統層面的“服務化”拆分操作,能夠解決業務系統層面的耦合和性能瓶頸,有利于系統的擴展維護。而數據庫層面的拆分,道理也是相通的。與服務的“治理”和“降級”機制類似,我們也能對不同業務類型的數據進行“分級”管理、維護、監控、擴展等。
眾所周知,數據庫往往最容易成為應用系統的瓶頸,而數據庫本身屬于“有狀態”的,相對于Web和應用服務器來講,是比較難實現“橫向擴展”的。數據庫的連接資源比較寶貴且單機處理能力也有限,在高并發場景下,垂直分庫一定程度上能夠突破IO、連接數及單機硬件資源的瓶頸,是大型分布式系統中優化數據庫架構的重要手段。
然后,很多人并沒有從根本上搞清楚為什么要拆分,也沒有掌握拆分的原則和技巧,只是一味的模仿大廠的做法。導致拆分后遇到很多問題(例如:跨庫join,分布式事務等)。
水平分表也稱為橫向分表,比較容易理解,就是將表中不同的數據行按照一定規律分布到不同的數據庫表中(這些表保存在同一個數據庫中),這樣來降低單表數據量,優化查詢性能。最常見的方式就是通過主鍵或者時間等字段進行Hash和取模后拆分。水平分表,能夠降低單表的數據量,一定程度上可以緩解查詢性能瓶頸。但本質上這些表還保存在同一個庫中,所以庫級別還是會有IO瓶頸。所以,一般不建議采用這種做法。
水平分庫分表與上面講到的水平分表的思想相同,唯一不同的就是將這些拆分出來的表保存在不同的數據中。這也是很多大型互聯網公司所選擇的做法。某種意義上來講,有些系統中使用的“冷熱數據分離”(將一些使用較少的歷史數據遷移到其他的數據庫中。而在業務功能上,通常默認只提供熱點數據的查詢),也是類似的實踐。在高并發和海量數據的場景下,分庫分表能夠有效緩解單機和單庫的性能瓶頸和壓力,突破IO、連接數、硬件資源的瓶頸。當然,投入的硬件成本也會更高。同時,這也會帶來一些復雜的技術問題和挑戰(例如:跨分片的復雜查詢,跨分片事務等)。
4) 分庫與分表帶來的分布式困境與應對之策
數據遷移與擴容問題
前面介紹到水平分表策略歸納總結為隨機分表和連續分表兩種情況。連續分表有可能存在數據熱點的問題,有些表可能會被頻繁地查詢從而造成較大壓力,熱數據的表就成為了整個庫的瓶頸,而有些表可能存的是歷史數據,很少需要被查詢到。連續分表的另外一個好處在于比較容易,不需要考慮遷移舊的數據,只需要添加分表就可以自動擴容。隨機分表的數據相對比較均勻,不容易出現熱點和并發訪問的瓶頸。但是,分表擴展需要遷移舊的數據。
針對于水平分表的設計至關重要,需要評估中短期內業務的增長速度,對當前的數據量進行容量規劃,綜合成本因素,推算出大概需要多少分片。對于數據遷移的問題,一般做法是通過程序先讀出數據,然后按照指定的分表策略再將數據寫入到各個分表中。
表關聯問題
在單庫單表的情況下,聯合查詢是非常容易的。但是,隨著分庫與分表的演變,聯合查詢就遇到跨庫關聯和跨表關系問題。在設計之初就應該盡量避免聯合查詢,可以通過程序中進行拼裝,或者通過反范式化設計進行規避。
分頁與排序問題
一般情況下,列表分頁時需要按照指定字段進行排序。在單庫單表的情況下,分頁和排序也是非常容易的。但是,隨著分庫與分表的演變,也會遇到跨庫排序和跨表排序問題。為了最終結果的準確性,需要在不同的分表中將數據進行排序并返回,并將不同分表返回的結果集進行匯總和再次排序,最后再返回給用戶。
分布式事務問題
隨著分庫與分表的演變,一定會遇到分布式事務問題,那么如何保證數據的一致性就成為一個必須面對的問題。目前,分布式事務并沒有很好的解決方案,難以滿足數據強一致性,一般情況下,使存儲數據盡可能達到用戶一致,保證系統經過一段較短的時間的自我恢復和修正,數據最終達到一致。
分布式全局唯一ID
在單庫單表的情況下,直接使用數據庫自增特性來生成主鍵ID,這樣確實比較簡單。在分庫分表的環境中,數據分布在不同的分表上,不能再借助數據庫自增長特性。需要使用全局唯一 ID,例如 UUID、GUID等。關于如何選擇合適的全局唯一 ID,我會在后面的章節中進行介紹。
摘抄自:http://blog.csdn.net/jiangpingjiangping/article/details/78069480
5) 說說 SQL 優化之道
(一)、一些常見的SQL實踐
(1)負向條件查詢不能使用索引
select from order where status!=0 and stauts!=1
not in/not exists都不是好習慣
可以優化為in查詢:
select from order where status in(2,3)
(2)前導模糊查詢不能使用索引
select from order where desc like ‘%XX’
而非前導模糊查詢則可以:
select from order where desc like ‘XX%’
(3)數據區分度不大的字段不宜使用索引
select from user where sex=1
原因:性別只有男,女,每次過濾掉的數據很少,不宜使用索引。
經驗上,能過濾80%數據時就可以使用索引。對于訂單狀態,如果狀態值很少,不宜使用索引,如果狀態值很多,能夠過濾大量數據,則應該建立索引。
(4)在屬性上進行計算不能命中索引
select from order where YEAR(date) 《 = ‘2017’
即使date上建立了索引,也會全表掃描,可優化為值計算:
select from order where date 《 = CURDATE()
或者:
select from order where date 《 = ‘2017-01-01’
(二)、并非周知的SQL實踐
(5)如果業務大部分是單條查詢,使用Hash索引性能更好,例如用戶中心
select from user where uid=? select from user where login_name=?
原因:B-Tree索引的時間復雜度是O(log(n));Hash索引的時間復雜度是O(1)
(6)允許為null的列,查詢有潛在大坑
單列索引不存null值,復合索引不存全為null的值,如果列允許為null,可能會得到“不符合預期”的結果集
select from user where name != ‘shenjian’
如果name允許為null,索引不存儲null值,結果集中不會包含這些記錄。
所以,請使用not null約束以及默認值。
(7)復合索引最左前綴,并不是值SQL語句的where順序要和復合索引一致
用戶中心建立了(login_name, passwd)的復合索引:
select from user where login_name=? and passwd=?
select from user where passwd=? and login_name=?
都能夠命中索引
select from user where login_name=?
也能命中索引,滿足復合索引最左前綴
select from user where passwd=?
不能命中索引,不滿足復合索引最左前綴
(8)使用ENUM而不是字符串
ENUM保存的是TINYINT,別在枚舉中搞一些“中國”“北京”“技術部”這樣的字符串,字符串空間又大,效率又低。
(三)、小眾但有用的SQL實踐
(9)如果明確知道只有一條結果返回,limit 1能夠提高效率
select from user where login_name=?
可以優化為:
select from user where login_name=? limit 1
原因:你知道只有一條結果,但數據庫并不知道,明確告訴它,讓它主動停止游標移動
(10)把計算放到業務層而不是數據庫層,除了節省數據的CPU,還有意想不到的查詢緩存優化效果
select from order where date 《 = CURDATE()
這不是一個好的SQL實踐,應該優化為:
$curDate = date(‘Y-m-d’);
$res = mysqlquery(
‘select from order where date 《 = $curDate’);
原因:
釋放了數據庫的CPU
多次調用,傳入的SQL相同,才可以利用查詢緩存
(11)強制類型轉換會全表掃描
select from user where phone=13800001234
你以為會命中phone索引么?大錯特錯了,這個語句究竟要怎么改?
末了,再加一條,不要使用select *(潛臺詞,文章的SQL都不合格 ==),只返回需要的列,能夠大大的節省數據傳輸量,與數據庫的內存使用量喲。
6) MySQL 遇到的死鎖問題
產生死鎖的四個必要條件:
(1) 互斥條件:一個資源每次只能被一個進程使用。
(2) 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
(3) 不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。
(4) 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關系。
這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發生死鎖。
下列方法有助于最大限度地降低死鎖:
(1)按同一順序訪問對象。
(2)避免事務中的用戶交互。
(3)保持事務簡短并在一個批處理中。
(4)使用低隔離級別。
(5)使用綁定連接。
7) 存儲引擎的 InnoDB 與 MyISAM
(1).InnoDB不支持FULLTEXT類型的索引。
(2).InnoDB 中不保存表的具體行數,也就是說,執行select count() from table時,InnoDB要掃描一遍整個表來計算有多少行,但是MyISAM只要簡單的讀出保存好的行數即可。注意的是,當count()語句包含 where條件時,兩種表的操作是一樣的。
(3)。對于AUTO_INCREMENT類型的字段,InnoDB中必須包含只有該字段的索引,但是在MyISAM表中,可以和其他字段一起建立聯合索引。
(4).DELETE FROM table時,InnoDB不會重新建立表,而是一行一行的刪除。
(5).LOAD TABLE FROM MASTER操作對InnoDB是不起作用的,解決方法是首先把InnoDB表改成MyISAM表,導入數據后再改成InnoDB表,但是對于使用的額外的InnoDB特性(例如外鍵)的表不適用。
另外,InnoDB表的行鎖也不是絕對的,假如在執行一個SQL語句時MySQL不能確定要掃描的范圍,InnoDB表同樣會鎖全表,例如update table set num=1 where name like “%aaa%”
8) 數據庫索引的原理
數據庫索引,是數據庫管理系統中一個排序的數據結構,以協助快速查詢、更新數據庫表中數據。索引的實現通常使用B樹及其變種B+樹。
9) 為什么要用 B-tree
一般來說,索引本身也很大,不可能全部存儲在內存中,因此索引往往以索引文件的形式存儲的磁盤上。這樣的話,索引查找過程中就要產生磁盤I/O消耗,相對于內存存取,I/O存取的消耗要高幾個數量級,所以評價一個數據結構作為索引的優劣最重要的指標就是在查找過程中磁盤I/O操作次數的漸進復雜度。換句話說,索引的結構組織要盡量減少查找過程中磁盤I/O的存取次數。
10) 聚集索引與非聚集索引的區別
(1)。聚集索引一個表只能有一個,而非聚集索引一個表可以存在多個
(2)。聚集索引存儲記錄是物理上連續存在,而非聚集索引是邏輯上的連續,物理存儲并不連續
(3)。聚集索引:物理存儲按照索引排序;聚集索引是一種索引組織形式,索引的鍵值邏輯順序決定了表數據行的物理存儲順序
非聚集索引:物理存儲不按照索引排序;非聚集索引則就是普通索引了,僅僅只是對數據列創建相應的索引,不影響整個表的物理存儲順序。
(4)。索引是通過二叉樹的數據結構來描述的,我們可以這么理解聚簇索引:索引的葉節點就是數據節點。而非聚簇索引的葉節點仍然是索引節點,只不過有一個指針指向對應的數據塊。
11) limit 20000 加載很慢怎么解決
mysql的性能低是因為數據庫要去掃描N+M條記錄,然后又要放棄之前N條記錄,開銷很大
解決思略:
(1)、前端加緩存,或者其他方式,減少落到庫的查詢操作,例如某些系統中數據在搜索引擎中有備份的,可以用es等進行搜索
(2)、使用延遲關聯,即先通用limit得到需要數據的索引字段,然后再通過原表和索引字段關聯獲得需要數據
select a.* from a,(select id from table_1 where is_deleted=‘N’ limit 100000,20) b where a.id = b.id
(3)、從業務上實現,不分頁如此多,例如只能分頁前100頁,后面的不允許再查了
(4)、不使用limit N,M,而是使用limit N,即將offset轉化為where條件。
12) 選擇合適的分布式主鍵方案
數據庫自增長序列或字段
UUID
使用UUID to Int64的方法
Redis生成ID
Twitter的snowflake算法
利用zookeeper生成唯一ID
MongoDB的ObjectId
13) 選擇合適的數據存儲方案
關系型數據庫 MySQL
MySQL 是一個最流行的關系型數據庫,在互聯網產品中應用比較廣泛。一般情況下,MySQL 數據庫是選擇的第一方案,基本上有 80% ~ 90% 的場景都是基于 MySQL 數據庫的。因為,需要關系型數據庫進行管理,此外,業務存在許多事務性的操作,需要保證事務的強一致性。同時,可能還存在一些復雜的 SQL 的查詢。值得注意的是,前期盡量減少表的聯合查詢,便于后期數據量增大的情況下,做數據庫的分庫分表。
內存數據庫 Redis
隨著數據量的增長,MySQL 已經滿足不了大型互聯網類應用的需求。因此,Redis 基于內存存儲數據,可以極大的提高查詢性能,對產品在架構上很好的補充。例如,為了提高服務端接口的訪問速度,盡可能將讀頻率高的熱點數據存放在 Redis 中。這個是非常典型的以空間換時間的策略,使用更多的內存換取 CPU 資源,通過增加系統的內存消耗,來加快程序的運行速度。
在某些場景下,可以充分的利用 Redis 的特性,大大提高效率。這些場景包括緩存,會話緩存,時效性,訪問頻率,計數器,社交列表,記錄用戶判定信息,交集、并集和差集,熱門列表與排行榜,最新動態等。
使用 Redis 做緩存的時候,需要考慮數據不一致與臟讀、緩存更新機制、緩存可用性、緩存服務降級、緩存穿透、緩存預熱等緩存使用問題。
文檔數據庫 MongoDB
MongoDB 是對傳統關系型數據庫的補充,它非常適合高伸縮性的場景,它是可擴展性的表結構。基于這點,可以將預期范圍內,表結構可能會不斷擴展的 MySQL 表結構,通過 MongoDB 來存儲,這就可以保證表結構的擴展性。
此外,日志系統數據量特別大,如果用 MongoDB 數據庫存儲這些數據,利用分片集群支持海量數據,同時使用聚集分析和 MapReduce 的能力,是個很好的選擇。
MongoDB 還適合存儲大尺寸的數據,GridFS 存儲方案就是基于 MongoDB 的分布式文件存儲系統。
列族數據庫 HBase
HBase 適合海量數據的存儲與高性能實時查詢,它是運行于 HDFS 文件系統之上,并且作為 MapReduce 分布式處理的目標數據庫,以支撐離線分析型應用。在數據倉庫、數據集市、商業智能等領域發揮了越來越多的作用,在數以千計的企業中支撐著大量的大數據分析場景的應用。
全文搜索引擎 ElasticSearch
在一般情況下,關系型數據庫的模糊查詢,都是通過 like 的方式進行查詢。其中,like “value%” 可以使用索引,但是對于 like “%value%” 這樣的方式,執行全表查詢,這在數據量小的表,不存在性能問題,但是對于海量數據,全表掃描是非常可怕的事情。ElasticSearch 作為一個建立在全文搜索引擎 Apache Lucene 基礎上的實時的分布式搜索和分析引擎,適用于處理實時搜索應用場景。此外,使用 ElasticSearch 全文搜索引擎,還可以支持多詞條查詢、匹配度與權重、自動聯想、拼寫糾錯等高級功能。因此,可以使用 ElasticSearch 作為關系型數據庫全文搜索的功能補充,將要進行全文搜索的數據緩存一份到 ElasticSearch 上,達到處理復雜的業務與提高查詢速度的目的。
ElasticSearch 不僅僅適用于搜索場景,還非常適合日志處理與分析的場景。著名的 ELK 日志處理方案,由 ElasticSearch、Logstash 和 Kibana 三個組件組成,包括了日志收集、聚合、多維度查詢、可視化顯示等。
14) ObjectId 規則
[0,1,2,3] [4,5,6] [7,8] [9,10,11]
時間戳 |機器碼 |PID |計數器
前四位是時間戳,可以提供秒級別的唯一性。
接下來三位是所在主機的唯一標識符,通常是機器主機名的散列值。
接下來兩位是產生ObjectId的PID,確保同一臺機器上并發產生的ObjectId是唯一的。
前九位保證了同一秒鐘不同機器的不同進程產生的ObjectId時唯一的。
最后三位是自增計數器,確保相同進程同一秒鐘產生的ObjectId是唯一的。
15) 聊聊 MongoDB 使用場景
高伸縮性的場景
MongoDB 非常適合高伸縮性的場景,它是可擴展性的表結構。基于這點,可以將預期范圍內,表結構可能會不斷擴展的 MySQL 表結構,通過 MongoDB 來存儲,這就可以保證表結構的擴展性。
日志系統的場景
日志系統數據量特別大,如果用 MongoDB 數據庫存儲這些數據,利用分片集群支持海量數據,同時使用聚集分析和 MapReduce 的能力,是個很好的選擇。
分布式文件存儲
MongoDB 還適合存儲大尺寸的數據,之前介紹的 GridFS 存儲方案,就是基于 MongoDB 的分布式文件存儲系統。
16) 倒排索引
倒排索引(英語:Inverted index),也常被稱為反向索引、置入檔案或反向檔案,是一種索引方法,被用來存儲在全文搜索下某個單詞在一個文檔或者一組文檔中的存儲位置的映射。它是文檔檢索系統中最常用的數據結構。
有兩種不同的反向索引形式:
一條記錄的水平反向索引(或者反向檔案索引)包含每個引用單詞的文檔的列表。
一個單詞的水平反向索引(或者完全反向索引)又包含每個單詞在一個文檔中的位置。
17) 聊聊 ElasticSearch 使用場景
全文搜索,這個是用的最多的。加上分詞插件、拼音插件什么的可以做成強大的全文搜索引擎。
數據庫,挺奇葩的用法,因為ES存數相同數據,更費空間,不過確實不錯,因為他的強大統計分析匯總能力,再加上分布式P2P擴展能力,現在硬件又那么便宜,所以就有人拿來當數據庫了。
在線統計分析引擎,日志系統。logstash,不用解釋了吧。可以實時動態分析數據,很是爽。
評論
查看更多