日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

sql优化之:数据库索引创建原则,or/in/union与索引优化,聚集索引/非聚集索引/联合索引/索引覆盖,MySQL冗余数据的三种方案,MySQL双主一致性架构优化(来源:架构师之路)

發布時間:2024/9/27 数据库 34 豆豆

一、一些常見的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 = mysql_query(
‘select * from order where date < = $curDate’);
原因:
釋放了數據庫的CPU
多次調用,傳入的SQL相同,才可以利用查詢緩存

(11)強制類型轉換會全表掃描
select * from user where phone=13800001234
你以為會命中phone索引么?大錯特錯了,這個語句究竟要怎么改?

末了,再加一條,不要使用select *(潛臺詞,文章的SQL都不合格 =_=),只返回需要的列,能夠大大的節省數據傳輸量,與數據庫的內存使用量喲。

=======================================

舉例,業務場景,用戶表,表結構為:
t_user(
uid primary key,
login_name unique,
passwd,
login_time,
age,

);

聚集索引(clustered index):聚集索引決定數據在磁盤上的物理排序,一個表只能有一個聚集索引,一般用primary key來約束。

舉例:t_user場景中,uid上的索引。

非聚集索引(non-clustered index):它并不決定數據在磁盤上的物理排序,索引上只包含被建立索引的數據,以及一個行定位符row-locator,這個行定位符,可以理解為一個聚集索引物理排序的指針,通過這個指針,可以找到行數據。

舉例,查找年輕MM的業務需求:
select uid from t_user where age > 18 and age < 26;
age上建立的索引,就是非聚集索引。

聯合索引:多個字段上建立的索引,能夠加速復核查詢條件的檢索
舉例,登錄業務需求:
select uid, login_time from t_user where
login_name=? and passwd=?
可以建立(login_name, passwd)的聯合索引。

聯合索引能夠滿足最左側查詢需求,例如(a, b, c)三列的聯合索引,能夠加速a | (a, b) | (a, b, c) 三組查詢需求。

這也就是為何不建立(passwd, login_name)這樣聯合索引的原因,業務上幾乎沒有passwd的單條件查詢需求,而有很多login_name的單條件查詢需求。

提問:
select uid, login_time from t_user where
passwd=? and login_name=?
能否命中(login_name, passwd)這個聯合索引?
回答:可以,最左側查詢需求,并不是指SQL語句的寫法必須滿足索引的順序(這是很多朋友的誤解)

索引覆蓋:被查詢的列,數據能從索引中取得,而不用通過行定位符row-locator再到row上獲取,即“被查詢列要被所建的索引覆蓋”,這能夠加速查詢速度。

舉例,登錄業務需求:
select uid, login_time from t_user where
login_name=? and passwd=?
可以建立(login_name, passwd, login_time)的聯合索引,由于login_time已經建立在索引中了,被查詢的uid和login_time就不用去row上獲取數據了,從而加速查詢。

末了多說一句,登錄這個業務場景,login_name具備唯一性,建這個單列索引就好。

作業:
假設訂單有三種狀態:0已下單,1已支付,2已完成
業務需求,查詢未完成的訂單,哪個SQL更快呢?
select * from order where status!=2
select * from order where status=0 or status=1
select * from order where status IN (0,1)
select * from order where status=0
union
select * from order where stauts=1

============================================
假設訂單業務表結構為:
order(oid, date, uid, status, money, time, …)
其中:
oid,訂單ID,主鍵
date,下單日期,有普通索引,管理后臺經常按照date查詢
uid,用戶ID,有普通索引,用戶查詢自己訂單
status,訂單狀態,有普通索引,管理后臺經常按照status查詢
money/time,訂單金額/時間,被查詢字段,無索引

假設訂單有三種狀態:0已下單,1已支付,2已完成
業務需求,查詢未完成的訂單,哪個SQL更快呢?
select * from order where status!=2
select * from order where status=0 or status=1
select * from order where status IN (0,1)
select * from order where status=0
union all
select * from order where status=1

結論:方案1最慢,方案2,3,4都能命中索引

但是…

一:union all 肯定是能夠命中索引的
select * from order where status=0
union all
select * from order where status=1
說明:
直接告訴MySQL怎么做,MySQL耗費的CPU最少
程序員并不經常這么寫SQL(union all)

二:簡單的in能夠命中索引
select * from order where status in (0,1)
說明:
讓MySQL思考,查詢優化耗費的cpu比union all多,但可以忽略不計
程序員最常這么寫SQL(in),這個例子,最建議這么寫

三:對于or,新版的MySQL能夠命中索引
select * from order where status=0 or status=1
說明:
讓MySQL思考,查詢優化耗費的cpu比in多,別把負擔交給MySQL
不建議程序員頻繁用or,不是所有的or都命中索引
對于老版本的MySQL,建議查詢分析下

四、對于!=,負向查詢肯定不能命中索引
select * from order where status!=2
說明:
全表掃描,效率最低,所有方案中最慢
禁止使用負向查詢

五、其他方案
select * from order where status < 2
這個具體的例子中,確實快,但是:
這個例子只舉了3個狀態,實際業務不止這3個狀態,并且狀態的“值”正好滿足偏序關系,萬一是查其他狀態呢,SQL不宜依賴于枚舉的值,方案不通用
這個SQL可讀性差,可理解性差,可維護性差,強烈不推薦

六、作業
這樣的查詢能夠命中索引么?
select * from order where uid in (
select uid from order where status=0
)
select * from order where status in (0, 1) order by date desc
select * from order where status=0 or date <= CURDATE()

==========================================
一,為什么要冗余數據
互聯網數據量很大的業務場景,往往數據庫需要進行水平切分來降低單庫數據量。

水平切分會有一個patition key,通過patition key的查詢能夠直接定位到庫,但是非patition key上的查詢可能就需要掃描多個庫了。

此時常見的架構設計方案,是使用數據冗余這種反范式設計來滿足分庫后不同維度的查詢需求。

例如:訂單業務,對用戶和商家都有訂單查詢需求:
Order(oid, info_detail);
T(buyer_id, seller_id, oid);
如果用buyer_id來分庫,seller_id的查詢就需要掃描多庫。
如果用seller_id來分庫,buyer_id的查詢就需要掃描多庫。

此時可以使用數據冗余來分別滿足buyer_id和seller_id上的查詢需求:
T1(buyer_id, seller_id, oid)
T2(seller_id, buyer_id, oid)
同一個數據,冗余兩份,一份以buyer_id來分庫,滿足買家的查詢需求;一份以seller_id來分庫,滿足賣家的查詢需求。

如何實施數據的冗余,是今天將要討論的內容。

二,服務同步雙寫

顧名思義,由服務層同步寫冗余數據,如上圖1-4流程:
業務方調用服務,新增數據
服務先插入T1數據
服務再插入T2數據
服務返回業務方新增數據成功

優點:
不復雜,服務層由單次寫,變兩次寫
數據一致性相對較高(因為雙寫成功才返回)

缺點:
請求的處理時間增加(要插入兩次,時間加倍)
數據仍可能不一致,例如第二步寫入T1完成后服務重啟,則數據不會寫入T2

如果系統對處理時間比較敏感,引出常用的第二種方案。

三,服務異步雙寫

數據的雙寫并不再由服務來完成,服務層異步發出一個消息,通過消息總線發送給一個專門的數據復制服務來寫入冗余數據,如上圖1-6流程:
業務方調用服務,新增數據
服務先插入T1數據
服務向消息總線發送一個異步消息(發出即可,不用等返回,通常很快就能完成)
服務返回業務方新增數據成功
消息總線將消息投遞給數據同步中心
數據同步中心插入T2數據

優點:
請求處理時間短(只插入1次)

缺點:
系統的復雜性增加了,多引入了一個組件(消息總線)和一個服務(專用的數據復制服務)
因為返回業務線數據插入成功時,數據還不一定插入到T2中,因此數據有一個不一致時間窗口(這個窗口很短,最終是一致的)
在消息總線丟失消息時,冗余表數據會不一致

不管是服務同步雙寫,還是服務異步雙寫,服務都需要關注“冗余數據”帶來的復雜性。如果想解除“數據冗余”對系統的耦合,引出常用的第三種方案。

四,線下異步雙寫

為了屏蔽“冗余數據”對服務帶來的復雜性,數據的雙寫不再由服務層來完成,而是由線下的一個服務或者任務來完成,如上圖1-6流程:
業務方調用服務,新增數據
服務先插入T1數據
服務返回業務方新增數據成功
數據會被寫入到數據庫的log中
線下服務或者任務讀取數據庫的log
線下服務或者任務插入T2數據

優點:
數據雙寫與業務完全解耦
請求處理時間短(只插入1次)

缺點:
返回業務線數據插入成功時,數據還不一定插入到T2中,因此數據有一個不一致時間窗口(這個窗口很短,最終是一致的)
數據的一致性依賴于線下服務或者任務的可靠性

五,總結
互聯網數據量大的業務場景,常常:
使用水平切分來降低單庫數據量
使用數據冗余的反范式設計來滿足不同維度的查詢需求
使用服務同步雙寫法能夠很容易的實現數據冗余
為了降低時延,可以優化為服務異步雙寫法
為了屏蔽“冗余數據”對服務帶來的復雜性,可以優化為線下異步雙寫法

============================
一、雙主保證高可用
MySQL數據庫集群常使用一主多從,主從同步,讀寫分離的方式來擴充數據庫的讀性能,保證讀庫的高可用,但此時寫庫仍然是單點。

在一個MySQL數據庫集群中可以設置兩個主庫,并設置雙向同步,以冗余寫庫的方式來保證寫庫的高可用。

二、并發引發不一致
數據冗余會引發數據的一致性問題,因為數據的同步有一個時間差,并發的寫入可能導致數據同步失敗,引起數據丟失:

如上圖所述,假設主庫使用了auto increment來作為自增主鍵:
兩個MySQL-master設置雙向同步可以用來保證主庫的高可用
數據庫中現存的記錄主鍵是1,2,3
主庫1插入了一條記錄,主鍵為4,并向主庫2同步數據
數據同步成功之前,主庫2也插入了一條記錄,由于數據還沒有同步成功,插入記錄生成的主鍵也為4,并向主庫1也同步數據
主庫1和主庫2都插入了主鍵為4的記錄,雙主同步失敗,數據不一致

三、相同步長免沖突
能否保證兩個主庫生成的主鍵一定不沖突呢?
回答:
設置不同的初始值
設置相同的增長步長
就能夠做到。

如上圖所示:
兩個MySQL-master設置雙向同步可以用來保證主庫的高可用
庫1的自增初始值是1,庫2的自增初始值是2,增長步長都為2
庫1中插入數據主鍵為1/3/5/7,庫2中插入數據主鍵為2/4/6/8,不沖突
數據雙向同步后,兩個主庫會包含全部數據

如上圖所示,兩個主庫最終都將包含1/2/3/4/5/6/7/8所有數據,即使有一個主庫掛了,另一個主庫也能夠保證寫庫的高可用。

四、上游生成ID避沖突
換一個思路,為何要依賴于數據庫的自增ID,來保證數據的一致性呢?
完全可以由業務上游,使用統一的ID生成器,來保證ID的生成不沖突:

如上圖所示,調用方插入數據時,帶入全局唯一ID,而不依賴于數據庫的auto increment,也能解決這個問題。

至于如何生成全局唯一,趨勢遞增的ID,參見文章《分布式ID生成算法》。

五、消除雙寫不治本
使用auto increment兩個主庫并發寫可能導致數據不一致,只使用一個主庫提供服務,另一個主庫作為shadow-master,只用來保證高可用,能否避免一致性問題呢?

如上圖所示:
兩個MySQL-master設置雙向同步可以用來保證主庫的高可用
只有主庫1對外提供寫入服務
兩個主庫設置相同的虛IP,在主庫1掛掉或者網絡異常的時候,虛IP自動漂移,shadow master頂上,保證主庫的高可用

這個切換由于虛IP沒有變化,所以切換過程對調用方是透明的,但在極限的情況下,也可能引發數據的不一致:

如上圖所示:
兩個MySQL-master設置雙向同步可以用來保證主庫的高可用,并設置了相同的虛IP
網絡抖動前,主庫1對上游提供寫入服務,插入了一條記錄,主鍵為4,并向shadow master主庫2同步數據
突然主庫1網絡異常,keepalived檢測出異常后,實施虛IP漂移,主庫2開始提供服務
在主鍵4的數據同步成功之前,主庫2插入了一條記錄,也生成了主鍵為4的記錄,結果導致數據不一致

六、內網DNS探測
虛IP漂移,雙主同步延時導致的數據不一致,本質上,需要在雙主同步完數據之后,再實施虛IP偏移,使用內網DNS探測,可以實現shadow master延時高可用:
使用內網域名連接數據庫,例如:db.58daojia.org
主庫1和主庫2設置雙主同步,不使用相同虛IP,而是分別使用ip1和ip2
一開始db.58daojia.org指向ip1
用一個小腳本輪詢探測ip1主庫的連通性
當ip1主庫發生異常時,小腳本delay一個x秒的延時,等待主庫2同步完數據之后,再將db.58daojia.org解析到ip2
程序以內網域名進行重連,即可自動連接到ip2主庫,并保證了數據的一致性

七、總結
主庫高可用,主庫一致性,一些小技巧:
雙主同步是一種常見的保證寫庫高可用的方式
設置相同步長,不同初始值,可以避免auto increment生成沖突主鍵
不依賴數據庫,業務調用方自己生成全局唯一ID是一個好方法
shadow master保證寫庫高可用,只有一個寫庫提供服務,并不能完全保證一致性
內網DNS探測,可以實現在主庫1出現問題后,延時一個時間,再進行主庫切換,以保證數據一致性

與50位技術專家面對面20年技術見證,附贈技術全景圖

總結

以上是生活随笔為你收集整理的sql优化之:数据库索引创建原则,or/in/union与索引优化,聚集索引/非聚集索引/联合索引/索引覆盖,MySQL冗余数据的三种方案,MySQL双主一致性架构优化(来源:架构师之路)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。