史上最全mysql刪庫跑路必會姿勢
基礎篇:MySql架構與儲存引擎
邏輯架構圖:
連線層:
mysql啟動後(可以把mysql類比為一個後臺的伺服器),等待客戶端請求,當請求到來後,mysql建立一個一個執行緒處理(執行緒池則分配一個空執行緒,當然也可使用nio執行緒模型。),每個執行緒獨立,擁有獨自記憶體空間。當請求為select請求則沒有關係,但是請求為update時,多執行緒同時修改一塊記憶體,就會引發一系列問題,由此引出 “鎖“的概念。
檢視mysql當前連線數:
show VARIABLES like '%max_connections%'
修改mysql連線數為200
set GLOBAL max_connections = 200
同時當客戶端連線到mysql伺服器時,伺服器會對其進行許可權驗證,包括,ip,使用者名稱,密碼的驗證,同時還要驗證是否有對操作某一個庫,表的許可權。
SQL處理層:
show variables like '%query_cache_type%'
SET GLOBAL query_cache_size = 188888888;
是否開啟mysql查詢結果快取,預設關閉,開啟後,mysql會對查詢出來的結果進行快取,實際應用中業務資料一般不在db層快取
mysql預設開啟sql解析快取;平時我們說的sql快取,一般指的sql解析快取。
解析查詢:
mysql對客戶端傳入的sql語句會按照一定的規則進行sql解析,而後進行sql優化,最後執行優化過的sql語句。而不是直接執行。
儲存引擎:
show engines;檢視儲存引擎
MyISAM
進入show VARIABLES like 'datadir' 檢視mysql資料檔案所在路徑,發現myisam引擎會生產三個資料檔案,xxx.frm儲存表結構檔案,所有引擎都有此檔案,xxx.MYD儲存表資料檔案,xxx.MYI儲存表索引檔案
特性:
併發性
支援表級鎖,
支援全文檢索,
支援資料檔案壓縮
試用場景:
只讀類應用;
非事物行應用(資料倉庫,報表,資料)
空間類應用(座標)
Innodb
show VARIABLES like 'innodb_file_per_table' 檢視innodb表空間型別
set global innodb_file_per_table=off 設定innodb使用系統表空間
mysql5.6以前預設使用系統表空間
系統表空間無法簡單的收縮檔案大小。
獨立表空可以通過optimize table 收縮系統檔案
系統表空間會產生io瓶頸
獨立表空間可以同時向多個檔案重新整理資料
推薦使用獨立表空間
特性:
Innodb是一種事務性儲存引擎
完全支援事物的acid特性
redo Log和Undo Log
Innodb支援行級鎖(併發度更高)
試用場景
適合大多數的oltp應用。
csv
特點:
以csv格式進行資料儲存,
所有列都不能為空,
不支援索引,
可以直接對資料檔案直接編輯(直接修改文字檔案,達到修改表的目的)。
create table mycsv(id int not null,c1 VARCHAR(10) not null,c2 char(10) not null)
engine=csv;
insert into mycsv values(1,'aaa','bbb'),(2,'cccc','dddd');
修改文字資料
flush TABLES;
select * from mycsv
create index idx_id on mycsv(id)
應用場景:
需要頻繁匯入匯出表資料的場景,如財務報表類
Archive
應用場景:日誌以及資料採集。
Memory
show VARIABLES like 'max_heap_table_size' 檢視memory引擎最大空間。
使用場景:
hash索引用於查詢或者是對映表(郵編和地區對應)
用於儲存資料分析中產生的中間表
用於快取週期性聚合資料的結果表
memory資料容易丟失,所以要求資料可再生。
Ferderted
特點:提供了遠端訪問mysql伺服器上表的方法
本地不儲存資料,資料全部放到遠端伺服器上
本地需要儲存表結構和遠端伺服器的連線資訊
使用場景:邊界資料庫,表 同步
該引擎預設禁止,啟用時需增加federated引數
表明,列名需要與遠端表相同
CREATE TABLE `local_fed` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`c1` varchar(10) NOT NULL DEFAULT '',
`c2` char(10) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=federated CONNECTION
='mysql://root:[email protected]:3306/remote/remote_fed'
應用篇:鎖,事物,索引
鎖
MyISAM表級鎖的兩種模式:table read lock 表共享讀鎖;table write Lock 表獨佔寫鎖;
加共享表級讀鎖: lock table 表名 read
1. lock table testmysam READ
啟動另外一個session select * from
testmysam 可以查詢
2. insert into testmysam value(2);
update testmysam set id=2 where id=1;
報錯
3.在另外一個session中
insert into testmysam value(2); 等待
4.在同一個session中
insert into testdemo value(2,'2','3'); 報錯
select * from testdemo ; 報錯
5.在另外一個session中
insert into testdemo value(2,'2','3'); 成功
6.加鎖在同一個session 中 select s.* from testmysam s 報錯
lock table 表名 as 別名 read;
檢視 show status LIKE 'table_locks_waited' 表被鎖過幾次
加獨佔表級寫鎖:lock table 表名 write
1.lock table testmysam WRITE
在同一個session中
insert testmysam value(3);
delete from testmysam where id = 3
select * from testmysam
2.對不同的表操作(報錯)
select s.* from testmysam s
insert into testdemo value(2,'2','3');
3.在其他session中 (等待)
select * from testmysam
Innodb行鎖
共享行鎖又稱讀鎖;當一個事務對某幾行上讀鎖時,允許其他事務對這幾行讀操作,但是不予許其進行寫操作,也不予許其他事務給這幾行上排它鎖,但允許上讀鎖。
排它鎖又稱寫鎖;當一個事務對某幾行上寫鎖時,允許其他事務對這幾行讀操作,不予許其進行寫操作,更不予許其他事務給這幾行上鎖,包括讀鎖。
注意:1.兩個事務不能鎖同一個索引。
2.insert,delete,update在事務中會預設加上排它鎖
3.行鎖必須有索引才能實現,否則會自動寫全表,那麼就不是行鎖了
1.
BEGIN
select * from testdemo where id =1 for update
在另外一個session中
update testdemo set c1 = '1' where id = 2 成功
update testdemo set c1 = '1' where id = 1 等待
2.BEGIN
update testdemo set c1 = '1' where id = 1
在另外一個session中
update testdemo set c1 = '1' where id = 1 等待
3.
BEGIN
update testdemo set c1 = '1' where c1 = '1'
在另外一個session中
update testdemo set c1 = '2' where c1 = '2' 等待
為什麼需要事務
現在的很多軟體都是多使用者,多程式,多執行緒的,對同一個表可能同時有很多人在用,為保持資料的一致性,所以提出了事務的概念。
事務的特性
事務應該具有4個屬性:原子性、一致性、隔離性、永續性。這四個屬性通常稱為ACID特性。
原子性(atomicity)。一個事務是一個不可分割的工作單位,事務中包括的諸操作要麼都做,要麼都不做。
一致性(consistency)。事務必須是使資料庫從一個一致性狀態變到另一個一致性狀態。一致性與原子性是密切相關的。
隔離性(isolation)。一個事務的執行不能被其他事務干擾。即一個事務內部的操作及使用的資料對併發的其他事務是隔離的,併發執行的各個事務之間不能互相干擾。
永續性(durability)。永續性也稱永久性(permanence),指一個事務一旦提交,它對資料庫中資料的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響。
事務隔離級別
mysql預設的事務隔離級別為repeatable-read
show variables like '%tx_isolation%';
未提交讀(READ UNCOMMITED) 解決的障礙:無; 引入的問題:髒讀
set SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
測試:
啟動兩個session
一個session中
start TRANSACTION
update account set balance = balance -50 where id = 1
另外一個session中查詢
select * from account
回到第一個session中 回滾事務
ROLLBACK
在第二個session種
update account set balance = balance -50 where id = 1
查詢結果還是 400
第二個session以為結果是350,但前面的400資料為髒讀資料,導致最後的結果和意料中的結果並不一致。
已提交讀 (READ COMMITED) 解決的障礙:髒讀; 引入的問題:不可重複讀
測試
show variables like '%tx_isolation%';
set SESSION TRANSACTION ISOLATION LEVEL read committed;
一個session中
start TRANSACTION
update account set balance = balance -50 where id = 1
另外一個session中查詢 (資料並沒改變)
select * from account
回到第一個session中 回滾事務
commit
在第二個session種
select * from account (資料已經改變)
可重複讀(REPEATABLE READ)解決的障礙:不可重複讀; 引入的問題:幻讀
測試
show variables like '%tx_isolation%';
set SESSION TRANSACTION ISOLATION LEVEL repeatable read;
一個session中
start TRANSACTION
update account set balance = balance -50 where id = 1
另外一個session中查詢 (資料並沒改變)
select * from account
回到第一個session中 回滾事務
commit
在第二個session種
select * from account (資料並未改變)
可序列化(SERIALIZABLE)解決的障礙:可重複讀; 引入的問題:鎖全表,效能低下
測試
show variables like '%tx_isolation%';
set SESSION TRANSACTION ISOLATION LEVEL repeatable read;
account 表有3條記錄,業務規定,最多允許4條記錄。
開啟一個事務
begin
select * from account 發現3條記錄
開啟另外一個事務
begin
select * from account 發現3條記錄 也是3條記錄
insert into account VALUES(4,'deer',500)
查詢 4條記錄
select * from account
回到第一個session
insert into account VALUES(5,'james',500)
select * from account 4條記錄
session1 與 session2 都提交事務
set SESSION TRANSACTION ISOLATION LEVEL serializable; 重新上面的測試發現插入報錯
總結:
事務隔離級別為可重複讀時,如果有索引(包括主鍵索引)的時候,以索引列為條件更新資料,會存在間隙鎖間、行鎖、頁鎖的問題,從而鎖住一些行;如果沒有索引,更新資料時會鎖住整張表
事務隔離級別為序列化時,讀寫資料都會鎖住整張表
隔離級別越高,越能保證資料的完整性和一致性,但是對併發效能的影響也越大,對於多數應用程式,可以優先考慮把資料庫系統的隔離級別設為Read Committed,它能夠避免髒讀取,而且具有較好的併發效能。
索引是什麼
MySQL官方對索引的定義為:索引(Index)是幫助MySQL高效獲取資料的資料結構。
可以得到索引的本質:索引是資料結構。
平時我們到圖書館,首先看到的都是目錄,通過目錄去查詢想要的書籍會非常的迅速。
我們要去圖書館找一本書,這圖書館的書肯定不是線性存放的,它對不同的書籍內容進行了分類存放,整索引由於一個個節點組成,根節點有中間節點,中間節點下面又由子節點,最後一層是葉子節點,
可見,整個索引結構是一棵倒掛著的樹,其實它就是一種資料結構,這種資料結構比前面講到的線性目錄更好的增加了查詢的速度。
MySql中的索引其實也是這麼一回事,我們可以在資料庫中建立一系列的索引,比如建立主鍵的時候預設會建立主鍵索引,上圖是一種BTREE的索引。每一個節點都是主鍵的Id
當我們通過ID來查詢內容的時候,首先去查索引庫,在到索引庫後能快速的定位索引的具體位置。
索引得分類
普通索引:即一個索引只包含單個列,一個表可以有多個單列索引
唯一索引:索引列的值必須唯一,但允許有空值
複合索引:即一個索引包含多個列
聚簇索引(聚集索引):並不是一種單獨的索引型別,而是一種資料儲存方式(索引與資料放在同一個檔案裡)。具體細節取決於不同的實現,InnoDB的聚簇索引其實就是在同一個結構中儲存了B-Tree索引(技術上來說是B+Tree)和資料行。
非聚簇索引:不是聚簇索引,就是非聚簇索引
檢視索引
SHOW INDEX FROM table_name\G
建立索引
CREATE [UNIQUE ] INDEX indexName ON mytable(columnname(length));
ALTER TABLE 表名 ADD [UNIQUE ] INDEX [indexName] ON (columnname(length))
刪除索引
DROP INDEX [indexName] ON mytable;
優化篇:慢查詢 ,執行計劃,sql優化
什麼是慢查詢
慢查詢日誌,顧名思義,就是查詢慢的日誌,是指mysql記錄所有執行超過long_query_time引數設定的時間閾值的SQL語句的日誌。該日誌能為SQL語句的優化帶來很好的幫助。預設情況下,慢查詢日誌是關閉的,要使用慢查詢日誌功能,首先要開啟慢查詢日誌功能。
慢查詢基本配置
slow_query_log 啟動停止技術慢查詢日誌
slow_query_log_file 指定慢查詢日誌得儲存路徑及檔案(預設和資料檔案放一起)
long_query_time 指定記錄慢查詢日誌SQL執行時間得伐值(單位:秒,預設10秒)
log_queries_not_using_indexes 是否記錄未使用索引的SQL
log_output 日誌存放的地方【TABLE】【FILE】【FILE,TABLE】
配置了慢查詢後,它會記錄符合條件的SQL
包括:
查詢語句
資料修改語句
已經回滾得SQL
實操:
通過下面命令檢視下上面的配置:
show VARIABLES like '%slow_query_log%'
show VARIABLES like '%slow_query_log_file%'
show VARIABLES like '%long_query_time%'
show VARIABLES like '%log_queries_not_using_indexes%'
show VARIABLES like 'log_output'
set global long_query_time=0; ---預設10秒,這裡為了演示方便設定為0
set GLOBAL slow_query_log = 1; --開啟慢查詢日誌
set global log_output='FILE,TABLE' --專案開發中日誌只能記錄在日誌檔案中,不能記表中
設定完成後,查詢一些列表可以發現慢查詢的日誌檔案裡面有資料了。
慢查詢解讀
從慢查詢日誌裡面摘選一條慢查詢日誌,資料組成如下
第一行:使用者名稱 、使用者的IP資訊、執行緒ID號
第二行:執行花費的時間【單位:毫秒】
第三行:執行獲得鎖的時間
第四行:獲得的結果行數
第五行:掃描的資料行數
第六行:這SQL執行的具體時間
第七行:具體的SQL語句
執行計劃
使用EXPLAIN關鍵字可以模擬優化器執行SQL查詢語句,從而知道MySQL是如何處理你的SQL語句的。分析你的查詢語句或是表結構的效能瓶頸。
執行計劃作用
表的讀取順序
資料讀取操作的操作型別
哪些索引可以使用
哪些索引被實際使用
表之間的引用
每張表有多少行被優化器查詢
執行計劃的語法
執行計劃的語法其實非常簡單: 在SQL查詢的前面加上EXPLAIN關鍵字就行。
比如:EXPLAIN select * from table1
重點的就是EXPLAIN後面你要分析的SQL語句
ID列
ID列:描述select查詢的序列號,包含一組數字,表示查詢中執行select子句或操作表的順序
根據ID的數值結果可以分成一下三種情況
id相同:執行順序由上至下
id不同:如果是子查詢,id的序號會遞增,id值越大優先順序越高,越先被執行
id相同不同:同時存在
分別舉例來看
如上圖所示,ID列的值全為1,代表執行的允許從t1開始載入,依次為t3與t2
EXPLAIN
select t2.* from t1,t2,t3 where t1.id = t2.id and t1.id = t3.id
and t1.other_column = '';
Id不同
如果是子查詢,id的序號會遞增,id值越大優先順序越高,越先被執行
EXPLAIN
select t2.* from t2 where id = (
select id from t1 where id = (select t3.id from t3 where t3.other_column='')
);
Id相同又不同
id如果相同,可以認為是一組,從上往下順序執行;
在所有組中,id值越大,優先順序越高,越先執行
EXPLAIN
select t2.* from (
select t3.id
from t3 where t3.other_column = ''
) s1 ,t2 where s1.id = t2.id
select_type列
Select_type:查詢的型別,
要是用於區別:普通查詢、聯合查詢、子查詢等的複雜查詢
型別如下
SIMPLE
EXPLAIN select * from t1
簡單的 select 查詢,查詢中不包含子查詢或者UNION
PRIMARY與SUBQUERY
PRIMARY:查詢中若包含任何複雜的子部分,最外層查詢則被標記為
SUBQUERY:在SELECT或WHERE列表中包含了子查詢
EXPLAIN
select t1.*,(select t2.id from t2 where t2.id = 1 ) from t1
DERIVED
在FROM列表中包含的子查詢被標記為DERIVED(衍生)
MySQL會遞迴執行這些子查詢, 把結果放在臨時表裡。
.UNION RESULT 與UNION
UNION:若第二個SELECT出現在UNION之後,則被標記為UNION;
UNION RESULT:從UNION表獲取結果的SELECT
#UNION RESULT ,UNION
EXPLAIN
select * from t1
UNION
select * from t2
table列
顯示這一行的資料是關於哪張表的
Type列
type顯示的是訪問型別,是較為重要的一個指標,結果值從最好到最壞依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
需要記憶的
system>const>eq_ref>ref>range>index>ALL
一般來說,得保證查詢至少達到range級別,最好能達到ref。
System與const
System:表只有一行記錄(等於系統表),這是const型別的特列,平時不會出現,這個也可以忽略不計
Const:表示通過索引一次就找到了
const用於比較primary key或者unique索引。因為只匹配一行資料,所以很快
如將主鍵置於where列表中,MySQL就能將該查詢轉換為一個常量
eq_ref
唯一性索引掃描,對於每個索引鍵,表中只有一條記錄與之匹配。常見於主鍵或唯一索引掃描
Ref
非唯一性索引掃描,返回匹配某個單獨值的所有行.
本質上也是一種索引訪問,它返回所有匹配某個單獨值的行,然而,它可能會找到多個符合條件的行,所以他應該屬於查詢和掃描的混合體
Range
只檢索給定範圍的行,使用一個索引來選擇行。key 列顯示使用了哪個索引
一般就是在你的where語句中出現了between、<、>、in等的查詢
這種範圍掃描索引掃描比全表掃描要好,因為它只需要開始於索引的某一點,而結束語另一點,不用掃描全部索引。
Index
當查詢的結果全為索引列的時候,雖然也是全部掃描,但是隻查詢的索引庫,而沒有去查詢
資料。
All
Full Table Scan,將遍歷全表以找到匹配的行
possible_keys 與Key
possible_keys:可能使用的key
Key:實際使用的索引。如果為NULL,則沒有使用索引
查詢中若使用了覆蓋索引,則該索引和查詢的select欄位重疊
key_len
Key_len表示索引中使用的位元組數,可通過該列計算查詢中使用的索引的長度。在不損失精確性的情況下,長度越短越好
key_len顯示的值為索引欄位的最大可能長度,並非實際使用長度,即key_len是根據表定義計算而得,不是通過表內檢索出的
key_len表示索引使用的位元組數,
根據這個值,就可以判斷索引使用情況,特別是在組合索引的時候,判斷所有的索引欄位是否都被查詢用到。
char和varchar跟字元編碼也有密切的聯絡,
latin1佔用1個位元組,gbk佔用2個位元組,utf8佔用3個位元組。(不同字元編碼佔用的儲存空間不同)
字元型別
字元型別-索引欄位為char型別+不可為Null時
name這一列為char(10),字符集為utf-8佔用3個位元組Keylen=10*3
字元型別-索引欄位為char型別+允許為Null時
name這一列為char(10),字符集為utf-8佔用3個位元組,外加需要存入一個null值
Keylen=10*3+1(null) 結果為31
索引欄位為varchar型別+不可為Null時
Keylen=varchar(n)變長欄位+不允許Null=n*(utf8=3,gbk=2,latin1=1)+2
索引欄位為varchar型別+允許為Null時
Keylen=varchar(n)變長欄位+允許Null=n*(utf8=3,gbk=2,latin1=1)+1(NULL)+2
總結
字元型別
變長欄位需要額外的2個位元組(VARCHAR值儲存時只儲存需要的字元數,另加一個位元組來記錄長度(如果列宣告的長度超過255,則使用兩個位元組),所以VARCAHR索引長度計算時候要加2),固定長度欄位不需要額外的位元組。
而NULL都需要1個位元組的額外空間,所以索引欄位最好不要為NULL,因為NULL讓統計更加複雜並且需要額外的儲存空間。
複合索引有最左字首的特性,如果複合索引能全部使用上,則是複合索引欄位的索引長度之和,這也可以用來判定複合索引是否部分使用,還是全部使用。
整數/浮點數/時間型別的索引長度
NOT NULL=欄位本身的欄位長度
NULL=欄位本身的欄位長度+1(因為需要有是否為空的標記,這個標記需要佔用1個位元組)
datetime型別在5.6中欄位長度是5個位元組,datetime型別在5.5中欄位長度是8個位元組
Ref
顯示索引的哪一列被使用了,如果可能的話,是一個常數。哪些列或常量被用於查詢索引列上的值
由key_len可知t1表的idx_col1_col2被充分使用,col1匹配t2表的col1,col2匹配了一個常量,即 'ac'
其中 【shared.t2.col1】 為 【資料庫.表.列】
Rows
根據表統計資訊及索引選用情況,大致估算出找到所需的記錄所需要讀取的行數
Extra
包含不適合在其他列中顯示但十分重要的額外資訊。
Using filesort
說明mysql會對資料使用一個外部的索引排序,而不是按照表內的索引順序進行讀取。MySQL中無法利用索引完成的排序操作稱為“檔案排序”
當發現有Using filesort 後,實際上就是發現了可以優化的地方
上圖其實是一種索引失效的情況,後面會講,可以看出查詢中用到了個聯合索引,索引分別為col1,col2,col3
當我排序新增了個col2,發現using filesort 就沒有了。
Using temporary
使了用臨時表儲存中間結果,MySQL在對查詢結果排序時使用臨時表。常見於排序 order by 和分組查詢 group by。
尤其發現在執行計劃裡面有using filesort而且還有Using temporary的時候,特別需要注意
Using index
表示相應的select操作中使用了覆蓋索引(Covering Index),避免訪問了表的資料行,效率不錯!
如果同時出現using where,表明索引被用來執行索引鍵值的查詢;
如果沒有同時出現using where,表明索引用來讀取資料而非執行查詢動作
覆蓋索引:
覆蓋索引(Covering Index),一說為索引覆蓋。
理解方式一:就是select的資料列只用從索引中就能夠取得,不必讀取資料行,MySQL可以利用索引返回select列表中的欄位,而不必根據索引再次讀取資料檔案,換句話說查詢列要被所建的索引覆蓋。
理解方式二:索引是高效找到行的一個方法,但是一般資料庫也能使用索引找到一個列的資料,因此它不必讀取整個行。畢竟索引葉子節點儲存了它們索引的資料;當能通過讀取索引就可以得到想要的資料,那就不需要讀取行了。一個索引包含了(或覆蓋了)滿足查詢結果的資料就叫做覆蓋索引
注意:
如果要使用覆蓋索引,一定要注意select列表中只取出需要的列,不可select *,
因為如果將所有欄位一起做索引會導致索引檔案過大,查詢效能下降。
所以,千萬不能為了查詢而在所有列上都建立索引,會嚴重影響修改維護的效能。
Using where 與 using join buffer
Using where
表明使用了where過濾
using join buffer
使用了連線快取:
impossible where
where子句的值總是false,不能用來獲取任何元組
sql優化順口溜
全職匹配我最愛,最左字首要遵守;
帶頭大哥不能死,中間兄弟不能斷;
索引列上少計算,範圍之後全失效;
LIKE百分寫最右,覆蓋索引不寫*;
全職匹配我最愛?
當建立了索引列後,能在where條件中使用索引的儘量所用。
最左字首要遵守,帶頭大哥不能死,中間兄弟不能斷?
如果索引了多列,要遵守最左字首法則。指的是查詢從索引的最左前列開始並且不跳過索引中的列。
聯合索引index(name,age,pos),帶頭大哥name,少了索引直接失效,使用(name,pos)丟下中間兄弟age索引失效。
索引列上少計算?
不在索引列上做任何操作(計算、函式、(自動or手動)型別轉換),會導致索引失效而轉向全表掃描
範圍之後全失效?
中間有範圍查詢會導致後面的索引列全部失效
EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' and age >22 and pos='manager'
LIKE百分寫最右?
like以萬用字元開頭('%abc...')mysql索引失效會變成全表掃描的操作
解決方式:覆蓋索引
EXPLAIN select name,age,pos from staffs where name like '%july%'
覆蓋索引不寫* ?
使用select * from 破壞了了覆蓋索引的使用條件。
補充兩個比較偏的
Null/Not 有影響
索引不可為空
在欄位為not null的情況下,使用is null 或 is not null 會導致索引失效
解決方式:覆蓋索引
EXPLAIN select name,age,pos from staffs where name is not null
索引可為空:
Is not null 的情況會導致索引失效