大數(shù)據(jù)分析工程師入門4-SQL進階
數(shù)據(jù)分析工作中,免不了與SQL數(shù)據(jù)庫打交道,尤其是對庫表的使用,所以如何對庫表進行創(chuàng)建、修改和刪除,是一項基礎(chǔ)技能。
DDL(DataDefinition Language的簡寫形式)是SQL語言集中負責數(shù)據(jù)結(jié)構(gòu)定義與數(shù)據(jù)庫對象定義的語言,由CREATE、ALTER與DROP三個語法所組成。
接下來分為庫、表兩部分給出示例代碼供讀者學習了解。
- 庫相關(guān)操作
- # 創(chuàng)建數(shù)據(jù)庫
- CREATE DATABASE testdatabase;
- # 選擇數(shù)據(jù)庫
- USE testdatabase;
- # 刪除數(shù)據(jù)庫
- DROP DATABASE testdatabase;
- # 列出已有數(shù)據(jù)庫
- SHOW DATABASES;
以上操作語句如果是在命令行中執(zhí)行,需注意要以分號結(jié)束。
CREATE DATABASE關(guān)鍵字后面的testdatabase就是新創(chuàng)建的庫名,庫名需要是唯一的,也就意味著不能和已經(jīng)存在的庫重名。USE testdatabase命令的作用就是切換到testdatabase庫下進行后續(xù)操作。SHOW DATABASES會列出所有當前用戶能訪問到的數(shù)據(jù)庫庫名。
- 表相關(guān)操作
- # 創(chuàng)建表
- CREATE TABLE test1 (
- id INT unsigned NOT NULL AUTO_INCREMENT,
- name VARCHAR(225),
- price DECIMAL(10,2),
- PRIMARY KEY (id)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- # 刪除表
- DROP TABLE test1;
- # 修改表字段類型
- ALTER TABLE test1 MODIFY name VARCHAR(100);
- # 添加表字段
- ALTER TABLE test1 ADD COLUMN age INT(3) FIRST;
- # 刪除表字段
- ALTER TABLE test1 DROP age;
- # 修改表字段名稱
- ALTER TABLE test1 CHANGE age age2 INT(20);
- # 表重命名
- ALTER TABLE test1 RENAME test2;
- # 查看表結(jié)構(gòu)
- DESC test2;
同樣,以上操作語句如果是在命令行中執(zhí)行,需注意要以分號結(jié)束。
CREATE TABLE 的時候,要求新指定的表名必須不存在,否則會出錯,這主要是為了防止意外覆蓋已有的表。
ALTER TABLE后面給出的要更改信息的表名必須存在,否則將報錯。使用ALTERTABLE要極為小心,應該在進行改動前做完整的備份(表結(jié)構(gòu)和數(shù)據(jù)的備份),增加列會對數(shù)據(jù)存儲造成影響,因此要盡量避免此類操作。
類似地,如果刪除了不應該刪除的列,可能會丟失該列中的所有數(shù)據(jù)。刪除表操作同樣無法撤銷,所以執(zhí)行該操作之前需十分謹慎小心。
另外和大家分享下,工作中常用的建表小技巧:
1. 創(chuàng)建表時,盡量使用一個自增的整型字段做主鍵。這樣做,如果后續(xù)需要使用spark等框架分析這個表時,是非常方便的。
2. 創(chuàng)建表時,可以增加兩個字段create_time和update_time。create_time存儲記錄的創(chuàng)建時間,update_time存儲記錄的最后一次變更時間,方便后續(xù)排查數(shù)據(jù)的變更情況。如果是使用MySQL,需要5.7及以上版本,具體語法示例如下:
- CREATE TABLE test1 (
- id INT unsigned NOT NULL AUTO_INCREMENT,
- name VARCHAR(225),
- price DECIMAL(10,2),
- create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- update_time TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- PRIMARY KEY (id)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3. 可以考慮增加一個邏輯刪除列,存儲記錄的生效狀態(tài)。這樣在刪除數(shù)據(jù)時,可以進行邏輯刪除,即把狀態(tài)為改為失效,而不是真的把數(shù)據(jù)刪掉。
MySQL索引的建立對于MySQL的高效運行是很重要的,索引可以大大提高MySQL的檢索速度。主要分為兩種類型,單列索引和組合索引。
接下來,我們一起來看下如何創(chuàng)建不同類型的索引:
- 建表時創(chuàng)建
語法:
- CREATE TABLE 表名(
- 字段名 數(shù)據(jù)類型 [完整性約束條件],
- ……,
- [UNIQUE | FULLTEXT | SPATIAL] INDEX | KEY
- [索引名](字段名1 [(長度)] [ASC | DESC]) [USING 索引方法]
- );
- 說明:
- UNIQUE:可選。表示索引為唯一性索引。
- FULLTEXT:可選。表示索引為全文索引。
- SPATIAL:可選。表示索引為空間索引。
- INDEX和KEY:用于指定字段為索引,兩者作用是一樣的。
- 索引名:可選。給創(chuàng)建的索引取一個新名稱。
- 字段名1:指定索引對應的字段的名稱,該字段必須是前面定義好的字段。
- 長度:可選。指索引的長度,必須是字符串類型才可以使用。
- ASC:可選。表示升序排列。
- DESC:可選。表示降序排列。
- BTree是最常見的索引方法,所有值(被索引的列)都是排過序的,每個葉節(jié)點到根節(jié)點距離相等。所以BTree適合用來查找某一范圍內(nèi)的數(shù)據(jù),而且可以直接支持數(shù)據(jù)排序(ORDER BY)。還有其他幾種索引方法,讀者可自行百度了解一下。
建表時創(chuàng)建單列索引和組合索引示例:
- CREATE TABLE classInfo(
- id INT AUTO_INCREMENT COMMENT 'id',
- classname VARCHAR(128) COMMENT '課程名稱',
- classid INT COMMENT '課程id',
- classtype VARCHAR(128) COMMENT '課程類型',
- classcode VARCHAR(128) COMMENT '課程代碼',
- -- 主鍵本身也是一種索引
- PRIMARY KEY (id),
- -- 給classid字段創(chuàng)建了唯一索引(注:也可以在上面創(chuàng)建字段時使用unique來創(chuàng)建唯一索引)
- UNIQUE INDEX (classid),
- -- 給classname字段創(chuàng)建普通索引
- INDEX (classname),
- -- 創(chuàng)建組合索引
- INDEX (classtype,classcode)
- -- 指定使用INNODB存儲引擎(該引擎支持事務)、utf8字符編碼
- ) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT '課程明細表';
- 建表后創(chuàng)建
語法:
- ALTER TABLE 表名 ADD [UNIQUE| FULLTEXT | SPATIAL] INDEX | KEY [索引名] (字段名1 [(長度)] [ASC | DESC]) [USING 索引方法];
- 或
- CREATE [UNIQUE | FULLTEXT | SPATIAL] INDEX 索引名 ON 表名(字段名) [USING 索引方法]
建表后創(chuàng)建單列索引和組合索引示例:
- --將id列設(shè)置為主鍵
- ALTER TABLE classInfo ADD PRIMARY KEY(id) ;
- --給classInfo表中的classid創(chuàng)建唯一索引
- ALTER TABLE classInfo ADD UNIQUE INDEX (classid);
- --給classInfo表中的classname創(chuàng)建普通索引
- ALTER TABLE classInfo ADD INDEX (classname);
- --給classInfo表中的classtype和classcode創(chuàng)建組合索引
- ALTER TABLE classInfo ADD INDEX (classtype,classcode);
索引建立以后,來看下如何對索引進行查看和刪除操作。
查看:
- show index from classInfo;
結(jié)果:
刪除:
- DROP INDEX 索引名 ON 表名
- 或
- ALTER TABLE 表名 DROPINDEX 索引名
示例:
- drop index classname on classInfo;
- alter table classInfo drop index classid;
索引的優(yōu)點:
- 大大加快數(shù)據(jù)的查詢速度
- 使用索引字段分組和排序進行數(shù)據(jù)查詢時,可以顯著減少查詢時分組和排序的時間
- 創(chuàng)建唯一索引,能夠保證數(shù)據(jù)庫表中每一行數(shù)據(jù)的唯一性
- 在實現(xiàn)數(shù)據(jù)的參考完整性方面,可以加速表和表之間的連接
索引的缺點:
- 創(chuàng)建索引和維護索引需要消耗時間,并且隨著數(shù)據(jù)量的增加,時間也會增加
- 索引需要占據(jù)磁盤空間
- 對數(shù)據(jù)表中的數(shù)據(jù)進行增加,修改,刪除時,索引也要動態(tài)的維護,降低了維護的速度
創(chuàng)建索引的原則:
- 更新頻繁的列不應設(shè)置索引
- 數(shù)據(jù)量小的表不要使用索引(畢竟總共2頁的文檔,還要目錄嗎?)
- 重復數(shù)據(jù)多的字段不應設(shè)為索引(比如性別,只有男和女,一般來說:重復的數(shù)據(jù)超過百分之十五就不適合建索引)
- 首先應該考慮對where 和 order by 使用的列上建立索引
如果一個SQL執(zhí)行緩慢,遠低于預期,我們該怎么去優(yōu)化它呢?
關(guān)于這個問題,MySQL提供了一個explain命令,它可以對select語句進行分析,并輸出SQL執(zhí)行的詳細過程和細節(jié)信息,以供開發(fā)人員進行針對性的優(yōu)化。
explain的語法很簡單,首先我們通過一個簡單的sql查詢來了解一下:
- explain select * from user_info where id = 2
其返回結(jié)果如下:
返回的每一個字段代表什么意思呢?
簡單總結(jié)一下:
- id: SELECT 查詢的標識符. 每個 SELECT 都會自動分配一個唯一的標識符.
- select_type: SELECT 查詢的類型.
- table: 查詢的是哪個表.
- type: 訪問類型.
- possible_keys: 此次查詢中可能選用的索引.
- key: 此次查詢中確切使用到的索引,如果沒有選擇索引,鍵是NULL.
- key_len:表示查詢優(yōu)化器使用了索引的字節(jié)數(shù). 這個字段可以評估組合索引是否完全被使用, 或只有最左部分字段被使用到,如果鍵是NULL,則長度為NULL。
- ref: 哪個字段或常數(shù)與key一起被使用.
- rows: 顯示此查詢一共掃描了多少行. 這個是一個估計值.
- extra: 額外的信息.
以上各個字段中,我們來重點講解下select_type、type和extra,其他字段通過以上注釋相信大家已經(jīng)基本能夠理解其含義了。
- select_type
表示查詢的類型,它的常用取值有:
(1)SIMPLE,表示此查詢不包含 UNION 查詢或子查詢。示例見上文。
(2)PRIMARY,表示此查詢是最外層的查詢;
DEPENDENT UNION,子查詢UNION語句的第二個或后面的SELECT,取決于外面的查詢, 即子查詢依賴于外層查詢的結(jié)果;
DEPENDENT SUBQUERY,子查詢中的第一個 SELECT,取決于外面的查詢,即子查詢依賴于外層查詢的結(jié)果;
UNION RESULT, UNION 語句的結(jié)果集;
示例代碼如下,相同顏色標示sql語句與select_type值的相對應。
(3)UNION, 表示此查詢是使用UNION語句的第二個或后面的SELECT
(4)SUBQUERY, 子查詢中的第一個 SELECT
那么DEPENDENT UNION和UNION, DEPENDENT SUBQUERY與SUBQUERY之間有什么區(qū)別呢?
顧名思義,關(guān)鍵點就在于DEPENDENT了,它的作用在于標示子查詢依賴于外層查詢的結(jié)果。
在以上第(2)點示例中,內(nèi)部“student_info.user_id=user_info.id” 與“order_info.user_id=user_info.id”條件會自動添加到UNION所使用的SELECT查詢的WHERE條件,然后再執(zhí)行。
由于外部定義的user_info數(shù)據(jù)表的id數(shù)據(jù)列要在子查詢中使用,所以DEPENDENT UNION和DEPENDENT SUBQUERY關(guān)鍵字出現(xiàn)在select_type中。
- type
type表示的是訪問類型,以上示例中,已經(jīng)出現(xiàn)了幾種type,接下來將常見type值及含義匯總一下:
|
Null >system > const > eq_ref > ref > range > index > ALL ,一般來說,得保證查詢至少達到range級別,最好能達到ref。結(jié)果值從好到壞依次是:
- extra
EXPLAIN 中的很多額外的信息會在 Extra 字段顯示, 常見的是以下四種:
|
本章節(jié)之前給出的示例中,有出現(xiàn)Using index和Using where,關(guān)于另外兩種的使用示例讀者感興趣的話可上網(wǎng)百度了解一下,這里就不再繼續(xù)舉例說明了。
能夠看懂explain的輸出,是對SQL或表結(jié)構(gòu)進行優(yōu)化的前提。所以,大家需要首先看懂并理解explain輸出內(nèi)容所包含的信息,進而優(yōu)化實現(xiàn)更加高效的查詢。
主從同步,簡單來說就是將一個服務器上的數(shù)據(jù)同步到另一個服務器上。
數(shù)據(jù)所在的服務器被稱為主服務器(Master),接受數(shù)據(jù)拷貝的服務器被稱為從服務器(Slave)。
主從同步主要有以下好處:
- 數(shù)據(jù)備份:主服務器上的數(shù)據(jù)出現(xiàn)問題后,可通過從服務器數(shù)據(jù)進行恢復;
- 提高主服務器的性能:在主服務器上生成實時數(shù)據(jù),而在從服務器上分析這些數(shù)據(jù);
- 提高整個數(shù)據(jù)庫服務的性能:在主服務器上執(zhí)行寫入和更新,在從服務器上向外提供讀功能,可以動態(tài)地調(diào)整從服務器的數(shù)量,從而提升整個數(shù)據(jù)庫的性能;
為什么要單獨強調(diào)一下mysql的主從同步機制,那是因為在實際數(shù)據(jù)分析工作中,當我們通過hive、spark等分布式框架去訪問mysql數(shù)據(jù)庫的時候,此時的分布式讀取會對服務器產(chǎn)生很大的壓力,如果直接讀取主庫的話,極可能會導致正在運行的主庫線上任務暫停幾分鐘,進而對線上業(yè)務造成不良影響。
所以,一般建議盡量通過從庫進行數(shù)據(jù)讀取,避免對線上服務造成損害。
由于主從同步相關(guān)操作平時都是運維或者DBA他們在維護,作為數(shù)據(jù)分析人員很少會需要直接實現(xiàn)這些,所以這里對如何實現(xiàn)主從同步等相關(guān)知識點就不展開細講,感興趣的小伙伴,可參考一下鏈接,了解一下。
https://blog.csdn.net/qq_15092079/article/details/81672920
上面提到主從同步是一種實時的數(shù)據(jù)備份方案,通常我們還會定時去對數(shù)據(jù)庫做數(shù)據(jù)備份。
其目的,是為了防止執(zhí)行一些災難性操作后,數(shù)據(jù)仍然可以恢復。
比如說,刪庫刪表。這是因為主從同步,通常是采用同步操作語句的方式,進行庫表結(jié)構(gòu)和數(shù)據(jù)拷貝的。因此,如果主庫執(zhí)行刪除數(shù)據(jù)庫或表的操作,從庫也會同步刪除。如果有定時備份的數(shù)據(jù)文件,出現(xiàn)這種情況,只需要把數(shù)據(jù)反向?qū)氲綌?shù)據(jù)庫中,就可以恢復。
mysql提供的數(shù)據(jù)備份的命令為mysqldump,通常是由DBA或者運維來進行備份操作,大家只需要知道這個知識點即可,方面平時溝通交流。
另外大家要有備份的意識,備份操作是十分必要的,相當于后悔藥。在筆者的公司,就發(fā)生過幾次數(shù)據(jù)誤刪的問題,都是通過備份完成恢復的。
show [full] processlist 能顯示用戶正在運行的線程,這在數(shù)據(jù)分析工作中對于協(xié)助故障診斷非常有幫助。
full關(guān)鍵字,可以不寫,如果加上后,會把正在執(zhí)行的SQL語句完整打印出來。
我們來直接執(zhí)行一下看看能返回哪些信息:
結(jié)果中的每個字段含義總結(jié)如下:
|
有一種情況,需要大家重點注意下,就是Command中出現(xiàn)Waiting for ... lock字眼時,表示有語句把庫或表給鎖住了。
通常這個時候,相關(guān)的操作庫或表的程序就會處于假死狀態(tài),表現(xiàn)為程序卡住不動。這時就需要聯(lián)系DBA或運維看下是什么原因?qū)е骆i庫或鎖表。所以,在你排查程序假死的問題時,如果程序有使用數(shù)據(jù)庫,可以考慮是不是這個因素導致的。
注:show processlist 顯示的信息都是來自MySQL系統(tǒng)庫 information_schema 中的 processlist 表。所以使用下面的查詢語句可以獲得相同的結(jié)果:
- select * from information_schema.processlist
數(shù)據(jù)分析工作中的常用操作:
1.按客戶端 IP 分組,看哪個客戶端的鏈接數(shù)最多
- select client_ip,count(client_ip) as client_num
- from (
- selectsubstring_index(host,':' ,1) as client_ip
- fromprocesslist ) as connect_info
- group by client_ip
- order by client_num desc;
2.查看正在執(zhí)行的線程,并按 Time 倒排序,看看有沒有執(zhí)行時間特別長的線程
- select *
- from information_schema.processlist
- where Command != 'Sleep'
- order by Time desc;
3.找出所有執(zhí)行時間超過 5 分鐘的線程,拼湊出 kill 語句,方便后面查殺
- select concat('kill ', id, ';')
- from information_schema.processlist
- where Command != 'Sleep' and Time > 300
- order by Time desc;
MySQL默認設(shè)置下,一個連接最長等待時間為8小時,如果8小時都處于空閑狀態(tài),就會出現(xiàn)連接超時問題,在使用MySQL時相信大家或多或少都會遇到這種狀況,這里跟大家分享下在遇到這種情況時,通常采取的措施和解決辦法。
首先,查看問題,看下wait_timeout的取值。
打開MySQL的控制臺,運行showvariables like '%timeout%',查看和連接時間有關(guān)的MySQL系統(tǒng)變量。
然后,解決問題,解決方式常用的有三種。
1. 增加 MySQL 的 wait_timeout 屬性的值
- //修改mysql配置文件,重啟后生效
- wait_timeout = 31536000
- or
- //通過mysql命令修改
- mysql> set wait_timeout= 31536000;
2. 減少連接池內(nèi)連接的生存周期
通過代碼配置,讓線程在mysql提示超時前回收,并重新連接。以下舉例為c3p0連接池的配置,其他連接池(如Druid、Dbcp)原理類似。
修改 c3p0 的配置文件,在 Spring 的配置文件中設(shè)置:
- <beanid="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
- <property name="maxIdleTime"value="1800"/>
- <!--other properties -->
- </bean>
3. 定期使用連接池內(nèi)的連接
定期使用連接池內(nèi)的連接,使得它們不會因為閑置超時而被 MySQL 斷開。
修改 c3p0 的配置文件,在 Spring的配置文件中設(shè)置:
- <beanid="dataSource"class="com.mchange.v2.c3p0.ComboPooledDataSource">
- <propertyname="preferredTestQuery" value="SELECT 1"/>
- <propertyname="idleConnectionTestPeriod" value="18000"/>
- <propertyname="testConnectionOnCheckout" value="true"/>
- </bean>
普通語言里的布爾型只有 true 和 false 兩個值,這種邏輯體系被稱為二值邏輯。而 SQL 語言里,除此之外還有第三個值NULL,因此這種邏輯體系被稱為三值邏輯。
本章節(jié)對于NULL值這部分的講解重點在于提醒大家對NULL 使用比較謂詞后得到的結(jié)果總是 NULL 。
這是因為,NULL 既不是值也不是變量。NULL 只是一個表示“沒有值”的標記,而比較謂詞只適用于值。因此,對并非值的 NULL 使用比較謂詞本來就是沒有意義的。比如如下幾種比較,返回的結(jié)果均是NULL 。
- 1 = NULL
- 2 > NULL
- 3 < NULL
- 4 <> NULL
- NULL = NULL
所以,當SQL語句的where條件里有一個字段(比如age)有NULL值,用該字段用于謂詞比較判斷的時候,比如 age <> 30,表面上理解起來age字段中的NULL值跟30不等,那這個where條件返回的應該是true,從而age字段為NULL的記錄應該會被保留下來,實際上不是的,它們比較后返回的結(jié)果是NULL ,age字段為NULL的記錄會被過濾掉。
因此,要想留下NULL值,正確的寫法為,age <> 30 or age is null。在沒有學到這個知識點之前,這樣進行數(shù)據(jù)過濾容易導致提取出來的數(shù)據(jù)結(jié)果與預期有偏差。
作為上一篇sql基礎(chǔ)的補充,結(jié)合實際工作經(jīng)驗,給大家分享一下經(jīng)常用到的更深層一點的sql技能,包括有DDL、索引、EXPLAIN、主從同步、數(shù)據(jù)備份、show processlist、wait_timeout和NULL值判斷,希望大家有所收獲哦!