少俠!如何寫一手好 SQL ?
閱讀本文大概需要 7 分鐘。
來源 |?編碼磚家?
鏈接 | cnblogs.com/xiaoyangjia/p/11267191.html
博主(編碼磚家)負責(zé)的項目主要采用阿里云數(shù)據(jù)庫MySQL,最近頻繁出現(xiàn)慢SQL告警,執(zhí)行時間最長的竟然高達5分鐘。導(dǎo)出日志后分析,主要原因竟然是沒有命中索引和沒有分頁處理?。
其實這是非常低級的錯誤,我不禁后背一涼,團隊成員的技術(shù)水平亟待提高啊。改造這些SQL的過程中,總結(jié)了一些經(jīng)驗分享給大家,如果有錯誤歡迎批評指正。

select field_1,field_2 from table where id < #{prePageMinId} order by id desc limit 20,prePageMinId是上一頁數(shù)據(jù)記錄的最小ID。雖然當(dāng)時查詢速度還湊合,隨著數(shù)據(jù)不斷增長,有朝一日必定不堪重負。max_used_connections / max_connections * 100% = 3/100 *100% ≈ 3%
show variables like '%max_connections%';show variables like '%max_user_connections%';
[mysqld]max_connections = 100max_used_connections = 20
查詢耗時0.5秒
充分利用但不濫用索引,須知索引也消耗磁盤和CPU。 不推薦使用數(shù)據(jù)庫函數(shù)格式化數(shù)據(jù),交給應(yīng)用程序處理。 不推薦使用外鍵約束,用應(yīng)用程序保證數(shù)據(jù)準(zhǔn)確性。 寫多讀少的場景,不推薦使用唯一索引,用應(yīng)用程序保證唯一性。 適當(dāng)冗余字段,嘗試創(chuàng)建中間表,用應(yīng)用程序計算中間結(jié)果,用空間換時間。 不允許執(zhí)行極度耗時的事務(wù),配合應(yīng)用程序拆分成更小的事務(wù)。 預(yù)估重要數(shù)據(jù)表(比如訂單表)的負載和數(shù)據(jù)增長態(tài)勢,提前優(yōu)化。
如果長度能夠滿足,整型盡量使用tinyint、smallint、medium_int而非int。
如果字符串長度確定,采用char類型。
如果varchar能夠滿足,不采用text類型。
精度要求較高的使用decimal類型,也可以使用BIGINT,比如精確兩位小數(shù)就乘以100后保存。

is not null的判斷。普通索引:最基本的索引。 組合索引:多個字段上建立的索引,能夠加速復(fù)合查詢條件的檢索。 唯一索引:與普通索引類似,但索引列的值必須唯一,允許有空值。 組合唯一索引:列值的組合必須唯一。 主鍵索引:特殊的唯一索引,用于唯一標(biāo)識數(shù)據(jù)表中的某一條記錄,不允許有空值,一般用primary key約束。 全文索引:用于海量文本的查詢,MySQL5.6之后的InnoDB和MyISAM均支持全文索引。由于查詢精度以及擴展性不佳,更多的企業(yè)選擇Elasticsearch。
分頁查詢很重要,如果查詢數(shù)據(jù)量超過30%,MYSQL不會使用索引。 單表索引數(shù)不超過5個、單個索引字段數(shù)不超過5個。 字符串可使用前綴索引,前綴長度控制在5-8個字符。 字段唯一性太低,增加索引沒有意義,如:是否刪除、性別。 合理使用覆蓋索引,如下所示: select loginname, nickname from member where login_name = ?
update status=0 FROMcoupon?WHERE expire_date <= #{currentDate} and status=1;如果大量優(yōu)惠券需要更新為不可用狀態(tài),執(zhí)行這條SQL可能會堵死其他SQL,分批處理偽代碼如下:int pageNo = 1;
int PAGE_SIZE = 100;
while(true) {
ListbatchIdList = queryList('select id FROM `coupon` WHERE expire_date <= #{currentDate} and status = 1 limit #{(pageNo-1) * PAGE_SIZE},#{PAGE_SIZE}');
if (CollectionUtils.isEmpty(batchIdList)) {
return;
}
update('update status = 0 FROM `coupon` where status = 1 and id in #{batchIdList}')
pageNo ++;
}
操作符<>優(yōu)化
select id from orders where amount != 100;如果金額為100的訂單極少,這種數(shù)據(jù)分布嚴(yán)重不均的情況下,有可能使用索引。鑒于這種不確定性,采用union聚合搜索結(jié)果,改寫方法如下:(select id from orders where amount > 100) union all(select id from orders where amount < 100 and amount > 0)
OR優(yōu)化
select id,product_name from orders where mobile_no = '13421800407' or user_id = 100;
(select id,product_name from orders where mobile_no = '13421800407') union(select id,product_name from orders where user_id = 100);
IN優(yōu)化
IN適合主表大子表小,EXIST適合主表小子表大。由于查詢優(yōu)化器的不斷升級,很多場景這兩者性能差不多一樣了。 嘗試改為join查詢,舉例如下:
select o.id from orders o left join user u on o.user_id = u.id where u.level = 'VIP';
不做列運算
select id from order where date_format(create_time,'%Y-%m-%d') = '2019-07-01';
select id from order where create_time between '2019-07-01 00:00:00' and '2019-07-01 23:59:59';
避免Select all
SELECT *,它會進行全表掃描,不能有效利用索引。Like優(yōu)化
SELECT column FROM table WHERE field like '%keyword%';
SELECT column FROM table WHERE field like 'keyword%';
Join優(yōu)化
驅(qū)動表和被驅(qū)動表盡可能增加查詢條件,滿足ON的條件而少用Where,用小結(jié)果集驅(qū)動大結(jié)果集。 被驅(qū)動表的join字段上加上索引,無法建立索引的時候,設(shè)置足夠的Join Buffer Size。 禁止join連接三個以上的表,嘗試增加冗余字段。
Limit優(yōu)化
select * from orders order by id desc limit 100000,10 耗時0.4秒select * from orders order by id desc limit 1000000,10耗時5.2秒
select * from orders where id > (select id from orders order by id desc limit 1000000, 1) order by id desc limit 0,10耗時0.5秒
select id from orders where id between 1000000 and 1000010 order by id desc耗時0.3秒
其他數(shù)據(jù)庫

推薦閱讀:
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。
朕已閱?
評論
圖片
表情

