1. 程式人生 > >5月14日 python學習總結 視圖、觸發器、事務、存儲過程、函數、流程控制、索引

5月14日 python學習總結 視圖、觸發器、事務、存儲過程、函數、流程控制、索引

頻繁 表結構 可執行 開頭 好處 你在 get 16進制轉換 自帶

一、視圖

1、什麽是視圖
視圖就是通過查詢得到一張虛擬表,然後保存下來,下次用的直接使用即可

2、為什麽要用視圖
如果要頻繁使用一張虛擬表,可以不用重復查詢

3、如何用視圖

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

drop view teacher2course;


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

二、觸發器

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

為何要用觸發器?

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

create trigger tri_after_insert_t1 after/before insert/delete/update on 表名 for each row
begin
    sql代碼。。。
end

#after  事件執行之後觸發   before 事件執行之前觸發
#觸發事件 insert delete update操作,觸發觸發器執行

  案例:

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 tri_after_insert_cmd after insert on cmd for each row begin
if NEW.success = no then insert into errlog(err_cmd,err_time) values(NEW.cmd,NEW.sub_time); end if; end $$ delimiter ;
#當使用update語句的時候,當修改原表數據的時候相對於修改數據後表的數據來說原表中修改的那條數據就是OLD對象,
#而修改數據後表被修改的那條數據就是NEW對象
 
#往表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‘);

drop trigger tri_after_insert_cmd;

  註:

    delimiter $$ 操作用於將結束符號從 ‘ ;‘ 改為 ‘ $$ ‘ ,用於屏蔽掉觸發器中的 ‘;‘
    在觸發器結尾要加上
delimiter ; ,將結束符再改回來,不影響其他代碼的正常執行

三、事務

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

開啟事務之後,只要沒有執行commit操作,都沒有真正存儲到硬盤,在commin提交事務之前,可以用 rollback 操作回滾數據。

  使用:

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

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。

三種開發模型:
1、 應用程序:只需要開發應用程序的邏輯
mysql:編寫好存儲過程,以供應用程序調用

優點:開發效率,執行效率都高
缺點:考慮到人為因素、跨部門溝通等問題,會導致擴展性差

2、 應用程序:除了開發應用程序的邏輯,還需要編寫原生sql
mysql: 執行sql

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


3、 應用程序:開發應用程序的邏輯,不需要編寫原生sql,基於別人編寫好的框架來處理數據,ORM
mysql:執行sql

優點:不用再編寫純生sql,這意味著開發效率比方式2高,同時兼容方式2擴展性高的好處
缺點:執行效率連方式2都比不過

  創建存儲過程:

delimiter $$
create procedure p1(
    in m int,
    in n int,
    out res int
)
begin
    select tname from teacher where tid > m and tid < n;
    set res=0;
end $$
delimiter ;

對於存儲過程,可以接收參數,其參數有三類:

#in          僅用於傳入參數用
#out        僅用於返回值用
#inout     既可以傳入又可以當作返回值
 

  如何用存儲過程:

  1、直接在mysql中調用

set @res=10
call p1(2,4,10);
#查看結果
select @res;

  2、在python程序中調用

import pymysql

conn=pymysql.connect(
    host=127.0.0.1,
    port=3306,
    user=root,
    password=123,
    charset=utf8,
    database=db42
)

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

cursor.callproc(p1,(2,4,10)) #@_p1_0=2,@_p1_1=4,@_p1_2=10


print(cursor.fetchall())

cursor.execute(select @_p1_2;)   #執行查看存儲器 out 輸出數據的值
print(cursor.fetchone())       #打印該輸出值

cursor.close()
conn.close()

  事務與存儲過程的使用:

delimiter //                #將結束符號改為 ‘//‘ ,用以屏蔽sql代碼塊中的 ‘;‘    
create PROCEDURE p5(
    OUT p_return_code tinyint  # 設置一個 tinyint類型的 輸出參數 p_return_code
)
BEGIN
    DECLARE exit handler for sqlexception   #聲明代碼段捕捉sqlexception異常
    BEGIN
        -- ERROR
        set p_return_code = 1;            # 1代表執行失敗,遇到sqlexception異常
        rollback;                        #回滾數據
    END;

    DECLARE exit handler for sqlwarning        #聲明代碼段捕捉sqlwarning異常
    BEGIN
        -- WARNING
        set p_return_code = 2;            # 2代表執行失敗,遇到sqlwarning異常
        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 ;        #將結束符號改回 ‘;‘ 

五、date_format函數

SELECT
    date_format(sub_time, %Y-%m),
    count(id)
FROM
    blog
GROUP BY
    date_format(sub_time, %Y-%m);

  date_format函數用於修改時間格式

  mysql還提供了很多自帶的函數,用到時可以查  

技術分享圖片
一、數學函數
    ROUND(x,y)
        返回參數x的四舍五入的有y位小數的值
        
    RAND()
        返回0到1內的隨機值,可以通過提供一個參數(種子)使RAND()隨機數生成器生成一個指定的值。

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

    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
            
四、日期和時間函數
    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
        
五、加密函數
    MD5()    
        計算字符串str的MD5校驗和
    PASSWORD(str)   
        返回字符串str的加密版本,這個加密過程是不可逆轉的,和UNIX密碼加密過程使用不同的算法。
        
六、控制流函數            
    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.1、準備表
/*
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.2、統計各科各分數段人數.顯示格式:課程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;
其他函數

六、流程控制

select
case
when name = egon then
    name
when name = alex then
    concat(name,_BIGSB)
else
    concat(name,_SB)
end

from emp;

一 條件語句

技術分享圖片
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 ;
if條件語句

二 循環語句

技術分享圖片
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 ;
while循環 技術分享圖片
delimiter //
CREATE PROCEDURE proc_repeat ()
BEGIN

    DECLARE i INT ;
    SET i = 0 ;
    repeat
        select i;
        set i = i + 1;
        until i >= 5
    end repeat;

END //
delimiter ;
repeat循環 技術分享圖片
BEGIN
    
    declare i int default 0;
    loop_label: loop
        
        set i=i+1;
        if i<8 then
            iterate loop_label;
        end if;
        if i>=10 then
            leave loop_label;
        end if;
        select i;
    end loop loop_label;

END
loop

七、索引

  參考老師博客: http://www.cnblogs.com/linhaifeng/articles/7274563.html

01 為什麽要用索引

對於一個應用來說,對數據庫的讀寫比例基本上是10:1,即讀多寫少
而且對於寫來說極少出現性能問題,大多數性能問題都是慢查詢
提到加速查,就必須用到索引

02 什麽是索引

索引就相當於書的目錄,是mysql中一種專門的數據結構,稱為key,
索引的本質原理就是通過不斷地縮小查詢範圍,來降低io次數從而提升查詢性能
強調:一旦為表創建了索引,以後的查詢都會先查索引,再根據索引定位的結果去找數據

MySQL常用的索引

普通索引INDEX:加速查找

唯一索引:
    -主鍵索引PRIMARY KEY:加速查找+約束(不為空、不能重復)
    -唯一索引UNIQUE:加速查找+約束(不能重復)

聯合索引:
    -PRIMARY KEY(id,name):聯合主鍵索引
    -UNIQUE(id,name):聯合唯一索引
    -INDEX(id,name):聯合普通索引

索引的應用場景:

舉個例子來說,比如你在為某商場做一個會員卡的系統。

這個系統有一個會員表
有下列字段:
會員編號 INT
會員姓名 VARCHAR(10)
會員身份證號碼 VARCHAR(18)
會員電話 VARCHAR(10)
會員住址 VARCHAR(50)
會員備註信息 TEXT

那麽這個 會員編號,作為主鍵,使用 PRIMARY
會員姓名 如果要建索引的話,那麽就是普通的 INDEX
會員身份證號碼 如果要建索引的話,那麽可以選擇 UNIQUE (唯一的,不允許重復)

#除此之外還有全文索引,即FULLTEXT
會員備註信息 , 如果需要建索引的話,可以選擇全文搜索。
用於搜索很長一篇文章的時候,效果最好。
用在比較短的文本,如果就一兩行字的,普通的 INDEX 也可以。
但其實對於全文搜索,我們並不會使用MySQL自帶的該索引,而是會選擇第三方軟件如Sphinx,專門來做全文搜索。

#其他的如空間索引SPATIAL,了解即可,幾乎不用

03 索引的影響

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


04 聚集索引(primary key)

特點:葉子節點存放的一整條數據

05 輔助索引(unique,index)

特點:
如果是按照這個字段創建的索引,
那麽葉子節點存放的是:{名字:名字所在那條記錄的主鍵的值}


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

刪除創建索引的語法:

#方法一:創建表時
      CREATE TABLE 表名 (
                字段名1  數據類型 [完整性約束條件…],
                字段名2  數據類型 [完整性約束條件…],
                [UNIQUE | FULLTEXT | SPATIAL ]   INDEX | KEY
                [索引名]  (字段名[(長度)]  [ASC |DESC]) 
                );


#方法二:CREATE在已存在的表上創建索引
        CREATE  [UNIQUE | FULLTEXT | SPATIAL ]  INDEX  索引名 
                     ON 表名 (字段名[(長度)]  [ASC |DESC]) ;


#方法三:ALTER TABLE在已存在的表上創建索引
        ALTER TABLE 表名 ADD  [UNIQUE | FULLTEXT | SPATIAL ] INDEX
                             索引名 (字段名[(長度)]  [ASC |DESC]) ;
                             
#刪除索引:DROP INDEX 索引名 ON 表名字;

  

索引註意:

  1、索引字段要盡量的小

  2、索引的最左匹配特性

索引的功能就是加速查找

mysql中的primary key,unique,聯合唯一也都是索引,這些索引除了加速查找以外,還有約束的功能

二 磁盤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,這個理論對於索引的數據結構設計非常有幫助。

三 索引的數據結構

前面講了索引的基本原理,數據庫的復雜性,又講了操作系統的相關知識,目的就是讓大家了解,任何一種數據結構都不是憑空產生的,一定會有它的背景和使用場景,我們現在總結一下,我們需要這種數據結構能夠做些什麽,其實很簡單,那就是:每次查找數據時把磁盤IO次數控制在一個很小的數量級,最好是常數數量級。那麽我們就想到如果一個高度可控的多路搜索樹是否能滿足需求呢?就這樣,b+樹應運而生(B+樹是通過二叉查找樹,再由平衡二叉樹,B樹演化而來)。

技術分享圖片

如上圖,是一顆b+樹,關於b+樹的定義可以參見B+樹,這裏只說一些重點,淺藍色的塊我們稱之為一個磁盤塊,可以看到每個磁盤塊包含幾個數據項(深藍色所示)和指針(黃色所示),如磁盤塊1包含數據項17和35,包含指針P1、P2、P3,P1表示小於17的磁盤塊,P2表示在17和35之間的磁盤塊,P3表示大於35的磁盤塊。真實的數據存在於葉子節點即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非葉子節點只不存儲真實的數據,只存儲指引搜索方向的數據項,如17、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,顯然成本非常非常高。

###b+樹性質
1.索引字段要盡量的小:通過上面的分析,我們知道IO次數取決於b+數的高度h,假設當前數據表的數據為N,每個磁盤塊的數據項的數量是m,則有h=㏒(m+1)N,當數據量N一定的情況下,m越大,h越小;而m = 磁盤塊的大小 / 數據項的大小,磁盤塊的大小也就是一個數據頁的大小,是固定的,如果數據項占的空間越小,數據項的數量越多,樹的高度越低。這就是為什麽每個數據項,即索引字段要盡量的小,比如int占4字節,要比bigint8字節少一半。這也是為什麽b+樹要求把真實的數據放到葉子節點而不是內層節點,一旦放到內層節點,磁盤塊的數據項會大幅度下降,導致樹增高。當數據項等於1時將會退化成線性表。
2.索引的最左匹配特性:當b+樹的數據項是復合的數據結構,比如(name,age,sex)的時候,b+數是按照從左到右的順序來建立搜索樹的,比如當(張三,20,F)這樣的數據來檢索的時候,b+樹會優先比較name來確定下一步的所搜方向,如果name相同再依次比較age和sex,最後得到檢索的數據;但當(20,F)這樣的沒有name的數據來的時候,b+樹就不知道下一步該查哪個節點,因為建立搜索樹的時候name就是第一個比較因子,必須要先根據name來搜索才能知道下一步去哪裏查詢。比如當(張三,F)這樣的數據來檢索時,b+樹可以用name來指定搜索方向,但下一個字段age的缺失,所以只能把名字等於張三的數據都找到,然後再匹配性別是F的數據了, 這個是非常重要的性質,即索引的最左匹配特性。

5月14日 python學習總結 視圖、觸發器、事務、存儲過程、函數、流程控制、索引