MySQL-視圖-觸發器-事務-存儲過程-函數-流程控制-索引與慢查詢優化-06

小科普java

核心業務邏輯代碼通常都是放在服務端python

客戶端容易被懂行點的人修改源碼,形成損失mysql

視圖***

什麼是視圖

一個查詢語句的結果是虛擬表,將(查詢出)這張虛擬表(的sql語句)保存下來,他就變成了一個視圖(mysql中仍是以表的形式存在的)linux

爲何要用視圖

當頻繁須要用到多張表的聯表結果,你就能夠事先生成好視圖,以後直接調用便可,避免了反覆寫聯表操做的 sql 語句(實際效果至關於再次執行語句)web

如何生成視圖

# 語法
create view 視圖名 as 生成虛擬表的查詢語句

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

show tables  # 便可看到該視圖(當作表了)
select * from teacher_course;  # 看到的就是

修改視圖 --> 最好(千萬)不要

關聯表數據改動前

關聯表數據改動以後

視圖中的數據自動更新了--> 執行查詢視圖記錄語句 = 從新執行了建立視圖的那個sql 語句算法

注意:sql

1.視圖只有表結構,視圖中的數據仍是來源於原來的表數據庫

2.不要改動視圖表中的數據(可能會報錯,也可能會改其餘表的數據)服務器

​ ---> 我在cmd終端改動影響到了其餘表

3.不要太依賴視圖,儘可能少用視圖來寫業務邏輯

  • 視圖會影響性能,佔用硬盤資源、數據庫資源
  • 工做中多是別的部門的人管理數據庫,跨部門交流比較麻煩,並且若是別人改動了視圖關聯表,或刪了視圖那涉及到的業務邏輯就很危險了。

觸發器

什麼是觸發器

到達某個條件自動觸發

觸發條件

當你在對數據進行增刪改的狀況下會自動觸發觸發器的運行

觸發器語法結構

修改mysql的默認結束符(;)

delimiter $$ # --> 默認 ; 改爲了 $$只對當前窗口有效,從新登陸也會無效

delimiter $$ # 改變當前窗口sql 語句的結束符
create trigger 觸發器的名字 after/before insert/update/delete on 表名 for each row
begin
    # 操做其餘表的sql 語句
end $$
delimiter ;  # 把sql結束符改回 ;

# 觸發器常見命名格式:tri_before/after_insert/update/delete_表名

觸發器死循環

觸發器裏面的代碼會觸發觸發器自己的執行,形成了死循環

在觸發器裏面千萬不要寫操做本表的語句,增刪改查都不要,會報錯

好比:user表的 新增前觸發器 代碼塊內寫的是 user表的新增插值操做,就會形成觸發器死循環(直接報錯)

六個觸發器的執行時機

新增前、新增後、刪除前、刪除後、更新前、更新後

小案例(新增後)

create table user(
    id int primary key auto_increment,
    name varchar(32) not null,
    password varchar(255) not null
)

create table log(
    id int primary key auto_increment,
    message varchar(255)
);


drop trigger tri_after_insert_user;  # 刪除已存在的觸發器 tri_after_insert_user
delimiter $$
create trigger tri_after_insert_user after insert on user for each row
begin
    # insert into user(name, password) values('老子翻車了', '123');  # 死循環了,像遞歸,不斷觸發這個觸發器
    # select * from user;  # 也會報錯,觸發器裏不能返回值 Not allowed to return a result set from a trigger
    insert into log(message) values ('看到我就說明你沒翻車');
end $$
delimiter ;

insert into user(name, password) values('會翻車嗎', '可能吧');

select * from user;
select * from log;

# 刪除觸發器
drop trigger tri_after_insert_user;

擴展: 視圖、存儲過程的查看

摘抄自:mysql查看存儲過程函數

# 查詢數據庫中的存儲過程和函數
       select `name` from mysql.proc where db = 'xx' and `type` = 'PROCEDURE'  # 存儲過程
       select `name` from mysql.proc where db = 'xx' and `type` = 'FUNCTION'   # 函數

       show procedure status\G;  # 存儲過程(\G 豎式排版查看,橫的太長了 看不清)
       show function status\G;  # 函數

# 查看存儲過程或函數的建立代碼

  show create procedure proc_name\G;
  show create function func_name\G;

# 查看視圖
  SELECT * from information_schema.VIEWS;   # 視圖
  SELECT * from information_schema.TABLES;   # 表

# 查看觸發器
  SHOW TRIGGERS [FROM db_name] [LIKE expr]
  SELECT * FROM triggers T WHERE trigger_name="mytrigger"\G;

NEW對象指代的就是當前記錄(對象)

NEW 對象能夠取到觸發這個觸發器的sql語句的記錄對象,經過 . 字段名 的方式來獲取到字段值

# 案例 (mysql大小寫不敏感)
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語句,要麼所有成功,要麼都不成功

事務的四大特性 ACID

用本身的話背下來

A:原子性 atomicity

一個事務是一個不可分割的工做單位,事務中包括的諸操做要麼都作,要麼都不作。

C:一致性 consistency

事務必須是使數據庫從一個一致性狀態變到另外一個一致性狀態。

一致性與原子性是密切相關的。

I:隔離性 isolation

一個事務的執行不能被其餘事務干擾。

即一個事務內部的操做及使用的數據對併發的其餘事務是隔離的,併發執行的各個事務之間不能互相干擾。

D:持久性 durability

持久性也稱永久性(permanence),指一個事務一旦提交,它對數據庫中數據的改變就應該是永久性的。

接下來的其餘操做或故障不該該對其有任何影響。

如何開啓事務

start transaction 標誌下面的語句都是 事務

事務開始後,只會在內存中修改

只有commit 以後纔會寫到硬盤上

事務回滾

rollback 事務開始的地方到這裏的語句都會回滾(失效)

永久性更改

commit 把數據刷在硬盤上,後面再 rollback 就回滾不回去了

start transaction、rollback、commit 有點像python異常捕獲的 try ... except ... else ...

小案例

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;

# 那如何檢測異常?

存儲過程

什麼是存儲過程

就相似於python中的自定義函數

內部封裝了 sql 語句,後續想要實現相應的操做,只須要調用存儲過程便可

如何建立存儲過程

語法結構

# 無參數版
delimiter $$  # 改mysql的結束符
create procedure 存儲結構名字()
begin
    sql 語句;
end
delimiter ;  # 改回來

call 存儲結構名字()  # 調用寫好的存儲過程


# 有參數版
delimiter $$  # 改mysql的結束符
create procedure 存儲結構名字(
    in m int,  # in 只能傳進來,不能返回
    in n int,  # 參數對應的意思---> 進仍是出, 變量名, 數據類型
    out res int,  # out 只能返回,不能傳
    inout xxx int,  # inout 能夠傳進來,也能夠被返回
)
begin
    sql 語句;
end
delimiter ;  # 改回來

call 存儲結構名字(m, n)  # 參數怎麼傳不知道。。

案例

存儲過程在哪一個庫裏定義就只能在哪一個庫裏面使用

定義存儲過程

delimiter $$

create procedure p1(
    in m int,  # in 只能傳進來,不能被返回
    in n int,  # 參數對應的意思---> 進仍是出, 變量名, 數據類型
    out res int  # out 只能被返回,不能傳入
)

begin
    select tname from teacher where tid > m and tid < n;
    set res=0;  # 就相似於一個標誌位,用來標識存儲器是否執行成功
end $$

delimiter ;

show procedure status\G;  # 查看存儲過程(豎式展現排版)

在mysql中調用存儲過程

存變量(設置初始值)

set @res=10; # 設置全局變量 @res 等於10select @res; # 查看全局變量 @res 的值

調用

call p1(1,5,@res);

將變量 @res 傳入,以後能夠經過 select @res 來查看存儲過程執行完成後的返回結果

在pymysql中調用存儲過程

# 使用的是上一步建立的存儲過程
import pymysql

conn = pymysql.connect(
    host='127.0.0.1',
    port=3306,
    user='root',
    password='000000',
    database='day38',
    charset='utf8',
    autocommit=True,
)

cursor = conn.cursor(pymysql.cursors.DictCursor)

# --------------------------------------------
# cursor.callproc() 調用存儲過程
#   內部自動用變量名存儲對應值(看下面案例註釋)
# --------------------------------------------
cursor.callproc('p1', (1, 5, 10))  # 這裏就不須要設置那個全局變量了(@res=10),內部自動用變量名存儲了對應的值
print(cursor.fetchall())
# [{'tname': '李平老師'}, {'tname': '劉海燕老師'}, {'tname': '朱雲海老師'}]

'''
callproc 內部自動用變量名存儲了對應的值

@_p1_0=1
@_p1_1=5
@_p1_2=10

# 自動取名規律: @_存儲過程名_標號
'''
cursor.execute('select @_p1_0=1')
print(cursor.fetchall())
# [{'@_p1_0=1': 1}]
cursor.execute('select @_p1_1=5')
print(cursor.fetchall())
# [{'@_p1_1=5': 1}]
cursor.execute('select @_p1_2=10')
print(cursor.fetchall())
# [{'@_p1_2=10': 0}]

案例-- 使用存儲過程監測事務

監測一個事務是否成功,一般使用存儲過程包起來

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

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 user123 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語句中使用!

參考博客:函數

MySQL內置函數

常見函數及練習

1、數學函數
    ROUND(x,y)
        返回參數x的四捨五入的有y位小數的值
        
    RAND()
        返回0到1內的隨機值,能夠經過提供一個參數(種子)使RAND()隨機數生成器生成一個指定的值。

2、聚合函數(經常使用於GROUP BY從句的SELECT查詢中)
    AVG(col)返回指定列的平均值
    COUNT(col)返回指定列中非NULL值的個數
    MIN(col)返回指定列的最小值
    MAX(col)返回指定列的最大值
    SUM(col)返回指定列的全部值之和
    GROUP_CONCAT(col) 返回由屬於一組的列值鏈接組合而成的結果    
    
3、字符串函數

    CHAR_LENGTH(str)
        返回值爲字符串str 的長度,長度的單位爲字符。一個多字節字符算做一個單字符。
    CONCAT(str1,str2,...)
        字符串拼接
        若有任何一個參數爲NULL ,則返回值爲 NULL。
    CONCAT_WS(separator,str1,str2,...)
        字符串拼接(自定義鏈接符)
        CONCAT_WS()不會忽略任何空字符串。 (然而會忽略全部的 NULL)。

    CONV(N,from_base,to_base)
        進制轉換
        例如:
            SELECT CONV('a',16,2); 表示將 a 由16進制轉換爲2進制字符串表示

    FORMAT(X,D)
        將數字X 的格式寫爲'#,###,###.##',以四捨五入的方式保留小數點後 D 位, 並將結果以字符串的形式返回。若  D 爲 0, 則返回結果不帶有小數點,或不含小數部分。
        例如:
            SELECT FORMAT(12332.1,4); 結果爲: '12,332.1000'
    INSERT(str,pos,len,newstr)
        在str的指定位置插入字符串
            pos:要替換位置其實位置
            len:替換的長度
            newstr:新字符串
        特別的:
            若是pos超過原字符串長度,則返回原字符串
            若是len超過原字符串長度,則由新字符串徹底替換
    INSTR(str,substr)
        返回字符串 str 中子字符串的第一個出現位置。

    LEFT(str,len)
        返回字符串str 從開始的len位置的子序列字符。

    LOWER(str)
        變小寫

    UPPER(str)
        變大寫
   
    REVERSE(str)
        返回字符串 str ,順序和字符順序相反。
        
    SUBSTRING(str,pos) , SUBSTRING(str FROM pos) SUBSTRING(str,pos,len) , SUBSTRING(str FROM pos FOR len)
        不帶有len 參數的格式從字符串str返回一個子字符串,起始於位置 pos。帶有len參數的格式從字符串str返回一個長度同len字符相同的子字符串,起始於位置 pos。 使用 FROM的格式爲標準 SQL 語法。也可能對pos使用一個負值。倘若這樣,則子字符串的位置起始於字符串結尾的pos 字符,而不是字符串的開頭位置。在如下格式的函數中能夠對pos 使用一個負值。

        mysql> SELECT SUBSTRING('Quadratically',5);
            -> 'ratically'

        mysql> SELECT SUBSTRING('foobarbar' FROM 4);
            -> 'barbar'

        mysql> SELECT SUBSTRING('Quadratically',5,6);
            -> 'ratica'

        mysql> SELECT SUBSTRING('Sakila', -3);
            -> 'ila'

        mysql> SELECT SUBSTRING('Sakila', -5, 3);
            -> 'aki'

        mysql> SELECT SUBSTRING('Sakila' FROM -4 FOR 2);
            -> 'ki'
            
4、日期和時間函數
    CURDATE()或CURRENT_DATE() 返回當前的日期
    CURTIME()或CURRENT_TIME() 返回當前的時間
    DAYOFWEEK(date)   返回date所表明的一星期中的第幾天(1~7)
    DAYOFMONTH(date)  返回date是一個月的第幾天(1~31)
    DAYOFYEAR(date)   返回date是一年的第幾天(1~366)
    DAYNAME(date)   返回date的星期名,如:SELECT DAYNAME(CURRENT_DATE);
    FROM_UNIXTIME(ts,fmt)  根據指定的fmt格式,格式化UNIX時間戳ts
    HOUR(time)   返回time的小時值(0~23)
    MINUTE(time)   返回time的分鐘值(0~59)
    MONTH(date)   返回date的月份值(1~12)
    MONTHNAME(date)   返回date的月份名,如:SELECT MONTHNAME(CURRENT_DATE);
    NOW()    返回當前的日期和時間
    QUARTER(date)   返回date在一年中的季度(1~4),如SELECT QUARTER(CURRENT_DATE);
    WEEK(date)   返回日期date爲一年中第幾周(0~53)
    YEAR(date)   返回日期date的年份(1000~9999)
    
    重點:
    DATE_FORMAT(date,format) 根據format字符串格式化date值

       mysql> SELECT DATE_FORMAT('2009-10-04 22:23:00', '%W %M %Y');
        -> 'Sunday October 2009'
       mysql> SELECT DATE_FORMAT('2007-10-04 22:23:00', '%H:%i:%s');
        -> '22:23:00'
       mysql> SELECT DATE_FORMAT('1900-10-04 22:23:00',
        ->                 '%D %y %a %d %m %b %j');
        -> '4th 00 Thu 04 10 Oct 277'
       mysql> SELECT DATE_FORMAT('1997-10-04 22:23:00',
        ->                 '%H %k %I %r %T %S %w');
        -> '22 22 10 10:23:00 PM 22:23:00 00 6'
       mysql> SELECT DATE_FORMAT('1999-01-01', '%X %V');
        -> '1998 52'
       mysql> SELECT DATE_FORMAT('2006-06-00', '%d');
        -> '00'
        
5、加密函數
    MD5()    
        計算字符串str的MD5校驗和
    PASSWORD(str)   
        返回字符串str的加密版本,這個加密過程是不可逆轉的,和UNIX密碼加密過程使用不一樣的算法。
        
6、控制流函數            
    CASE WHEN[test1] THEN [result1]...ELSE [default] END
        若是testN是真,則返回resultN,不然返回default
    CASE [test] WHEN[val1] THEN [result]...ELSE [default]END  
        若是test和valN相等,則返回resultN,不然返回default

    IF(test,t,f)   
        若是test是真,返回t;不然返回f

    IFNULL(arg1,arg2) 
        若是arg1不是空,返回arg1,不然返回arg2

    NULLIF(arg1,arg2) 
        若是arg1=arg2返回NULL;不然返回arg1        
        
7、控制流函數小練習
#7.一、準備表
/*
Navicat MySQL Data Transfer

Source Server         : localhost_3306
Source Server Version : 50720
Source Host           : localhost:3306
Source Database       : student

Target Server Type    : MYSQL
Target Server Version : 50720
File Encoding         : 65001

Date: 2018-01-02 12:05:30
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for course
-- ----------------------------
DROP TABLE IF EXISTS `course`;
CREATE TABLE `course` (
  `c_id` int(11) NOT NULL,
  `c_name` varchar(255) DEFAULT NULL,
  `t_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`c_id`),
  KEY `t_id` (`t_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of course
-- ----------------------------
INSERT INTO `course` VALUES ('1', 'python', '1');
INSERT INTO `course` VALUES ('2', 'java', '2');
INSERT INTO `course` VALUES ('3', 'linux', '3');
INSERT INTO `course` VALUES ('4', 'web', '2');

-- ----------------------------
-- Table structure for score
-- ----------------------------
DROP TABLE IF EXISTS `score`;
CREATE TABLE `score` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `s_id` int(10) DEFAULT NULL,
  `c_id` int(11) DEFAULT NULL,
  `num` double DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of score
-- ----------------------------
INSERT INTO `score` VALUES ('1', '1', '1', '79');
INSERT INTO `score` VALUES ('2', '1', '2', '78');
INSERT INTO `score` VALUES ('3', '1', '3', '35');
INSERT INTO `score` VALUES ('4', '2', '2', '32');
INSERT INTO `score` VALUES ('5', '3', '1', '66');
INSERT INTO `score` VALUES ('6', '4', '2', '77');
INSERT INTO `score` VALUES ('7', '4', '1', '68');
INSERT INTO `score` VALUES ('8', '5', '1', '66');
INSERT INTO `score` VALUES ('9', '2', '1', '69');
INSERT INTO `score` VALUES ('10', '4', '4', '75');
INSERT INTO `score` VALUES ('11', '5', '4', '66.7');

-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
  `s_id` varchar(20) NOT NULL,
  `s_name` varchar(255) DEFAULT NULL,
  `s_age` int(10) DEFAULT NULL,
  `s_sex` char(1) DEFAULT NULL,
  PRIMARY KEY (`s_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES ('1', '魯班', '12', '男');
INSERT INTO `student` VALUES ('2', '貂蟬', '20', '女');
INSERT INTO `student` VALUES ('3', '劉備', '35', '男');
INSERT INTO `student` VALUES ('4', '關羽', '34', '男');
INSERT INTO `student` VALUES ('5', '張飛', '33', '女');

-- ----------------------------
-- Table structure for teacher
-- ----------------------------
DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher` (
  `t_id` int(10) NOT NULL,
  `t_name` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`t_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of teacher
-- ----------------------------
INSERT INTO `teacher` VALUES ('1', '大王');
INSERT INTO `teacher` VALUES ('2', 'alex');
INSERT INTO `teacher` VALUES ('3', 'egon');
INSERT INTO `teacher` VALUES ('4', 'peiqi');

#7.二、統計各科各分數段人數.顯示格式:課程ID,課程名稱,[100-85],[85-70],[70-60],[ <60]

select  score.c_id,
          course.c_name, 
      sum(CASE WHEN num BETWEEN 85 and 100 THEN 1 ELSE 0 END) as '[100-85]',
      sum(CASE WHEN num BETWEEN 70 and 85 THEN 1 ELSE 0 END) as '[85-70]',
      sum(CASE WHEN num BETWEEN 60 and 70 THEN 1 ELSE 0 END) as '[70-60]',
      sum(CASE WHEN num < 60 THEN 1 ELSE 0 END) as '[ <60]'
from score,course where score.c_id=course.c_id GROUP BY score.c_id;

date_format() 函數(需掌握)

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');

自定義函數

注意

  • 函數中不要寫sql語句(不然會報錯),函數僅僅只是一個功能,是一個在sql中被應用的功能

  • 若要想在begin...end...中寫sql,請用存儲過程

delimiter //
create function f1(
    i1 int,
    i2 int)
returns int
BEGIN
    declare num int;
    set num = i1 + i2;
    return(num);
END //
delimiter ;

# 在查詢中使用函數
select f1(11,nid) ,name from tb2;

# 刪除函數
drop function func_name;

流程控制

if 條件語句

# if條件語句
delimiter //  # 修改mysql 默認的語句結束符
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 ;  # 將sql語句默認結束符改回 ;

while 循環

# 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 ;

索引與慢查詢優化 **

mysql 默認有查詢優化機制,咱們不須要再上面花多少精力,能優化的,mysql基本都直接給咱們優化了(可適當增長几個索引)

詳細內容參考egon 的博客

第八篇:索引原理與慢查詢優化 ,寫的很詳細,案例也很充分,我這個寫的很差😂

本篇僅做快速瞭解,第一遍最好是去看 egon 的博客(能有個詳細的瞭解)

感受寫的着實不錯,直接複製過來了。。。

前言(摘抄)

索引是應用程序設計和開發的一個重要方面。

若索引太多,應用程序的性能可能會受到影響,而索引太少,對查詢性能又會產生影響,要找到一個平衡點,這對應用程序的性能相當重要。

一些開發人員老是在過後纔想起添加索引----我一直認爲,這源於一種錯誤的開發模式,若是知道數據的使用,從一開始就應該在須要處添加索引。

開發人員每每對數據庫的使用停留在應用的層面,好比編寫SQL語句、存儲過程之類,他們甚至可能不知道索引的存在,或認爲過後讓相關DBA加上便可,但DBA每每不夠了解業務的數據流,而添加索引須要經過監控大量的SQL語句進而從中找到問題,這個步驟所需的時間確定是遠大於初始添加索引所需的時間,而且可能會遺漏一部分的索引。

固然索引也並非越多越好,我曾經遇到過這樣一個問題:
某臺MySQL服務器io stat顯示磁盤使用率一直處於100%,通過分析後發現是因爲開發人員添加了太多的索引,在刪除一些沒必要要的索引以後,磁盤使用率立刻降低爲20%。
可見索引的添加也是很是有技術含量的。

這一塊瞭解個大概便可,能在寫sql語句時稍微考慮下性能就能夠了,畢竟我是要搞開發的呀~

索引

索引的目的在於提升查詢效率,與咱們查閱圖書所用的目錄是一個道理:先定位到章,而後定位到該章下的一個小節,而後找到頁數。

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

擴展閱讀

索引原理

數據庫比起查字典,飛機航班等顯然要複雜的多,由於不只面臨着等值查詢,還有範圍查詢(>、<、between、in)、模糊查詢(like)、並集查詢(or)等等。
那數據庫應該選擇怎麼樣的方式來應對全部的問題呢?咱們回想字典的例子,能不能把數據分紅段,而後分段查詢呢?
最簡單的若是1000條數據,1到100分紅第一段,101到200分紅第二段,201到300分紅第三段......這樣查第250條數據,只要找第三段就能夠了,一會兒去除了90%的無效數據。
但若是是1千萬的記錄呢,分紅幾段比較好?稍有算法基礎的同窗會想到搜索樹,其平均複雜度是lgN,具備不錯的查詢性能。
但這裏咱們忽略了一個關鍵的問題,複雜度模型是基於每次相同的操做成原本考慮的。
而數據庫實現比較複雜,一方面數據是保存在磁盤上的,另一方面爲了提升性能,每次又能夠把部分數據讀入內存來計算,由於咱們知道訪問磁盤的成本大概是訪問內存的十萬倍左右,因此簡單的搜索樹難以知足複雜的應用場景。

磁盤IO與預讀

前面提到了訪問磁盤,那麼這裏先簡單介紹一下磁盤IO和預讀。
磁盤讀取數據靠的是機械運動,每次讀取數據花費的時間能夠分爲尋道時間、旋轉延遲、傳輸時間三個部分,
尋道時間指的是磁臂移動到指定磁道所須要的時間,主流磁盤通常在5ms如下;
旋轉延遲就是咱們常常據說的磁盤轉速,好比一個磁盤7200轉,表示每分鐘能轉7200次,也就是說1秒鐘能轉120次,旋轉延遲就是1/120/2 = 4.17ms;
傳輸時間指的是從磁盤讀出或將數據寫入磁盤的時間,通常在零點幾毫秒,相對於前兩個時間能夠忽略不計。
那麼訪問一次磁盤的時間,即一次磁盤IO的時間約等於5+4.17 = 9ms左右,聽起來還挺不錯的,但要知道一臺500 -MIPS(Million Instructions Per Second)的機器每秒能夠執行5億條指令,由於指令依靠的是電的性質,換句話說執行一次IO的時間能夠執行約450萬條指令,數據庫動輒十萬百萬乃至千萬級數據,每次9毫秒的時間,顯然是個災難。
下圖是計算機硬件延遲的對比圖,供你們參考:

考慮到磁盤IO是很是高昂的操做,計算機操做系統作了一些優化,當一次IO時,不光把當前磁盤地址的數據,而是把相鄰的數據也都讀取到內存緩衝區內,由於局部預讀性原理告訴咱們,當計算機訪問一個地址的數據的時候,與其相鄰的數據也會很快被訪問到。
每一次IO讀取的數據咱們稱之爲一頁(page)。具體一頁有多大數據跟操做系統有關,通常爲4k或8k,也就是咱們讀取一頁內的數據時候,實際上才發生了一次IO,這個理論對於索引的數據結構設計很是有幫助。

常見索引

  • primary key 主鍵索引
  • unique key 惟一性索引
  • index key 普通索引
  • 聯合索引(上面三個展開),下面的聯合索引有介紹

上述三個鍵均可以加快查詢,primary key 和 unique key 除索引外還有額外的約束

外鍵是用來建立表與表之間關聯關係的,不算索引

索引必定是本身建的(key),普通字段沒有 索引(以前我理解錯了)

索引的本質

經過不斷地縮小想要獲取數據的範圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是說,有了這種索引機制,咱們能夠老是用同一種查找方式來鎖定數據。

索引的缺點

  • 在表中有大量數據時建立索引速度很慢
  • 在索引建立完畢後,對錶的查詢性能大幅度提高
  • 往建立好索引的表(有大量數據)裏插入數據會變得很是更新索引致使慢

小結

雖然索引好用,但應該在合理範圍內去用,並非越多越好

索引的數據結構 -- B+ 樹

前面講了索引的基本原理,數據庫的複雜性,又講了操做系統的相關知識,目的就是讓你們瞭解,任何一種數據結構都不是憑空產生的,必定會有它的背景和使用場景,咱們如今總結一下:

​ 咱們須要這種數據結構可以作些什麼,其實很簡單,那就是:每次查找數據時把磁盤IO次數控制在一個很小的數量級,最好是常數數量級。

​ 那麼咱們就想到若是一個高度可控的多路搜索樹是否能知足需求呢?

​ 就這樣,b+樹應運而生(B+樹是經過二叉查找樹,再由平衡二叉樹,B樹演化而來)。

如上圖,是一顆b+樹,關於b+樹的定義能夠參見B+樹,這裏只說一些重點.

淺藍色的塊咱們稱之爲一個磁盤塊,能夠看到每一個磁盤塊包含幾個數據項(深藍色所示)和指針(黃色所示),

​ 如磁盤塊1包含數據項17和35,包含指針P一、P二、P3,P1表示小於17的磁盤塊,P2表示在17和35之間的磁盤塊,P3表示大於35的磁盤塊。

​ 真實的數據存在於葉子節點即三、五、九、十、1三、1五、2八、2九、3六、60、7五、7九、90、99。

非葉子節點只不存儲真實的數據,只存儲指引搜索方向的數據項,如1七、35並不真實存在於數據表中。

b+樹的查找過程

如圖所示,若是要查找數據項29,那麼首先會把磁盤塊1由磁盤加載到內存,此時發生一次IO,在內存中用二分查找肯定29在17和35之間,鎖定磁盤塊1的P2指針,內存時間由於很是短(相比磁盤的IO)能夠忽略不計,經過磁盤塊1的P2指針的磁盤地址把磁盤塊3由磁盤加載到內存,發生第二次IO,29在26和30之間,鎖定磁盤塊3的P2指針,經過指針加載磁盤塊8到內存,發生第三次IO,同時內存中作二分查找找到29,結束查詢,總計三次IO。

​ 真實的狀況是,3層的b+樹能夠表示上百萬的數據,若是上百萬的數據查找只須要三次IO,性能提升將是巨大的,若是沒有索引,每一個數據項都要發生一次IO,那麼總共須要百萬次的IO,顯然成本很是很是高。

案例:查 71

b+樹性質

索引字段要儘可能的小

經過上面的分析,咱們知道IO次數取決於b+數的高度h,假設當前數據表的數據爲N,每一個磁盤塊的數據項的數量是m,則有h=㏒(m+1)N,當數據量N必定的狀況下,m越大,h越小;而m = 磁盤塊的大小 / 數據項的大小,磁盤塊的大小也就是一個數據頁的大小,是固定的,若是數據項佔的空間越小,數據項的數量越多,樹的高度越低。

​ 這就是爲何每一個數據項,即索引字段都要儘可能的小,好比int佔4字節,要比bigint8字節少一半。這也是爲何b+樹要求把真實的數據放到葉子節點而不是內層節點,一旦放到內層節點,磁盤塊的數據項會大幅度降低,致使樹增高。當數據項等於1時將會退化成線性表。

索引的最左匹配特性

當b+樹的數據項是複合的數據結構,好比(name,age,sex)的時候,b+數是按照從左到右的順序來創建搜索樹的,好比當(張三,20,F)這樣的數據來檢索的時候,b+樹會優先比較name來肯定下一步的所搜方向,若是name相同再依次比較age和sex,最後獲得檢索的數據;但當(20,F)這樣的沒有name的數據來的時候,b+樹就不知道下一步該查哪一個節點,由於創建搜索樹的時候name就是第一個比較因子,必需要先根據name來搜索才能知道下一步去哪裏查詢。好比當(張三,F)這樣的數據來檢索時,b+樹能夠用name來指定搜索方向,但下一個字段age的缺失,因此只能把名字等於張三的數據都找到,而後再匹配性別是F的數據了, 這個是很是重要的性質,即索引的最左匹配特性

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

查詢次數由樹的層級決定,層級越低次數越少查詢速度越快磁盤塊存的數據越多層級越少越容易拿到數據

————> 這也是把 id 做爲主鍵的緣由

一個磁盤塊兒的大小是必定的,那也就意味着能存的數據量是必定的。

如何保證樹的層級最低呢?一個磁盤塊兒存放佔用空間比較小的數據項

彙集索引非彙集索引

索引也有不一樣的種類,按不一樣的要求去分(不僅僅是一個字段做爲索引)

彙集索引(primary key)

彙集索引其實指的就是表的主鍵(通常都是 id字段)

InnoDB 引擎規定一張表中必需要有主鍵

InnoDB 在建表的時候對應到硬盤上是兩個文件,.frm 表結構文件只存放表結構,不可能放索引,也就意味着 InnoDB 的索引跟數據都放在 .ibd 表數據文件

彙集索引特色:葉子結點放的是一條條完整的記錄

輔助索引(unique,index)

查詢數據的時候不可能都是用 id 做爲篩選條件,也可能會用 id 以外的 name,password 等字段信息,那麼這個時候就沒法利用到彙集索引的加速查詢優點。

此時就須要給其餘字段創建(的)索引,這些索引就叫輔助索引

輔助索引特色:葉子結點存放的是輔助索引字段對應的那條記錄的主鍵的值(好比:按照name字段建立索引,那麼葉子節點存放的是:{name對應的值:name所在的那條記錄的主鍵值}找到後再拿着id 去彙集索引裏面去查

彙集索引和非彙集索引的不一樣

葉子結點存放的是不是一整行的信息

覆蓋索引

InnoDB存儲引擎支持覆蓋索引(covering index,或稱索引覆蓋)

使用覆蓋索引的一個好處是:輔助索引不包含整行記錄的全部信息,故其大小要遠小於彙集索引,所以能夠減小大量的IO操做

select name from user where name='jason';

覆蓋索引:只在輔助索引的葉子節點中就已經找到了全部咱們想要的數據(條件和要查的字段相同的時候)

where 條件後面的字段做爲輔助索引,select 後面的字段正好是(沒有多餘的字段)要找的數據,若是輔助索引和要找的數據同樣,那就找到了,就是覆蓋索引 --> 我的解釋,沒有權威性

非覆蓋索引

select age from user where name='jason';

雖然查詢的時候查到了輔助索引name,可是要查的是age字段,因此查到name字段後還須要利用查到的彙集索引id 纔去查找那條記錄中 age 字段的值

聯合索引

  • primary key(host, port) 聯合主鍵索引(通常不用聯合主鍵,主鍵通常都是專門的 id 字段)
  • unique(host, port) 聯合惟一索引
  • index(host, port) 聯合普通索引
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';  # 速度變快

慢查詢優化

查詢優化神器-explain 經過分析sql語句來提高效率

設定一個時間檢測全部超出改時間的sql語句,而後針對性的進行優化!

根據使用場景管理並設置合適的索引

注意這塊的小點能提升sql語句性能

摘自egon博客:,放本身博客方便看

測試索引

數據準備

#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,'egon','male',concat('egon',i,'@oldboy'));
        set i=i+1;
    end while;
END$$ #$$結束
delimiter ; #從新聲明分號爲結束符號

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

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

在沒有索引的前提下測試查詢速度

#無索引:mysql根本就不知道究竟是否存在id等於333333333的記錄,只能把數據表從頭至尾掃描一遍,此時有多少個磁盤塊就須要進行多少IO操做,因此查詢速度很慢
mysql> select * from s1 where id=333333333;
Empty set (0.33 sec)

在表中已經存在大量數據的前提下,爲某個字段段創建索引,創建速度會很慢

在索引創建完畢後,以該字段爲查詢條件時,查詢速度提高明顯

1. mysql先去索引表裏根據b+樹的搜索原理很快搜索到id等於333333333的記錄不存在,IO大大下降,於是速度明顯提高

2. 咱們能夠去mysql的data目錄下找到該表,能夠看到佔用的硬盤空間多了

3.須要注意,以下圖

總結

#1. 必定是爲搜索條件的字段建立索引,好比select * from s1 where id = 333;就須要爲id加上索引

#2. 在表中已經有大量數據的狀況下,建索引會很慢,且佔用硬盤空間,建完後查詢速度加快
好比create index idx on s1(id);會掃描表中全部的數據,而後以id爲數據項,建立索引結構,存放於硬盤的表中。
建完之後,再查詢就會很快了。

#3. 須要注意的是:innodb表的索引會存放於s1.ibd文件中,而myisam表的索引則會有單獨的索引文件table1.MYI

MySAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。而在innodb中,表數據文件自己就是按照B+Tree(BTree即Balance True)組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,所以innodb表數據文件自己就是主索引。
由於inndob的數據文件要按照主鍵彙集,因此innodb要求表必需要有主鍵(Myisam能夠沒有),若是沒有顯式定義,則mysql系統會自動選擇一個能夠惟一標識數據記錄的列做爲主鍵,若是不存在這種列,則mysql會自動爲innodb表生成一個隱含字段做爲主鍵,這字段的長度爲6個字節,類型爲長整型.

正確使用索引

索引未命中

並非說咱們建立了索引就必定會加快查詢速度, 若想利用索引達到預想的提升查詢速度的效果,咱們在添加索引時,必須遵循如下問題

範圍問題

範圍問題,或者說條件不明確,條件中出現這些符號或關鍵字:>、>=、<、<=、!= 、between...and...、like、

大於號、小於號

不等於!=

between ...and...

like

創建索引的字段選擇

儘可能選擇區分度高的列做爲索引,區分度的公式是count(distinct col)/count(*),表示字段不重複的比例,比例越大咱們掃描的記錄數越少,惟一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就是0,那可能有人會問,這個比例有什麼經驗值嗎?使用場景不一樣,這個值也很難肯定,通常須要join的字段咱們都要求是0.1以上,即平均1條掃描10條記錄

#先把表中的索引都刪除,讓咱們專心研究區分度的問題
mysql> desc s1;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | YES  | MUL | NULL    |       |
| name   | varchar(20) | YES  |     | NULL    |       |
| gender | char(5)     | YES  |     | NULL    |       |
| email  | varchar(50) | YES  | MUL | NULL    |       |
+--------+-------------+------+-----+---------+-------+
rows in set (0.00 sec)

mysql> drop index a on s1;
Query OK, 0 rows affected (0.20 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> drop index d on s1;
Query OK, 0 rows affected (0.18 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> desc s1;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | YES  |     | NULL    |       |
| name   | varchar(20) | YES  |     | NULL    |       |
| gender | char(5)     | YES  |     | NULL    |       |
| email  | varchar(50) | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
rows in set (0.00 sec)

#先把表中的索引都刪除,讓咱們專心研究區分度的問題

咱們編寫存儲過程爲表s1批量添加記錄,name字段的值均爲egon,也就是說name這個字段的區分度很低(gender字段也是同樣的,咱們稍後再搭理它)

回憶b+樹的結構,查詢的速度與樹的高度成反比,要想將樹的高低控制的很低,須要保證:在某一層內數據項均是按照從左到右,從小到大的順序依次排開,即左1<左2<左3<...

而對於區分度低的字段,沒法找到大小關係,由於值都是相等的,毫無疑問,還想要用b+樹存放這些等值的數據,只能增長樹的高度,字段的區分度越低,則樹的高度越高。極端的狀況,索引字段的值都同樣,那麼b+樹幾乎成了一根棍。本例中就是這種極端的狀況,name字段全部的值均爲'egon'

#如今咱們得出一個結論:爲區分度低的字段創建索引,索引樹的高度會很高,然而這具體會帶來什麼影響呢???

#1:若是條件是name='xxxx',那麼確定是能夠第一時間判斷出'xxxx'是不在索引樹中的(由於樹中全部的值均爲'egon’),因此查詢速度很快

#2:若是條件正好是name='egon',查詢時,咱們永遠沒法從樹的某個位置獲得一個明確的範圍,只能往下找,往下找,往下找。。。這與全表掃描的IO次數沒有多大區別,因此速度很慢
條件中的 = 和 in 能夠亂序(mysql查詢優化器自動優化)

=和in能夠亂序,好比a = 1 and b = 2 and c = 3 創建(a,b,c)索引能夠任意順序,mysql的查詢優化器會幫你優化成索引能夠識別的形式

索引列不能參加運算

索引列不能參與計算,保持列「乾淨」,好比from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,緣由很簡單,b+樹中存的都是數據表中的字段值,但進行檢索時,須要把全部元素都應用函數才能比較,顯然成本太大。因此語句應該寫成create_time = unix_timestamp(’2014-05-29’)

and/or
#一、and與or的邏輯
    條件1 and 條件2:全部條件都成立纔算成立,但凡要有一個條件不成立則最終結果不成立
    條件1 or 條件2:只要有一個條件成立則最終結果就成立

#二、and的工做原理
    條件:
        a = 10 and b = 'xxx' and c > 3 and d =4
    索引:
        製做聯合索引(d,a,b,c)
    工做原理:
        對於連續多個and:mysql會按照聯合索引,從左到右的順序找一個區分度高的索引字段(這樣即可以快速鎖定很小的範圍),加速查詢,即按照d—>a->b->c的順序

#三、or的工做原理
    條件:
        a = 10 or b = 'xxx' or c > 3 or d =4
    索引:
        製做聯合索引(d,a,b,c)
        
    工做原理:
        對於連續多個or:mysql會按照條件的順序,從左到右依次判斷,即a->b->c->d

在左邊條件成立可是索引字段的區分度低的狀況下(name與gender均屬於這種狀況),會依次往右找到一個區分度高的索引字段,加速查詢

通過分析,在條件爲name='egon' and gender='male' and id>333 and email='xxx'的狀況下,咱們徹底不必爲前三個條件的字段加索引,由於只能用上email字段的索引,前三個字段的索引反而會下降咱們的查詢效率

最左匹配原則

最左前綴匹配原則,是很是重要的原則,對於組合索引mysql會一直向右匹配直到遇到範圍查詢(>、<、between、like)就中止匹配(指的是範圍大了,有索引速度也慢),好比a = 1 and b = 2 and c > 3 and d = 4 若是創建(a,b,c,d)順序的索引,d是用不到索引的,若是創建(a,b,d,c)的索引則均可以用到,a,b,d的順序能夠任意調整。

其餘狀況
- 使用函數
    select * from tb1 where reverse(email) = 'egon';
            
- 類型不一致
    若是列是字符串類型,傳入條件是必須用引號引發來,否則...
    select * from tb1 where email = 999;
    
#排序條件爲索引,則select字段必須也是索引字段,不然沒法命中
- order by
    select name from s1 order by email desc;
    當根據索引排序時候,select查詢的字段若是不是索引,則速度仍然很慢
    select email from s1 order by email desc;
    特別的:若是對主鍵排序,則仍是速度很快:
        select * from tb1 order by nid desc;
 
- 組合索引最左前綴
    若是組合索引爲:(name,email)
    name and email       -- 命中索引
    name                 -- 命中索引
    email                -- 未命中索引


- count(1)或count(列)代替count(*)在mysql中沒有差異了

- create index xxxx  on tb(title(19)) #text類型,必須制定長度

其餘注意事項

- 避免使用select *
- count(1)或count(列) 代替 count(*)  (默認是用id 彙集索引去查,效率會高不少)
- 建立表時儘可能時 char 代替 varchar  (自行選擇執行效率仍是硬盤資源)
- 表的字段順序固定長度的字段優先
- 組合索引代替多個單列索引(常用多個條件查詢時)
- 儘可能使用短索引 (單個磁盤片上的數據多,層級少,查的快)
- 使用鏈接(JOIN)來代替子查詢(Sub-Queries)
- 連表時注意條件類型需一致
- 索引散列值(重複少)不適合建索引,例:性別不適合(大量重複的,分層很差找)
相關文章
相關標籤/搜索