1. 程式人生 > >mysql高階、索引

mysql高階、索引

一.mysql高階

1.檢視

# 引子
select * from emp left join dep on emp.dep_id = dep.id
union
select * from emp right join dep on emp.dep_id = dep.id;

create view temp(emp_id,emp_name,salary,dep_id,dep_id2,dep_name,work) as 
select * from emp left join dep on emp.dep_id = dep.id
union
select * from emp right join dep on emp.dep_id = dep.id;

# 全連線形成一張新表, 現有查詢是基於全連線這張新表, 如何來操作 # 解決: 將新表其別名full_table, 將需求轉換為 select 需求欄位 from full_table 條件

什麼是檢視?

檢視是由一張表或者多張表的查詢結果構成的一張表,這張虛擬的表的作用就是為了查詢。

為什麼用檢視?

為了將複雜常用的查詢結果保留下來重複使用或者將一張大表拆分成多張小表,就是將複雜問題簡單化,提升查詢的效率。

'''
what: 檢視是由一張表或多張表的查詢結果構成的一張虛擬表
why: 將複雜常用的查詢結果保留下來重複使用 | 將一張大表拆分成多張小表

語法:
create [or replace] view 檢視名[(查詢欄位別名們)] as 查詢語句
create view new_emp as (select * from emp);

注:
1.查詢欄位別名們 要與 查詢語句的查詢欄位對應
2.create or replace: 操作檢視沒有則建立、有則替換
create or replace view new_emp(id,姓名,工資) as (select id,name,salary from emp where dep_id = 2);

檢視的修改:alter 等價於 create or replace, 且語法一致
alter view new_emp(id,姓名,工資) as (select id,name,salary from emp where dep_id = 1);

檢視中欄位的操作:不允許alter操作欄位
alter table new_emp rename new_emp1;
alter view new_emp modify id tinyint;

檢視中記錄的操作:等價於普通表,完成增刪改查
update new_emp set 姓名='san' where id = 3;
delete from new_emp where id = 3;
insert into new_emp(id, 姓名, 工資) values (10, "Bob", 10000); # 操作的是實體表, 虛擬表要重新建立才能拿到最新資料

檢視的刪除:
drop view 檢視名;

總結: 虛擬表作用 -- 查詢
'''

2.觸發器

什麼是觸發器?

在表發生資料更新時(update,delete,insert),會自動觸發的功能稱之為觸發器。

為什麼用觸發器?

當一個表在發生資料更新時,需要去完成一些額外的操作,可以為具體資料更新的方式新增觸發器。

'''
what:在表發生資料更新時,會自動觸發的功能稱之為觸發器
why:當一個表在發生資料更新時,需要去完成一些操作,可以為具體資料更新的方式新增觸發器

語法:
delimiter //
create trigger 觸發器名 before|after insert|update|delete on 表名 for each row
begin 
    需要觸發執行的sql程式碼們
end //
delimiter ;

# 觸發器名: t1_before_insert_tri

注:delimiter是用來修改sql的語句結束識別符號
刪除觸發器:drop trigger 觸發器名;
'''
# cmd表
create table cmd (
    id int primary key auto_increment,
    user char(32),
    priv char(10),
    cmd char (64),
    sub_time datetime, # 提交時間
    success enum ('yes', 'no') # 0代表執行失敗
);
# 錯誤日誌表
create table errlog (
    id int primary key auto_increment,
    err_cmd char(64),
    err_time datetime
);
# 建立觸發器
delimiter //
create trigger trigger1 after insert on cmd for each row
begin
# new就是cmd當前插入的那條記錄(物件)
if new.success = "no" then
    insert into errlog values(null, new.cmd, new.sub_time);
end if;
end //
delimiter ;

# 往表cmd中插入記錄,觸發觸發器,根據IF的條件決定是否插入錯誤日誌
insert into cmd(user, priv, cmd, sub_time, success) values
    ('egon', '0765', 'ls -l /etc', now(), 'yes'),
    ('jerry', '0852', 'cat /etc/passwd', now(), 'no'),
    ('kevin', '0867', 'useradd xxx', now(), 'no'),
    ('owen', '0912', 'ps aux', now(), 'yes');
# 檢視cmd資料資訊
select * from cmd;
# 檢視錯誤日誌表中的記錄是否有自動插入
select * from errlog;

3.事務

什麼是事務?

事務是邏輯上的一組操作,要麼都成功,要麼都失敗。

為什麼有事務?

很多時候一個數據操作,不是一個sql語句就完成的,可能有很多個sql語句,如果部分sql執行成功而部分sql執行失敗將導致資料錯亂

比如:轉賬 => 轉入轉出都成功,才能認為操作成功

'''
what:事務是邏輯上的一組操作,要麼都成功,要麼都失敗
why:很多時候一個數據操作,不是一個sql語句就完成的,可能有很多個sql語句,如果部分sql執行成功而部分sql執行失敗將導致資料錯亂
eg:轉賬 => 轉入轉出均成功,才能認為操作成功

事務的使用:
start transaction; --開啟事物,在這條語句之後的sql將處在同一事務,並不會立即修改資料庫
commit;--提交事務,讓這個事物中的sql立即執行資料的操作,
rollback;--回滾事務,取消這個事物,這個事物不會對資料庫中的資料產生任何影響

事務的四大特性:
1.原子性:事務是一組不可分割的單位,要麼同時成功,要麼同時不成功
2.一致性:事物前後的資料完整性應該保持一致(資料庫的完整性:如果資料庫在某一時間點下,所有的資料都符合所有的約束,則稱資料庫為完整性的狀態)
3.隔離性:事物的隔離性是指多個使用者併發訪問資料時,一個使用者的事物不能被其它使用者的事務所幹擾,多個併發事務之間資料要相互隔離
4.永續性:永續性是指一個事物一旦被提交,它對資料的改變就是永久性的,接下來即使資料庫發生故障也不應該對其有任何影響

事務的使用者隔離級別:
資料庫使用者可以控制資料庫工作在哪個級別下,就可與防止不同的隔離性問題
read uncommitted --不做任何隔離,可能髒讀,幻讀
read committed --可以防止髒讀,不能防止不可重複讀,和幻讀, 
Repeatable read --可以防止髒讀,不可重複讀,不能防止幻讀
Serializable --資料庫執行在序列化實現,所有問題都沒有,就是效能低

修改隔離級別:
select @@tx_isolation;--查詢當前級別 
set[session|global] transaction isolation level ....;修改級別
例項:
set global transaction isolation level Repeatable read;
注:修改後重新連線伺服器生效
'''
#準備資料
create table account(
    id int primary key auto_increment,
    name varchar(20),
    money double
);
insert into account values
    (1,'owen',10000),
    (2,'egon',1000),
    (3,'jerry',1000),
    (4,'kevin',1000);

# egon向owen借1000塊錢
# 未使用事務
update account set money = money - 1000 where id = 1;
update account set moneys = money + 1000 where id = 2; # money打錯了導致執行失敗

# 在python中使用事務處理
from pymysql.err import InternalError
sql = 'update account set money = money - 1000 where id = 1;'
sql2 = 'update account set moneys = money + 1000 where id = 2;' # money打錯了導致執行失敗
try:
    cursor.execute(sql)
    cursor.execute(sql2)
    conn.commit()
except InternalError:
    print("轉賬失敗")
    conn.rollback()

4.儲存過程

什麼是儲存過程?

用於完成指定功能的sql語句,類似於python中的函式

為什麼要儲存過程?

將指定功能的sql語句塊建立成儲存過程,不僅將sql語句邏輯化了,更是功能化了,那我們要完成相同的事,只需要重複使用建立的儲存過程,就不需要再重複書寫sql語句了。

總結:儲存過程可以讓sql語句具有複用性,從而提高開發效率。

語法:
delimiter //
create procedure 儲存過程名(
    輸入輸出型別1 引數名1 引數型別1(寬度), 
    ... ,
    輸入輸出型別n 引數名n 引數型別n(寬度)
)
begin
sql語句塊
end //
delimiter ;
注:
1.輸入輸出型別:in | out | inout
2.call 儲存過程名(實參們)來呼叫儲存過程

案例:
set @res = null; # 定義空值變數, 用來接收儲存過程的執行結果
delimiter //
create procedure user_info(in b int, in l int, out res char(20))
begin
select * from emp limit b, l;
set res = 'success';
end //
delimiter ;
call user_info(2, 3, @res); # 呼叫儲存過程, 傳入相應的實參
select @res; # 檢視儲存過程的執行結果

變數的使用:
1.賦值變數:set @變數名 = 變數值
2.使用變數:@變數名 | select @變數名
3.刪除變數:set @變數名 = null

三種開發方式:
1. 業務邏輯 + 儲存過程:高執行與開發效率,低耦合 | 不易移植,人員成本高
2. 業務邏輯 + 原生sql:人員成本低 | 開發難度大
3. 業務邏輯 + ORM:高開發效率,物件化操作資料庫,可移植 | 效能消耗加大,多表聯查、複雜條件會複製化ORM

儲存過程的操作:
1.檢視
select routine_name, routine_type from information_schema.routines where routine_schema='資料庫名';

eg: select routine_name, routine_type from information_schema.routines where routine_schema='db2';

2.刪除
drop procedure [if exists] 資料庫名.儲存過程名

5.流程控制

1.if 語句的使用

第一種 if:
"""
if 條件 then
語句;
end if;
"""
第二種 if elseif
"""
if 條件  then
語句1;
elseif 條件 then
語句2;
else 語句3;
end if;
"""
# 案例:編寫過程 實現 輸入一個整數type  範圍 1 - 2 輸出  type=1 or type=2 or type=other;
delimiter //
create procedure showType(in type int,out result char(20))
begin
if type = 1 then 
set result = "type = 1";
elseif type = 2 then 
set result = "type = 2";
else 
set result = "type = other";
end if;
end //
delimiter ;

set @res=null;
call showType(100, @res);
select @res;

2.CASE語句

大體意思與Swtich一樣的 你給我一個值 我對它進行選擇 然後執行匹配上的語句 語法:

create procedure caseTest(in type int)
begin
CASE type 
when 1  then select "type = 1";
when 2  then select "type = 2";
else select "type = other";
end case;
end

3.定義變數

declare 變數名 型別 default 值; 例如: declare i int default 0;

4.WHILE迴圈

迴圈輸出10次hello mysql
create procedure showHello()
begin 
declare i int default 0;
while  i < 10 do
select "hello mysql";
set i  = i + 1;
end while;
end

5.LOOP迴圈

沒有條件,需要自己定義結束語句

語法:

輸出十次hello mysql;
create procedure showloop()
begin 
declare i int default 0;
aloop: LOOP
select "hello loop";
set i = i + 1;
if i > 9 then leave aloop;
end if;
end LOOP aloop;
end

6.REPEAT迴圈

#類似do while
#輸出10次hello repeat
create procedure showRepeat()
begin
declare i int default 0;
repeat
select "hello repeat";
set i = i + 1;
until i > 9
end repeat;
end

#輸出0-100之間的奇數
create procedure showjishu()
begin
declare i int default 0;
aloop: loop
set i = i + 1;
if i >= 101 then leave aloop; end if;
if i % 2 = 0 then iterate aloop; end if;
select i;
end loop aloop;
end

二.mysql索引

1.什麼是索引?

在關係資料庫中,索引是一種單獨的、物理的對資料庫表中一列或多列的值進行排序的一種儲存結構; 也稱之為key

索引的作用相當於圖書的目錄,可以根據目錄中的頁碼快速找到所需的內容。

2.為什麼需要索引?

思考:一個專案正常執行後,對資料庫的操作中,哪些操作是最頻繁的? 對資料庫的寫操作(增加 刪除 修改)頻繁嗎? 對資料庫的讀操作(查詢)頻繁嗎?

相比較下,對資料的讀操作會更加頻繁,比例在10:1左右,也就是說對資料庫的查詢操作是非常頻繁的,隨著時間的推移,表中的記錄會越來越多,此時如果查詢速度太慢的話對使用者體驗是非常不利的,索引是提升查詢效率最有效的手段。簡單的說索引的就是用幫我們加快查詢速度的

需要注意的是:在資料庫中插入資料會引發索引的重建

​3.索引的實現原理

如何能實現加快查詢的效果呢?

來看一個例子:

第一版的新華字典共800頁,那時沒有檢字表,每個字的詳細資訊,隨機的羅列在書中,一同學買回來查了一次,在也沒用過,因為沒有任何的資料結構,查字只能一頁一頁往後翻,反了兩小時沒翻著,只能放棄了!

後來出版社發現了這個問題,他們將書中所有字按照拼音音節順序進行了排序,拼音首字母為a的排在最前,首字母為z的排在最後:

如此一來再不再需要一頁一頁的去查字了,而是先檢視索引,找出字的拼音首字母到索引中進行對照,例如:找字其拼音首字母為d,所以直接找到D對應的索引目錄,很快就能定位到要找的字在79頁,查詢速度得到數量級的提升! 需要注意的是,原來內容為800頁現在因為多了索引資料,整體頁數必然增加了

資料庫中的索引,實現思路與字典是一致的,需要一個獨立的儲存結構,專門儲存索引資料

本質上索引是通過不斷的縮小查詢範圍來提高查詢效率

磁碟IO問題(瞭解)

資料庫的資料最終儲存到了硬碟上

機械硬碟由於設計原理,導致查詢資料時需要有一個尋道時間與平均延遲時間,常規硬碟尋道為5ms,平均延遲按照每分鐘7200轉來計算,7200/60 = 120 ; 1000/120/2 = 4ms 總共為9ms,那麼9毫秒對於cpu而言已經非常非常的長了,足夠做很多運算操作,目前最新的處理器每秒能處理數萬億次運算,拿一個非常垃圾的處理器來舉例子,假設處理器每秒處理5億次計算,每毫秒是50萬次運算,9ms可以進行450萬次運算,資料庫中成千上萬的資料,每條資料9ms顯然慢到不行!

作業系統預讀取(瞭解)

考慮到磁碟IO是非常高昂的操作,計算機作業系統做了一些優化,當一次IO時,不僅獲取當前磁碟地址的資料,而且把相鄰的資料也都讀取到記憶體緩衝區內,因為區域性預讀性原理告訴我們,當計算機訪問一個地址的資料的時候,與其相鄰的資料也會很快被訪問到。每一次IO讀取的資料我們稱之為一頁(page)。具體一頁有多大資料跟作業系統有關,一般為4k或8k,也就是我們讀取一頁內的資料時候,實際上才發生了一次IO,這個理論對於索引的資料結構設計非常有幫助。

索引資料結構剖析

在字典的例子中我們知道了,索引是獨立於真實資料的一個儲存結構,這個結構到底是什麼樣的?

索引最終的目的是要儘可能降低io次數,減少查詢的次數,以最少的io找到需要的資料,此時B+樹閃亮登場

光有資料結構還不行,還需要有對應的演算法做支援,就是二分查詢法

有了B+資料結構後查詢資料的方式就不再是逐個的對比了,而是通過二分查詢法來查詢(流程演示)

另外,其實大多數檔案系統都是使用B+是來完成的!

應該儘可能的將資料量小的欄位作為索引

通過分析可以發現在上面的樹中,查詢一個任何一個數據都是3次IO操作, 但是這個3次並不是固定的,它取決於數結構的高度,目前是三層,如果要儲存新的資料比99還大的資料時,發現葉子節點已經不夠了必須在上面加一個子節點,由於樹根只能有一個,所以整個數的高度會增加,一旦高度增加則 查詢是IO次數也會增加,所以:應該儘可能的將資料量小的欄位作為索引,這樣一個葉子節點能儲存的資料就更多,從而降低樹的高度;例如:nameid,應當將id設定為索引而不是name

最左匹配原則*

當b+樹的資料項是複合的資料結構,比如(name,age,sex)的時候(多欄位聯合索引),b+樹會按照從左到右的順序來建立搜尋樹,比如當(張三,20,F)這樣的資料來檢索的時候,b+樹會優先比較name來確定下一步的所搜方向,如果name相同再依次比較age和sex,最後得到檢索的資料;但當(20,F)這樣的沒有name的資料來的時候,b+樹就不知道下一步該查哪個節點,因為建立搜尋樹的時候name就是第一個比較因子,必須要先根據name來搜尋才能知道下一步去哪裡查詢。比如當(張三,F)這樣的資料來檢索時,b+樹可以用name來指定搜尋方向,但下一個欄位age的缺失,所以只能把名字等於張三的資料都找到,然後再匹配性別是F的資料了, 這個是非常重要的性質,即索引的最左匹配特性。

聚集索引*

mysql官方文件原文: 插入瞭解 或摺疊

MySQL為表把它的資料詞典資訊以.frm檔案的形式存在資料庫目錄裡,這對所有MySQL儲存引擎都是真的。但 是每個InnoDB表在表空間內的InnoDB內部資料詞典裡有它自己的條目。當MySQL移除表或資料庫,它不得不 刪除.frm檔案和InnoDB資料詞典內的相應條目。這就是為什麼你不能在資料庫之間簡單地移動.frm檔案來移 動InnoDB表。

每個InnoDB表有專門索引,被稱為clustered index,對行的資料被存於其中。如果你對你的表定義一 個PRIMARY KEY, 主鍵的索引是集束索引。

如果你沒有為表定義PRIMARY KEY,MySQL拾取第一個僅有NOT NULL列的UNIQUE索引作為主鍵,並 且InnoDB把它當作集束索引來用。如果表中沒有這樣一個索引,InnoDB內部產生一個集束索引,其中 用InnoDB在這樣一個表內指定給行的行ID來排序行。行ID是一個6位元組的域,它在新行被插入的時候簡單地增加。因此被行ID排序的行是物理地按照插入順序排的。

通過集束索引訪問一個行是較快的,因為行資料是在索引搜尋引導的同一頁面。如果表是巨大的,當對比於傳 統解決方案,集束索引構架經常節約磁碟I/O。(在許多資料庫,資料傳統地被存在與索引記錄不同的頁)。

在InnoDB中,非集束索引裡的記錄(也稱為第二索引)包含對行的主鍵值。InnoDB用這個 主鍵值來從集束索 引中搜索行。注意,如果主鍵是長的,第二索引使用更多空間。

聚焦索引的特點:

  葉子節點儲存的就是完整的一行記錄,如果設定了主鍵,主鍵就作為聚集索引,

  如果沒有主鍵,則找第一個NOT NULL 且QUNIQUE的列作為聚集索引,

  如果也沒有這樣的列,innoDB會在表內自動產生一個聚集索引,它是自增的

輔助索引*

除了聚集索引之外的索引都稱之為輔助索引或第二索引,包括 foreign keyunique

輔助索引的特點:

其葉子節點儲存的是索引資料與所在行的主鍵值,InnoDB用這個 主鍵值來從聚集索引中搜查詢資料

覆蓋索引

覆蓋索引指的是需要的資料僅在輔助索引中就能找到:

#假設stu表的name欄位是一個輔助索引
select name from stu where name = "jack";

這樣的話則不需要在查詢聚集索引資料已經找到

回表

如果要查詢的資料在輔助索引中不存在,則需要回到聚集索引中查詢,這種現象稱之為回表

# name欄位是一個輔助索引 而sex欄位不是索引 
select sex from stu where name = "jack";

需要從輔助索引中獲取主鍵的值,在拿著主鍵值到聚集索引中找到sex的值

查詢速度對比:

聚集索引 > 覆蓋索引 > 非覆蓋索引

4.正確使用索引

# 首先準備一張表資料量在百萬級別
create table usr(id int,name char(10),gender char(3),email char(30));
#準備資料
delimiter //
create procedure addData(in num int)
begin 
declare i int default 0;
while  i < num do
    insert into usr values(i,"jack","m",concat("xxxx",i,"@qq.com"));    
set i  = i + 1;
end while;
end //
delimiter ;

#執行查詢語句 觀察查詢時間
select count(*) from usr where id = 1;
#1 row in set (3.85 sec)
#時間在秒級別 比較慢


1.
#新增主鍵
alter table usr add primary key(id);
#再次查詢
select count(*) from usr where id = 1;
#1 row in set (0.00 sec)
#基本在毫秒級就能完成 提升非常大

2.
#當條件為範圍查詢時
select count(*) from usr where id > 1;
#速度依然很慢 對於這種查詢沒有辦法可以優化因為需要的資料就是那麼多
#縮小查詢範圍 速度立馬就快了
select count(*) from usr where id > 1 and id < 10;




#當查詢語句中匹配欄位沒有索引時 效率測試
select count(*) from usr where name = "jack";
#1 row in set (2.85 sec)
# 速度慢


3.
# 為name欄位新增索引
create index name_index on usr(name);
# 再次查詢
select count(*) from usr where name = "jack";
#1 row in set (3.89 sec)
# 速度反而降低了 為什麼?
#由於name欄位的區分度非常低 完全無法區分 ,因為值都相同 這樣一來B+樹會沒有任何的子節點,像一根竹竿每一都匹配相當於,有幾條記錄就有幾次io ,所有要注意 區分度低的欄位不應該建立索引,不能加速查詢反而降低寫入效率,
#同理 性別欄位也不應該建立索引,email欄位更加適合建立索引

# 修改查詢語句為
select count(*) from usr where name = "aaaaaaaaa";
#1 row in set (0.00 sec) 速度非常快因為在 樹根位置就已經判斷出樹中沒有這個資料 全部跳過了

# 模糊匹配時
select count(*) from usr where name like "xxx"; #
select count(*) from usr where name like "xxx%"; #
select count(*) from usr where name like "%xxx"; #
#由於索引是比較大小 會從左邊開始匹配 很明顯所有字元都能匹配% 所以全都匹配了一遍



4.索引欄位不能參加運算
select count(*) from usr where id * 12 = 120;
#速度非常慢原因在於 mysql需要取出所有列的id 進行運算之後才能判斷是否成立
#解決方案
select count(*) from usr where id = 120/12;
#速度提升了 因為在讀取資料時 條件就一定固定了 相當於
select count(*) from usr where id = 10;
#速度自然快了

5.有多個匹配條件時 索引的執行順序  andor
#先看and
#先刪除所有的索引
alter table usr  drop primary key;
drop index name_index on usr;

#測試
select count(*) from usr where name = "jack" and gender = "m" and id = 1 and email = "[email protected]";
#1 row in set (1.34 sec) 時間在秒級 

#為name欄位新增索引
create index name_index on usr(name);
#測試
select count(*) from usr where name = "jack" and gender = "m" and id = 1 and email = "[email protected]";
#1 row in set (17.82 sec) 反而時間更長了

#為gender欄位新增索引
create index gender_index on usr(gender);
#測試
select count(*) from usr where name = "jack" and gender = "m" and id = 1 and email = "[email protected]";
#1 row in set (16.83 sec) gender欄位任然不具備區分度 

#為id加上索引
alter table usr add primary key(id);
#測試
select count(*) from usr where name = "jack" and gender = "m" and id = 1 and email = "[email protected]";
#1 row in set (0.00 sec) id子彈區分度高 速度提升
#雖然三個欄位都有索引 mysql並不是從左往右傻傻的去查 而是找出一個區分度高的欄位優先匹配

#改為範圍匹配
select count(*) from usr where name = "jack" and gender = "m" and id > 1 and email = "[email protected]";
#速度變慢了

#刪除id索引 為email建立索引
alter table usr drop primary key;
create index email_index on usr(email);
#測試
select count(*) from usr where name = "jack" and gender = "m" and id = 1 and email = "[email protected]";
#1 row in set (0.00 sec) 速度非常快

#對於or條件 都是從左往右匹配 
select count(*) from usr where name = "jackxxxx" or email = "[email protected]";

#注意 必須or兩邊都有索引才會使用索引 


6.多欄位聯合索引
為什麼需要聯合索引
案例:
select count(*) from usr where name = "jack" and gender = "m" and id  > 3 and email = "[email protected]";
假設所有欄位都是區分度非常高的欄位,那麼除看id為誰新增索引都能夠提升速度,但是如果sql語句中沒有出現所以欄位,那就無法加速查詢,最簡單的辦法是為每個欄位都加上索引,但是索引也是一種資料,會佔用記憶體空間,並且降低寫入效率
此處就可以使用聯合索引,

聯合索引最重要的是順序 按照最左匹配原則 應該將區分度高的放在左邊 區分度低的放到右邊
#刪除其他索引
drop index name_index on usr;
drop index email_index on usr;
#聯合索引
create index mul_index on usr(email,name,gender,id);
# 查詢測試
select count(*) from usr where name = "xx" and id = 1 and email = "xx";
只要語句中出現了最左側的索引(email) 無論在前在後都能提升效率 

drop index mul_index on usr;
案例