視圖、觸發器、存儲過程、索引


今日內容

  • 視圖
  • 觸發器
  • 事務
  • 存儲過程
  • 內置函數
  • 流程控制
  • 索引

視圖

1、什么是視圖

​ 視圖就是通過查詢得到一張虛擬表,然后保存下來,下次直接使用即可

2、為什么要用視圖

​ 如果要頻繁使用一張虛擬表,可以不用重復查詢

3、如何用視圖

create view teacher2course as
select * from teacher inner join course on teacher.tid = course.teacher_id;

強調
1、在硬盤中,視圖只有表結構文件,沒有表數據文件
2、視圖通常是用於查詢,盡量不要修改視圖中的數據

drop view teacher2course;

思考:開發過程中會不會去使用視圖?

不會!視圖是mysql的功能,如果你的項目里面大量的使用到了視圖,那意味着你后期想要擴張某個功能的時候這個功能恰巧又需要對視圖進行修改,意味着你需要先在mysql這邊將視圖先修改一下,然后再去應用程序中修改對應的sql語句,這就涉及到跨部門溝通的問題,所以通常不會使用視圖,而是通過重新修改sql語句來擴展功能

觸發器

在滿足對某張表數據的增、刪、改的情況下,自動觸發的功能稱之為觸發器

為何要用觸發器

​ 觸發器專門針對我們對某一張表數據增insert、刪delete、改update的行為,這類行為一旦執行
​ 就會觸發觸發器的執行,即自動運行另外一段sql代碼

創建觸發器語法

# 針對插入
create trigger tri_after_insert_t1 after insert on 表名 for each row
begin
    sql代碼。。。
end 
create trigger tri_after_insert_t2 before insert on 表名 for each row
begin
    sql代碼。。。
end

# 針對刪除
create trigger tri_after_delete_t1 after delete on 表名 for each row
begin
    sql代碼。。。
end
create trigger tri_after_delete_t2 before delete on 表名 for each row
begin
    sql代碼。。。
end

# 針對修改
create trigger tri_after_update_t1 after update on 表名 for each row
begin
    sql代碼。。。
end
create trigger tri_after_update_t2 before update on 表名 for each row
begin
    sql代碼。。。
end

# 案例
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 $$  # 將mysql默認的結束符由;換成$$
create trigger tri_after_insert_cmd after insert on cmd for each row
begin
    if NEW.success = 'no' then  # 新記錄都會被MySQL封裝成NEW對象
        insert into errlog(err_cmd,err_time) values(NEW.cmd,NEW.sub_time);
    end if;
end $$
delimiter ;  # 結束之后記得再改回來,不然后面結束符就都是$$了

#往表cmd中插入記錄,觸發觸發器,根據IF的條件決定是否插入錯誤日志
INSERT INTO cmd (
    USER,
    priv,
    cmd,
    sub_time,
    success
)
VALUES
    ('egon','0755','ls -l /etc',NOW(),'yes'),
    ('egon','0755','cat /etc/passwd',NOW(),'no'),
    ('egon','0755','useradd xxx',NOW(),'no'),
    ('egon','0755','ps aux',NOW(),'yes');

# 查詢errlog表記錄
select * from errlog;
# 刪除觸發器
drop trigger tri_after_insert_cmd;

事務

什么是事務

​ 開啟一個事務可以包含一些sql語句,這些sql語句要么同時成功
​ 要么一個都別想成功,稱之為事務的原子性

事務的作用

保證了對數據操作的數據安全性

案例:用交行的卡操作建行ATM機給工商的賬戶轉錢

事務應該具有4個屬性:原子性、一致性、隔離性、持久性。這四個屬性通常稱為ACID特性

原子性(atomicity)。一個事務是一個不可分割的工作單位,事務中包括的諸操作要么都做,要么都不做。

一致性(consistency)。事務必須是使數據庫從一個一致性狀態變到另一個一致性狀態。一致性與原子性是密切相關的。

隔離性(isolation)。一個事務的執行不能被其他事務干擾。即一個事務內部的操作及使用的數據對並發的其他事務是隔離的,並發執行的各個事務之間不能互相干擾。

持久性(durability)。持久性也稱永久性(permanence),指一個事務一旦提交,它對數據庫中數據的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響。

如何用

create table user(
id int primary key auto_increment,
name char(32),
balance int
);

insert into user(name,balance)
values
('wsb',1000),
('egon',1000),
('ysb',1000);

# 修改數據之前先開啟事務操作
start transaction;

# 修改操作
update user set balance=900 where name='wsb'; #買支付100元
update user set balance=1010 where name='egon'; #中介拿走10元
update user set balance=1090 where name='ysb'; #賣家拿到90元

# 回滾到上一個狀態
rollback;

# 開啟事務之后,只要沒有執行commit操作,數據其實都沒有真正刷新到硬盤
commit;
"""開啟事務檢測操作是否完整,不完整主動回滾到上一個狀態,如果完整就應該執行commit操作"""

# 站在python代碼的角度,應該實現的偽代碼邏輯,
try:
    update user set balance=900 where name='wsb'; #買支付100元
    update user set balance=1010 where name='egon'; #中介拿走10元
    update user set balance=1090 where name='ysb'; #賣家拿到90元
except 異常:
    rollback;
else:
    commit;

# 那如何檢測異常?

存儲過程

存儲過程包含了一系列可執行的sql語句,存儲過程存放於MySQL中,通過調用它的名字可以執行其內部的一堆sql

三種開發模型

第一種

"""
應用程序:只需要開發應用程序的邏輯
mysql:編寫好存儲過程,以供應用程序調用
優點:開發效率,執行效率都高
缺點:考慮到人為因素、跨部門溝通等問題,會導致擴展性差
"""

第二種

"""
應用程序:除了開發應用程序的邏輯,還需要編寫原生sql
優點:比方式1,擴展性高(非技術性的),不需要DBA
缺點:
1、開發效率,執行效率都不如方式1
2、編寫原生sql太過於復雜,而且需要考慮到sql語句的優化問題
"""

第三種

"""
應用程序:開發應用程序的邏輯,不需要編寫原生sql,基於別人編寫好的框架來處理數據,ORM
優點:不用再編寫純生sql,這意味着開發效率比方式2高,同時兼容方式2擴展性高的好處
缺點:執行效率連方式2都比不過
"""

創建存儲過程

delimiter $$
create procedure p1(
    in m int,  # in表示這個參數必須只能是傳入不能被返回出去
    in n int,  
    out res int  # out表示這個參數可以被返回出去,還有一個inout表示即可以傳入也可以被返回出去
)
begin
    select tname from teacher where tid > m and tid < n;
    set res=0;
end $$
delimiter ;

# 小知識點補充,當一張表的字段特別多記錄也很多的情況下,終端下顯示出來會出現顯示錯亂的問題
select * from mysql.user\G;

如何用存儲過程

# 大前提:存儲過程在哪個庫下面創建的只能在對應的庫下面才能使用!!!

# 1、直接在mysql中調用
set @res=10  # res的值是用來判斷存儲過程是否被執行成功的依據,所以需要先定義一個變量@res存儲10
call p1(2,4,10);  # 報錯
call p1(2,4,@res);  

# 查看結果
select @res;  # 執行成功,@res變量值發生了變化

# 2、在python程序中調用
pymysql鏈接mysql
產生的游表cursor.callproc('p1',(2,4,10))  # 內部原理:@_p1_0=2,@_p1_1=4,@_p1_2=10;
cursor.excute('select @_p1_2;')


# 3、存儲過程與事務使用舉例(了解)
delimiter //
create PROCEDURE p5(
    OUT p_return_code tinyint
)
BEGIN
    DECLARE exit handler for sqlexception
    BEGIN
        -- ERROR
        set p_return_code = 1;
        rollback;
    END;


  DECLARE exit handler for sqlwarning
  BEGIN
      -- WARNING
      set p_return_code = 2;
      rollback;
  END;

  START TRANSACTION;
      update user set balance=900 where id =1;
      update user set balance=1010 where id = 2;
      update user set balance=1090 where id =3;
  COMMIT;

  -- SUCCESS
  set p_return_code = 0; #0代表執行成功


END //
delimiter ;

函數

注意與存儲過程的區別,mysql內置的函數只能在sql語句中使用!

參考博客:http://www.cnblogs.com/linhaifeng/articles/7495918.html#_label2

CREATE TABLE blog (
    id INT PRIMARY KEY auto_increment,
    NAME CHAR (32),
    sub_time datetime
);

INSERT INTO blog (NAME, sub_time)
VALUES
    ('第1篇','2015-03-01 11:31:21'),
    ('第2篇','2015-03-11 16:31:21'),
    ('第3篇','2016-07-01 10:21:31'),
    ('第4篇','2016-07-22 09:23:21'),
    ('第5篇','2016-07-23 10:11:11'),
    ('第6篇','2016-07-25 11:21:31'),
    ('第7篇','2017-03-01 15:33:21'),
    ('第8篇','2017-03-01 17:32:21'),
    ('第9篇','2017-03-01 18:31:21');

select date_format(sub_time,'%Y-%m'),count(id) from blog group by date_format(sub_time,'%Y-%m');

流程控制

# if條件語句
delimiter //
CREATE PROCEDURE proc_if ()
BEGIN
    
    declare i int default 0;
    if i = 1 THEN
        SELECT 1;
    ELSEIF i = 2 THEN
        SELECT 2;
    ELSE
        SELECT 7;
    END IF;

END //
delimiter ;

# while循環
delimiter //
CREATE PROCEDURE proc_while ()
BEGIN

    DECLARE num INT ;
    SET num = 0 ;
    WHILE num < 10 DO
        SELECT
            num ;
        SET num = num + 1 ;
    END WHILE ;

END //
delimiter ;

索引與慢查詢優化

知識回顧:數據都是存在硬盤上的,那查詢數據不可避免的需要進行IO操作

索引在MySQL中也叫做“鍵”,是存儲引擎用於快速找到記錄的一種數據結構。

  • primary key
  • unique key
  • index key

注意foreign key不是用來加速查詢用的,不在我們研究范圍之內,上面三種key前兩種除了有加速查詢的效果之外還有額外的約束條件(primary key:非空且唯一,unique key:唯一),而index key沒有任何約束功能只會幫你加速查詢

索引就是一種數據結構,類似於書的目錄。意味着以后再查數據應該先找目錄再找數據,而不是用翻頁的方式查詢數據

本質都是:通過不斷地縮小想要獲取數據的范圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是說,有了這種索引機制,我們可以總是用同一種查找方式來鎖定數據。

索引的影響:

  • 在表中有大量數據的前提下,創建索引速度會很慢
  • 在索引創建完畢后,對表的查詢性能會大幅度提升,但是寫的性能會降低

b+樹

https://images2017.cnblogs.com/blog/1036857/201709/1036857-20170912011123500-158121126.png

只有葉子結點存放真實數據,根和樹枝節點存的僅僅是虛擬數據

查詢次數由樹的層級決定,層級越低次數越少

一個磁盤塊兒的大小是一定的,那也就意味着能存的數據量是一定的。如何保證樹的層級最低呢?一個磁盤塊兒存放占用空間比較小的數據項

思考我們應該給我們一張表里面的什么字段字段建立索引能夠降低樹的層級高度>>> 主鍵id字段

聚集索引(primary key)

聚集索引其實指的就是表的主鍵,innodb引擎規定一張表中必須要有主鍵。先來回顧一下存儲引擎。

myisam在建表的時候對應到硬盤有幾個文件(三個)?

innodb在建表的時候對應到硬盤有幾個文件(兩個)?frm文件只存放表結構,不可能放索引,也就意味着innodb的索引跟數據都放在idb表數據文件中。

特點:葉子結點放的一條條完整的記錄

輔助索引(unique,index)

輔助索引:查詢數據的時候不可能都是用id作為篩選條件,也可能會用name,password等字段信息,那么這個時候就無法利用到聚集索引的加速查詢效果。就需要給其他字段建立索引,這些索引就叫輔助索引

特點:葉子結點存放的是輔助索引字段對應的那條記錄的主鍵的值(比如:按照name字段創建索引,那么葉子節點存放的是:{name對應的值:name所在的那條記錄的主鍵值})

select name from user where name='jason';

上述語句叫覆蓋索引:只在輔助索引的葉子節點中就已經找到了所有我們想要的數據

select age from user where name='jason';

上述語句叫非覆蓋索引,雖然查詢的時候命中了索引字段name,但是要查的是age字段,所以還需要利用主鍵才去查找

測試索引

准備

#1. 准備表
create table s1(
id int,
name varchar(20),
gender char(6),
email varchar(50)
);

#2. 創建存儲過程,實現批量插入記錄
delimiter $$ #聲明存儲過程的結束符號為$$
create procedure auto_insert1()
BEGIN
    declare i int default 1;
    while(i<3000000)do
        insert into s1 values(i,'jason','male',concat('jason',i,'@oldboy'));
        set i=i+1;
    end while;
END$$ #$$結束
delimiter ; #重新聲明 分號為結束符號

#3. 查看存儲過程
show create procedure auto_insert1\G 

#4. 調用存儲過程
call auto_insert1();

# 表沒有任何索引的情況下
select * from s1 where id=30000; #(48.21 sec)
# 避免打印帶來的時間損耗
select count(id) from s1 where id = 30000; #(46.28 sec)
select count(id) from s1 where id = 1; #(47.98 sec)

# 給id做一個主鍵
alter table s1 add primary key(id);  # 速度很慢 2 min 43.63 sec  #這里如果是使用modify會慢很多很多(可能要更改每個數據里的id)

select count(id) from s1 where id = 1;  # 速度相較於未建索引之前兩者差着數量級
#(0.11 sec)

select count(id) from s1 where name = 'jason'  # 速度仍然很慢
#(8.64 sec)


"""
范圍問題
"""
# 並不是加了索引,以后查詢的時候按照這個字段速度就一定快   
select count(id) from s1 where id > 1;  # 速度相較於id = 1慢了很多 (8.64 sec)
select count(id) from s1 where id >1 and id < 3;#(0.00 sec)
select count(id) from s1 where id > 1 and id < 10000;# (0.19 sec)
select count(id) from s1 where id != 3;#(6.73 sec)

alter table s1 drop primary key;  # 刪除主鍵 單獨再來研究name字段
select count(id) from s1 where name = 'jason';  # 又慢了

create index idx_name on s1(name);  # 給s1表的name字段創建索引
select count(id) from s1 where name = 'jason'  # 仍然很慢!!!
"""
再來看b+樹的原理,數據需要區分度比較高,而我們這張表全是jason,根本無法區分
那這個樹其實就建成了“一根棍子”
"""
select count(id) from s1 where name = 'xxx';  
# 這個會很快,我就是一根棍,第一個不匹配直接不需要再往下走了
select count(id) from s1 where name like 'xxx';
select count(id) from s1 where name like 'xxx%';
select count(id) from s1 where name like '%xxx';  # 慢 最左匹配特性

# 區分度低的字段不能建索引
drop index idx_name on s1;

# 給id字段建普通的索引
create index idx_id on s1(id);
select count(id) from s1 where id = 3;  # 快了
select count(id) from s1 where id*12 = 3;  # 慢了  索引的字段一定不要參與計算

drop index idx_id on s1;
select count(id) from s1 where name='jason' and gender = 'male' and id = 3 and email = 'xxx';
# 針對上面這種連續多個and的操作,mysql會從左到右先找區分度比較高的索引字段,先將整體范圍降下來再去比較其他條件
create index idx_name on s1(name);
select count(id) from s1 where name='jason' and gender = 'male' and id = 3 and email = 'xxx';  # 並沒有加速

drop index idx_name on s1;
# 給name,gender這種區分度不高的字段加上索引並不難加快查詢速度

create index idx_id on s1(id);
select count(id) from s1 where name='jason' and gender = 'male' and id = 3 and email = 'xxx';  # 快了  先通過id已經講數據快速鎖定成了一條了
select count(id) from s1 where name='jason' and gender = 'male' and id > 3 and email = 'xxx';  # 慢了  基於id查出來的數據仍然很多,然后還要去比較其他字段

drop index idx_id on s1

create index idx_email on s1(email);
select count(id) from s1 where name='jason' and gender = 'male' and id > 3 and email = 'xxx';  # 快 通過email字段一劍封喉 

聯合索引

select count(id) from s1 where name='jason' and gender = 'male' and id > 3 and email = 'xxx';  
# 如果上述四個字段區分度都很高,那給誰建都能加速查詢
# 給email加然而不用email字段
select count(id) from s1 where name='jason' and gender = 'male' and id > 3; 
# 給name加然而不用name字段
select count(id) from s1 where gender = 'male' and id > 3; 
# 給gender加然而不用gender字段
select count(id) from s1 where id > 3; 

# 帶來的問題是所有的字段都建了索引然而都沒有用到,還需要花費四次建立的時間
create index idx_all on s1(email,name,gender,id);  # 最左匹配原則,區分度高的往左放
select count(id) from s1 where name='jason' and gender = 'male' and id > 3 and email = 'xxx';  # 速度變快

總結:上面這些操作,你感興趣可以敲一敲,不感興趣你就可以不用敲了,權當看個樂呵。理論掌握了就行了

慢查詢日志

設定一個時間檢測所有超出改時間的sql語句,然后針對性的進行優化!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM