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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > 数据库 >内容正文

数据库

天天写SQL,这些神奇的特性你知道吗?

發(fā)布時(shí)間:2024/1/1 数据库 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 天天写SQL,这些神奇的特性你知道吗? 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
摘要:不要歪了,我這里說(shuō)特性它不是 bug,而是故意設(shè)計(jì)的機(jī)制或語(yǔ)法,你有可能天天寫語(yǔ)句或許還沒(méi)發(fā)現(xiàn)原來(lái)還能這樣用,沒(méi)關(guān)系我們一起學(xué)下漲姿勢(shì)。

本文分享自華為云社區(qū)《【云駐共創(chuàng)】天天寫 SQL,你遇到了哪些神奇的特性?》,作者:?龍哥手記 。

一 SQL 的第一個(gè)神奇特性

日常開(kāi)發(fā)我們經(jīng)常會(huì)對(duì)表進(jìn)行聚合查詢操作,但只能在 SELECT 子句中寫下面 3 種內(nèi)容:通過(guò) GROUP BY 子句指定的聚合鍵、聚合函數(shù)(SUM 、AVG 等)、常量,不懂沒(méi)關(guān)系我們來(lái)看個(gè)例子

聽(tīng)我解釋

有學(xué)生班級(jí)表(tbl_student_class) 以及數(shù)據(jù)如下

DROP TABLE IF EXISTS tbl_student_class; CREATE TABLE tbl_student_class (id int(8) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主鍵',sno varchar(12) NOT NULL COMMENT '學(xué)號(hào)',cno varchar(5) NOT NULL COMMENT '班級(jí)號(hào)',cname varchar(20) NOT NULL COMMENT '班級(jí)名',PRIMARY KEY (id) ) COMMENT='學(xué)生班級(jí)表'; -- ---------------------------- -- Records of tbl_student_class -- ---------------------------- INSERT INTO tbl_student_class VALUES ('1', '20190607001', '0607', '影視7班'); INSERT INTO tbl_student_class VALUES ('2', '20190607002', '0607', '影視7班'); INSERT INTO tbl_student_class VALUES ('3', '20190608003', '0608', '影視8班'); INSERT INTO tbl_student_class VALUES ('4', '20190608004', '0608', '影視8班'); INSERT INTO tbl_student_class VALUES ('5', '20190609005', '0609', '影視9班'); INSERT INTO tbl_student_class VALUES ('6', '20190609006', '0609', '影視9班');

我想統(tǒng)計(jì)各個(gè)班(班級(jí)號(hào)、班級(jí)名)一個(gè)有多少人、以及最大的學(xué)號(hào),我們?cè)撛趺磳戇@個(gè)查詢 SQL?我想大家用腳都寫得出來(lái)

SELECT cno,cname,count(sno),MAX(sno) FROM tbl_student_class GROUP BY cno,cname;

可是有人會(huì)想了,cno 和 cname 本來(lái)就是一對(duì)一,cno 一旦確定,cname 也就確定了嗎,那 SQL 咱們是不是可以這么寫?

SELECT cno,cname,count(sno),MAX(sno) FROM tbl_student_class GROUP BY cno;

執(zhí)行報(bào)錯(cuò)了

[Err] 1055 - Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'test.tbl_student_class.cname' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by 提示信息:SELECT 列表中的第二個(gè)表達(dá)式(cname)不在 GROUP BY 的子句中,同時(shí)它也不是**聚合函數(shù)**;這與 sql 模式:ONLY_FULL_GROUP_BY 不相容的哈

那為什么 GROUP BY 之后不能直接引用原表(不在 GROUP BY 子句)中的列 ?莫急,我們慢慢往下看就明白了

1.0 SQL 模式

MySQL 服務(wù)器可以在不同的 SQL 模式下運(yùn)行,并且可以針對(duì)不同的客戶端以不同的方式應(yīng)用這些模式,具體取決于 sql_mode 系統(tǒng)變量的值。DBA 可以設(shè)置全局 SQL 模式以匹配站點(diǎn)服務(wù)器操作要求,并且每個(gè)應(yīng)用程序可以將其會(huì)話 SQL 模式設(shè)置為其自己的要求。

模式會(huì)影響 MySQL 支持的 SQL 語(yǔ)法以及它執(zhí)行的數(shù)據(jù)驗(yàn)證檢查,這使得在不同環(huán)境中使用 MySQL 以及將 MySQL 與其他數(shù)據(jù)庫(kù)服務(wù)器一起使用變得更加容易。更多詳情請(qǐng)查官網(wǎng)自己找:Server SQL Modes

MySQL 版本不同,內(nèi)容會(huì)略有不同(包括默認(rèn)值),查閱的時(shí)候注意與自身的 MySQL 版本保持一致哈

SQL 模式主要分兩類:語(yǔ)法支持類和數(shù)據(jù)檢查類,常用的如下

語(yǔ)法支持類

  • ONLY_FULL_GROUP_BY
    對(duì)于 GROUP BY 聚合操作,如果在 SELECT 中的列、HAVING 或者 ORDER BY 子句的列,沒(méi)有在 GROUP BY 中出現(xiàn),那么這個(gè) SQL 是不合法的
  • ANSI_QUOTES
    啟用 ANSI_QUOTES 后,不能用雙引號(hào)來(lái)引用字符串,因?yàn)樗唤忉尀樽R(shí)別符。設(shè)置它以后,update t set f1="" …,會(huì)報(bào) Unknown column ‘’ in field list 這樣的語(yǔ)法錯(cuò)誤
  • PIPES_AS_CONCAT
    把 || 視為字符串的連接操作符而非 或 運(yùn)算符,這種和 Oracle 數(shù)據(jù)庫(kù)是一樣的哈,也和字符串的拼接函數(shù) CONCAT () 有點(diǎn)類似
  • NO_TABLE_OPTIONS
    使用 SHOW CREATE TABLE 時(shí)不會(huì)輸出 MySQL 特有的語(yǔ)法部分,如 ENGINE,這個(gè)在使用 mysqldump?跨 DB 種類遷移的時(shí)候需要考慮
  • NO_AUTO_CREATE_USER
    字面意思不自動(dòng)創(chuàng)建用戶,在給 MySQL 用戶授權(quán)時(shí),我們習(xí)慣用 GRANT … ON … TO dbuser,順道一起創(chuàng)建用戶。設(shè)置該選項(xiàng)后就與 oracle 操作類似,授權(quán)之前必須先建立好用戶

1.1 數(shù)據(jù)檢查類

  • NO_ZERO_DATE

認(rèn)為日期‘0000-00-00’非法,與是否設(shè)置后面的嚴(yán)格模式有關(guān)系

1、如果設(shè)置了嚴(yán)格模式,則 NO_ZERO_DATE 自然滿足。但如果是?INSERT IGNORE 或 UPDATE IGNORE,’0000-00-00’依然允許且只顯示 warning;

2、如果在非嚴(yán)格模式下,設(shè)置了 NO_ZERO_DATE,效果與上面一樣,’0000-00-00’ 允許但顯示 warning;如果沒(méi)有設(shè)置 NO_ZERO_DATE,no warning,當(dāng)做完全合法的值;

3、NO_ZERO_IN_DATE 情況與上面類似,不同的是控制日期和天,是否可為 0 ,即 2010-01-00 是否合法;

  • NO_ENGINE_SUBSTITUTION

使用 ALTER TABLE 或 CREATE TABLE 指定 ENGINE 時(shí),需要的存儲(chǔ)引擎被禁用或未編譯,該如何處理。啟用 NO_ENGINE_SUBSTITUTION 時(shí),那么直接拋出錯(cuò)誤;不設(shè)置此值時(shí),CREATE 用默認(rèn)的存儲(chǔ)引擎替代,ATLER 不進(jìn)行更改,并拋出一個(gè) warning

  • STRICT_TRANS_TABLES

設(shè)置它,表示啟用嚴(yán)格模式。注意 STRICT_TRANS_TABLES 不是幾種策略的組合,單獨(dú)指 INSERT、UPDATE 出現(xiàn)少值或無(wú)效值該如何處理:

1、前面提到的把 ‘’ 傳給 int,嚴(yán)格模式下非法,若啟用非嚴(yán)格模式則變成 0,產(chǎn)生一個(gè) warning;

2、Out Of Range,變成插入最大邊界值;

3、當(dāng)要插入的新行中,不包含其定義中沒(méi)有顯式 DEFAULT 子句的非 NULL 列的值時(shí),該列缺少值

1.2 默認(rèn)模式

當(dāng)我們沒(méi)有修改配置文件的情況下,MySQL 是有自己的默認(rèn)模式的;版本不同,默認(rèn)模式也不同

-- 查看 MySQL 版本 SELECT VERSION(); -- 查看 sql_mode SELECT @@sql_mode;

我們可以看到,5.7.21 的默認(rèn)模式包含

ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION

而第一個(gè):ONLY_FULL_GROUP_BY 就會(huì)約束:當(dāng)我們進(jìn)行聚合查詢的時(shí)候,SELECT 的列不能直接包含非 GROUP BY 子句中的列。那如果我們?nèi)サ粼撃J?#xff08;從 “嚴(yán)格模式” 到 “寬松模式”)呢?

我們發(fā)現(xiàn),上述報(bào)錯(cuò)的 SQL

-- 寬松模式下 可以執(zhí)行 SELECT cno,cname,count(sno),MAX(sno) FROM tbl_student_class GROUP BY cno;

能正常執(zhí)行了,但是一般情況下不推薦這樣配置,線上環(huán)境往往是 “嚴(yán)格模式”,而不是 “寬松模式”;雖然案例中,無(wú)論是 “嚴(yán)格模式”,還是 “寬松模式”,結(jié)果都是對(duì)的,那是因?yàn)?cno 與 cname 唯一對(duì)應(yīng)的,如果 cno 與 cname 不是唯一對(duì)應(yīng),那么在 “寬松模式下” cname 的值是隨機(jī)的,這就會(huì)造成難以排查的問(wèn)題,有興趣的可以去試下;

二 SQL 的第二個(gè)神奇特性

2.1 問(wèn)題描述下

今天我想比較兩個(gè)數(shù)據(jù)集

表 A 一共?50,000,000?行,其中有一列叫「ID」,表 B 也有一列叫「ID」。我想查的是有 A 表里有多少 ID 在 B 表里面,數(shù)據(jù)庫(kù)用的是 snowflake,它是一種一種多租戶、事務(wù)性、安全、高度可擴(kuò)展的彈性數(shù)據(jù)庫(kù),或者叫它實(shí)施數(shù)倉(cāng)也行,具備完整的 SQL 支持和 schema-less 數(shù)據(jù)模式,支持 ACID 的事務(wù),也提供用于遍歷、展平和嵌套半結(jié)構(gòu)化數(shù)據(jù)的內(nèi)置函數(shù)和 SQL 擴(kuò)展,并支持 JSON 和 Avro 等流行格式;

用 query:

with A as (select distinct(id) as id from Table_A ), B as (select distinct(id) as id from Table_B ), result as (select * from A where id in (select id from B) ) select count(*) from result

返回結(jié)果是 26,000,000

也就是說(shuō),A 應(yīng)有 24,000,000 行不在 B 里面,對(duì)吧

可是我把第 11 行的 in 改成 not in 后,情況有點(diǎn)出乎我的意料

with A as (select distinct(id) as id from Table_A ), B as (select distinct(id) as id from Table_B ), result as (select * from A where id not in (select id from B) ) select count(*) from result

返回結(jié)果竟然是 0,而不是 24,000,000

于是我在 snowflake 論壇搜了下,發(fā)現(xiàn) 5 年前在這帖子下面有人回復(fù)到:

If you use NOT IN (subquery), it compares every returned value and in case of NULL on any side of comparison it stops immediately with non defined result if you use NOT IN (subquery), it compares every returned value and in case of NULL on any side of comparison it stops immediately with non defined result

就是說(shuō),當(dāng)你用 not in,subquery(例如上面第 11 行的 select id from B)里如果有 Null, 那么它就會(huì)立刻停止,返回未定義的結(jié)果,所以最后結(jié)果是 0;

該如何解決?很簡(jiǎn)單

2.2 去掉 null 值

在第 7 行加了限定 where id is not null 后,結(jié)果正常了

with A as (select distinct(id) as id from Table_A ), B as (select distinct(id) as id from Table_B where id is not null ), result as (select * from A where id not in (select id from B) ) select count(*) from result

最終返回結(jié)果為 24,000,000,這樣就對(duì)了啊

2.3 用 not exists 代替 not in

注意第 11 行,用了 not exists 代替 not in

with A as (select distinct(id) as id from Table_A ), B as (select distinct(id) as id from Table_B where id is not null ), result as (select * from A where not exists (select * from B where A.id=B.id) ) select count(*) from result

返回結(jié)果也為 24,000,000

當(dāng)然,這肯定不是 bug 哈,而是特性,不然不會(huì)這么多年都留著,是我懂得太少了,得惡補(bǔ)下 SQL 哭泣。

不過(guò)不知道這個(gè)特性設(shè)計(jì)當(dāng)初目的是什么,如果?subquery 返回了 undefined,你好歹給我報(bào)個(gè)錯(cuò)啊。這個(gè)「特性」不僅僅在 snowflake 上面出現(xiàn),看 StackOverflow 上討論,貌似 Oracle 也是有這個(gè)「特性」的哈

三 SQL 的第三個(gè)神奇特性

像?Web?服務(wù)這樣需要快速響應(yīng)的應(yīng)用場(chǎng)景中,SQL 的性能直接決定了系統(tǒng)是否可以使用;特別在一些中小型應(yīng)用中,SQL 的性能更是決定服務(wù)能否快速響應(yīng)的唯一標(biāo)準(zhǔn)

嚴(yán)格地優(yōu)化查詢性能時(shí),必須要了解所使用數(shù)據(jù)庫(kù)的功能特點(diǎn),此外,查詢速度慢并不只是因?yàn)?SQL 語(yǔ)句本身,還有可能內(nèi)存分配不佳、文件結(jié)構(gòu)不合理、刷臟頁(yè)等其他原因啊;

因此下面介紹些 SQL 神奇特性,但不能解決所有的性能問(wèn)題,但是卻能處理很多因 SQL 寫法不合理而產(chǎn)生的性能問(wèn)題

所以下面盡量介紹一些不依賴具體數(shù)據(jù)庫(kù)實(shí)現(xiàn),使?SQL 執(zhí)行速度更快、消耗內(nèi)存更少的優(yōu)化技巧,只需調(diào)整 SQL 語(yǔ)句就能實(shí)現(xiàn)的通用的優(yōu)化 Tip

3.1 環(huán)境準(zhǔn)備

下文所講的內(nèi)容是從 SQL 層面展開(kāi)的哈,而不是針對(duì)某種特性的數(shù)據(jù)庫(kù),也就是說(shuō),下文的內(nèi)容基本上適用于任何關(guān)系型數(shù)據(jù)庫(kù)都可以;

但是,關(guān)系型數(shù)據(jù)庫(kù)那么多,逐一來(lái)演示示例了,顯然不太現(xiàn)實(shí);我們以常用的 MySQL 來(lái)進(jìn)行就行啦

MySQL 版本: 5.7.30-log ,存儲(chǔ)引擎: InnoDB

準(zhǔn)備兩張表: tbl_customer 和 tbl_recharge_record

3.2 使用高效的查詢

針對(duì)某一個(gè)查詢,有時(shí)候會(huì)有多種 SQL 實(shí)現(xiàn),例如 IN、EXISTS、連接之間的互相轉(zhuǎn)換

從理論上來(lái)講,得到相同結(jié)果的不同 SQL 語(yǔ)句應(yīng)該有相同的性能的哈,但遺憾的是,查詢優(yōu)化器生成的執(zhí)行計(jì)劃很大程度上要受到外部數(shù)據(jù)結(jié)構(gòu)的影響

所以,要想優(yōu)化查詢性能,必須知道如何寫 SQL 語(yǔ)句才能使優(yōu)化器生成更高效的執(zhí)行計(jì)劃

3.3 使用 EXISTS 代替 IN

關(guān)于 IN,相信大家都比較熟悉,使用方便,也容易理解;雖說(shuō) IN 使用方便,但它卻存在性能瓶頸

如果 IN 的參數(shù)是 1,2,3 這樣的數(shù)值列表,一般還不需要特別注意,但如果參數(shù)是子查詢,那么就需要注意了

在大多時(shí)候,?[NOT]IN?和?[NOT]EXISTS?返回的結(jié)果是相同的,但是兩者用于子查詢時(shí),EXISTS 的速度會(huì)更快一些

假設(shè)我們要查詢有充值記錄的顧客信息,SQL 該怎么寫?

相信大家第一時(shí)間想到的是 IN

IN 使用起來(lái)確實(shí)簡(jiǎn)單,也非常好理解;我們來(lái)看下它的執(zhí)行計(jì)劃

我們?cè)賮?lái)看看 EXISTS 的執(zhí)行計(jì)劃:

可以看到的是,IN 的執(zhí)行計(jì)劃中新產(chǎn)生了一張臨時(shí)表: <subquery2> ,這會(huì)導(dǎo)致效率變慢

所以通常來(lái)講,EXISTS 比 IN 更快的原因有兩個(gè)

  • 1、如果連接列(customer_id)上建立了索引,那么查詢 tbl_recharge_record 時(shí)可以通過(guò)索引查詢,而不是全表查詢
  • 2、使用 EXISTS,一旦查到一行數(shù)據(jù)滿足條件就會(huì)終止查詢,不用像使用 IN 時(shí)一樣進(jìn)行掃描全表(NOT EXISTS 也一樣)

如果當(dāng) IN 的參數(shù)是子查詢時(shí),數(shù)據(jù)庫(kù)首先會(huì)執(zhí)行子查詢,然后將結(jié)果存儲(chǔ)在一張臨時(shí)表里(內(nèi)聯(lián)視圖),然后掃描整個(gè)視圖,很多情況下這種做法非常耗費(fèi)資源

使用 EXISTS 的話,數(shù)據(jù)庫(kù)不會(huì)生成臨時(shí)表

但是從代碼的可讀性上來(lái)看,IN 要比 EXISTS 好,使用 IN 時(shí)的代碼看起來(lái)更加一目了然,易于理解

因此,如果確信使用 IN 也能快速獲取結(jié)果,就沒(méi)有必要非得改成 EXISTS 了

其實(shí)有很多數(shù)據(jù)庫(kù)也嘗試著改善了 IN 的性能

Oracle 數(shù)據(jù)庫(kù)中,如果我們?cè)谟兴饕牧猩鲜褂?IN, 也會(huì)先掃描索引

PostgreSQL 從版 本 7.4 起也改善了使用子查詢作為 IN 謂詞參數(shù)時(shí)的查詢速度

說(shuō)不定在未來(lái)的某一天,無(wú)論在哪個(gè)關(guān)系型數(shù)據(jù)庫(kù)上,IN 都能具備與 EXISTS 一樣的性能

3.4 使用連接代替 IN

其實(shí)在平時(shí)工作當(dāng)中,更多的是用連接代替 IN 來(lái)改善查詢性能,而非 EXISTS,不是說(shuō)連接更好,而是 EXISTS 很難掌握

回到問(wèn)題:查詢有充值記錄的顧客信息,如果用連接來(lái)實(shí)現(xiàn),SQL 改如何寫?

這種寫法能充分利用索引;而且,因?yàn)闆](méi)有了子查詢,所以數(shù)據(jù)庫(kù)也不會(huì)生成中間表;所以,查詢效率是不錯(cuò)的

至于 JOIN 與 EXISTS 相比哪個(gè)性能更好,這不太好說(shuō);如果沒(méi)有索引,可能 EXISTS 會(huì)略勝一籌,有索引的話,兩者都差不多

3.5 避免排序

說(shuō)到 SQL 的排序,我們第一時(shí)間想到的肯定是:ORDERBY,通過(guò)它,我們可以按指定的某些列來(lái)順序輸出結(jié)果

但是,除了 ORDERBY 顯示的排序,數(shù)據(jù)庫(kù)內(nèi)部還有很多運(yùn)算在暗中進(jìn)行排序;會(huì)進(jìn)行排序的代表性的運(yùn)算有下面這些

如果只在內(nèi)存中進(jìn)行排序,那么還好;但是如果因內(nèi)存不足而需要在硬盤上排序,那么性能就會(huì)急劇下降

所以,要盡量避免(或減少)無(wú)謂的排序,能夠大大提高查詢效率

靈活使用集合運(yùn)算符的 ALL?可選項(xiàng)

SQL 中有 UNION 、 INTERSECT 、 EXCEPT 三個(gè)集合運(yùn)算符,分表代表這集合運(yùn)算的 并集、交集、差集

默認(rèn)情況下,這些運(yùn)算符會(huì)為了排除掉重復(fù)數(shù)據(jù)而進(jìn)行排序

Using temporary 表示進(jìn)行了排序或者分組,顯然這個(gè) SQL 并沒(méi)有進(jìn)行分組,而是進(jìn)行了排序運(yùn)算

所以如果我們不在乎結(jié)果中是否有重復(fù)數(shù)據(jù),或者事先知道不會(huì)有重復(fù)數(shù)據(jù),可以使用 UNIONALL 代替 UNION 試下,可以看到,執(zhí)行計(jì)劃中沒(méi)有排序運(yùn)算了

對(duì)于 INTERSECT 和 EXCEPT 也是一樣的,加上 ALL 可選項(xiàng)后就不會(huì)進(jìn)行排序了

加上 ALL 可選項(xiàng)是一個(gè)非常有效的優(yōu)化手段,但各個(gè)數(shù)據(jù)庫(kù)對(duì)它的實(shí)現(xiàn)情況卻是參差不齊,如下圖所示

注意:Oracle 使用?MINUS?代替?EXCEPT;MySQL 壓根就沒(méi)有實(shí)現(xiàn) INTERSECT 和 EXCEPT 運(yùn)算

3.6 使用 EXISTS 來(lái)代替 DISTINCT

為了排除重復(fù)數(shù)據(jù), DISTINCT 也會(huì)進(jìn)行排序

還記得用連接代替 IN 的案例嗎,如果不用 DISTINCT

SQL:SELECT tc.*FROM tbl_recharge_record trr LEFTJOIN tbl_customer tc on trr.customer_id = tc.id

那么查出來(lái)的結(jié)果會(huì)有很多重復(fù)記錄,所以我們必須改進(jìn) SQL

SELECTDISTINCT tc.*FROM tbl_recharge_record trr LEFTJOIN tbl_customer tc on trr.customer_id = tc.id

會(huì)發(fā)現(xiàn)執(zhí)行計(jì)劃中有個(gè) Using temporary,它表示用到了排序運(yùn)算

我們使用 EXISTS 來(lái)進(jìn)行優(yōu)化

可以看到,已經(jīng)規(guī)避了排序運(yùn)算

3.7 在極值函數(shù)中使用索引

SQL 語(yǔ)言里有兩個(gè)極值函數(shù):MAX 和 MIN,使用這兩個(gè)函數(shù)時(shí)都會(huì)進(jìn)行排序

例如: SELECTMAX (recharge_amount) FROM tbl_recharge_record

會(huì)進(jìn)行全表掃描,并會(huì)進(jìn)行隱式的排序,找出單筆充值最大的金額

但是如果參數(shù)字段上建有索引,則只需掃描索引,但不需要掃描整張表

例如:SELECTMAX (customer_id) FROM tbl_recharge_record;

會(huì)通過(guò)索引: idx_c_id 進(jìn)行掃描,找出充值記錄中最大的顧客 ID

但是這種方法并不是去掉了排序這一過(guò)程,而是優(yōu)化了排序前的查找速度,從而減弱排序?qū)φw性能的影響

能寫在 WHERE 子句里的條件千萬(wàn)不要寫在 HAVING 子句里

我們來(lái)看兩個(gè) SQL 以及其執(zhí)行結(jié)果

你就明白了

從結(jié)果上來(lái)看,兩條 SQ 一樣;但是從性能上來(lái)看,第二條語(yǔ)句寫法效率更高,原因有兩個(gè):

1)減少排序的數(shù)據(jù)量

GROUP BY 子句聚合時(shí)會(huì)進(jìn)行排序,如果事先通過(guò) WHERE 子句篩選出一部分行,就能夠減輕排序的負(fù)擔(dān)了

2)有效利用索引

3.8 WHERE 子句的條件里可以使用索引

HAVING 子句是針對(duì)聚合后生成的視圖進(jìn)行篩選的,但是很多時(shí)候聚合后的視圖都沒(méi)有繼承原表的索引結(jié)構(gòu)

關(guān)于 HAVING,更多詳情可查看:神奇的 SQL 之 HAVING→ 容易被輕視的主角

在 GROUP BY 子句和 ORDER BY 子句中使用索引

一般來(lái)說(shuō),GROUP BY 子句和 ORDER BY 子句都會(huì)進(jìn)行排序

如果 GROUP BY 和 ORDER BY 的列有索引,那么可以提高查詢效率

特別是在一些數(shù)據(jù)庫(kù)中,如果列上建立的是唯一索引,那么排序過(guò)程本身都會(huì)被省略掉

  • 使用索引

使用索引是最常用的 SQL 優(yōu)化手段,這個(gè)大家都知道,怕就怕大家不知道:明明有索引,為什么查詢還是這么慢(為什么索引沒(méi)用上)

關(guān)于索引未用到的情況,可查看:神奇的 SQL 之擦肩而過(guò) → 真的用到索引了嗎,本文就不做過(guò)多闡述了

總之就是:查詢盡量往索引上靠,規(guī)避索引未用上的情況

  • 減少臨時(shí)表

在 SQL 中,子查詢的結(jié)果會(huì)被看成一張新表(臨時(shí)表),這張新表與原始表一樣,可以通過(guò) SQL 進(jìn)行操作

但是,頻繁使用臨時(shí)表會(huì)帶來(lái)兩個(gè)問(wèn)題

  • 1、臨時(shí)表相當(dāng)于原表數(shù)據(jù)的一份備份,會(huì)耗費(fèi)內(nèi)存資源
  • 2、很多時(shí)候(特別是聚合時(shí)),臨時(shí)表沒(méi)有繼承原表的索引結(jié)構(gòu)

因此,盡量減少臨時(shí)表的使用也是提升性能的一個(gè)重要方法

  • 靈活使用 HAVING 子句

對(duì)聚合結(jié)果指定篩選條件時(shí),使用 HAVING 子句是基本原則

但是如果對(duì) HAVING 不熟,我們往往找出替代它的方式來(lái)實(shí)現(xiàn),就像這樣

然而,對(duì)聚合結(jié)果指定篩選條件時(shí)不需要專門生成中間表,像下面這樣使用 HAVING 子句就可以

HAVING 子句和聚合操作都是同時(shí)執(zhí)行的,所以比起生成臨時(shí)表后再執(zhí)行 WHERE 子句,效率會(huì)更高一些,而且代碼看起來(lái)也更簡(jiǎn)潔

需要對(duì)多個(gè)字段使用 IN 謂詞時(shí),讓它們匯總到一處

SQL-92 中加入了行與行比較的功能,這樣一來(lái),比較謂詞 = 、<、> 和 IN 謂詞的參數(shù)就不再只是標(biāo)量值了,而應(yīng)是值列表了

我們來(lái)看一個(gè)示例,多個(gè)字段使用 IN 謂詞

這段代碼中用到了兩個(gè)子查詢,我們可以進(jìn)行列匯總優(yōu)化,把邏輯寫在一起

這樣一來(lái),子查詢不用考慮關(guān)聯(lián)性,而且只執(zhí)行一次就可以

還可以進(jìn)一步簡(jiǎn)化,在 IN 中寫多個(gè)字段的組合

簡(jiǎn)化后,不用擔(dān)心連接字段時(shí)出現(xiàn)的類型轉(zhuǎn)換問(wèn)題,也不會(huì)對(duì)字段進(jìn)行加工,因此可以使用索引

先進(jìn)行連接再進(jìn)行聚合

連接和聚合同時(shí)使用時(shí),先進(jìn)行連接操作可以避免產(chǎn)生中間表

合理地使用視圖

視圖是非常方便的工具,我們?cè)?strong>日常工作中經(jīng)常用到

但是,如果沒(méi)有經(jīng)過(guò)深入思考就定義復(fù)雜的視圖,可能會(huì)帶來(lái)巨大的性能問(wèn)題

特別是視圖的定義語(yǔ)句中包含以下運(yùn)算的時(shí)候,SQL 會(huì)非常低效,執(zhí)行速度也會(huì)變得非常慢

小結(jié)下

文中雖然列舉了幾個(gè)要點(diǎn),但其實(shí)優(yōu)化的核心思想只有一個(gè),那就是找出性能瓶頸所在,然后解決它;

其實(shí)不只是數(shù)據(jù)庫(kù)和 SQL,計(jì)算機(jī)世界里容易成為性能瓶頸的也是對(duì)硬盤,也就是文件系統(tǒng)的訪問(wèn)(因此可以通過(guò)增加內(nèi)存,或者使用訪問(wèn)速度更快的硬盤等方法來(lái)提升性能)

不管是減少排序還是使用索引,亦或是避免臨時(shí)表的使用,其本質(zhì)都是為了減少對(duì)硬盤的訪問(wèn)!

四 高斯數(shù)據(jù)庫(kù)特性為啥優(yōu)異

首先,能釋放 CPU 多核心的計(jì)算資源

眾所周知,軟件計(jì)算能力的提升一方面得益于 CPU 硬件能力的增強(qiáng),另一方面也得益于軟件設(shè)計(jì)層面能夠充分利用 CPU 的計(jì)算資源。當(dāng)前處理器普遍采用多核設(shè)計(jì),GaussDB (for MySQL) 單個(gè)節(jié)點(diǎn)最多可以支持 64 核的 CPU。單線程查詢的方式至多能用滿一個(gè)核的 CPU 資源,性能提升程度有限,遠(yuǎn)遠(yuǎn)無(wú)法滿足企業(yè)大數(shù)據(jù)量查詢場(chǎng)景下對(duì)降低時(shí)延的要求。因此,復(fù)雜的查詢分析型計(jì)算過(guò)程必須考慮充分利用 CPU 的多核計(jì)算資源,讓多個(gè)核參與到并行計(jì)算任務(wù)中才能大幅度提升查詢計(jì)算的處理效率;

下圖是使用 CPU 多核資源并行計(jì)算一個(gè)表的 count () 過(guò)程的例子:表數(shù)據(jù)進(jìn)行切塊后分發(fā)給多個(gè)核進(jìn)行并行計(jì)算,** 每個(gè)核計(jì)算部分?jǐn)?shù)據(jù)得到一個(gè)中間 count () 結(jié)果 **,并在最后階段將所有中間結(jié)果進(jìn)行聚合得到最終結(jié)果

然后,是并行查詢

GaussDB (for MySQL) 支持并行執(zhí)行的查詢方式,用于降低分析型查詢場(chǎng)景的處理時(shí)間,滿足企業(yè)級(jí)應(yīng)用對(duì)查詢低時(shí)延的要求。如前面所述,并行查詢的基本實(shí)現(xiàn)原理是將查詢?nèi)蝿?wù)進(jìn)行切分并分發(fā)到多個(gè) CPU 核上進(jìn)行計(jì)算,充分利用 CPU 的多核計(jì)算資源來(lái)縮短查詢時(shí)間。并行查詢的性能提升倍數(shù),理論上與 CPU 的核數(shù)正相關(guān),就是說(shuō)并行度越高能夠使用的 CPU 核數(shù)就越多,性能提升的倍數(shù)也就越高;

下圖展示的是:在 GaussDB (for MySQL) 的 64U 實(shí)例上查詢 100G 數(shù)據(jù)量的 COUNT (*) 查詢耗時(shí),不同的查詢并發(fā)度分別對(duì)應(yīng)不同耗時(shí),并發(fā)度越高對(duì)應(yīng)的查詢耗時(shí)越短

它支持多種類型的并行查詢算子,以滿足客戶各種不同復(fù)雜查詢場(chǎng)景。當(dāng)前最新版本(2021-9)已經(jīng)支持的并行查詢場(chǎng)景包括:

  • 主鍵查詢、二級(jí)索引查詢
  • 主鍵掃描、索引掃描、范圍掃描、索引等值查詢,索引逆向查詢
  • 并行條件過(guò)濾(where/having)、投影計(jì)算
  • 并行多表 JOIN(包括 HashJoin、NestLoopJoin、SemiJoin 等)查詢
  • 并行聚合函數(shù)運(yùn)算,包括 SUM/AVG/COUNT/BIT_AND/BIT_OR/BIT_XOR 等
  • 并行表達(dá)式運(yùn)算,包括算術(shù)運(yùn)算、邏輯運(yùn)算、一般函數(shù)運(yùn)算及混合運(yùn)算等
  • 并行分組 group by、排序 order by、limit/offset、distinct 運(yùn)算
  • 并行 UNION、子查詢、視圖查詢
  • 并行分區(qū)表查詢
  • 并行查詢支持的數(shù)據(jù)類型包括:整型、字符型、時(shí)間類型、浮點(diǎn)型等等
  • 其他查詢

下圖是 GaussDB (for MySQL) 并行查詢針對(duì)?TPC-H?的 22 條查詢場(chǎng)景所做的性能測(cè)試結(jié)果,測(cè)試數(shù)據(jù)量為 100G,并發(fā)線程數(shù)據(jù)是 32。下圖展示了并行查詢相比傳統(tǒng)?MySQL 單線程查詢的性能提升情況:32 并行執(zhí)行下,單表復(fù)雜查詢最高提升 26 倍性能,普遍提升 20 + 倍性能。多表 JOIN 復(fù)雜查詢最高提升近 27 倍性能,普遍提升 10 + 倍性能,子查詢性能也有較大提升;

總而言之

GaussDB (for MySQL) 并行查詢充分調(diào)用了 CPU 的多核計(jì)算資源,極大降低了分析型查詢場(chǎng)景的處理時(shí)間,大幅度提升了數(shù)據(jù)庫(kù)性能,可以很好的滿足客戶多種復(fù)雜查詢場(chǎng)景的低時(shí)延要求。

點(diǎn)擊關(guān)注,第一時(shí)間了解華為云新鮮技術(shù)~

總結(jié)

以上是生活随笔為你收集整理的天天写SQL,这些神奇的特性你知道吗?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。