MySQL(二)
一、视图
视图是一个虚拟表(非真实存在),其本质是【根据SQL语句获取动态的数据集,并为其命名】,用户使用时只需使用【名称】即可获取结果集,并可以将其当作表来使用。
临时表搜索
1 SELECT 2 * 3 FROM 4 ( 5 SELECT 6 nid, 7 NAME 8 FROM 9 tb1 10 WHERE 11 nid > 2 12 ) AS A 13 WHERE 14 A. NAME > 'sun';
1、创建视图
1 --格式:CREATE VIEW 视图名称 AS SQL语句 2 CREATE VIEW v1 AS 3 SELET nid, 4 name 5 FROM 6 A 7 WHERE 8 nid > 4
2、删除视图
1 --格式:DROP VIEW 视图名称 2 3 DROP VIEW v1
3、修改视图
1 -- 格式:ALTER VIEW 视图名称 AS SQL语句 2 3 ALTER VIEW v1 AS 4 SELET A.nid, 5 B. NAME 6 FROM 7 A 8 LEFT JOIN B ON A.id = B.nid 9 LEFT JOIN C ON A.id = C.nid 10 WHERE 11 A.id > 2 12 AND C.nid < 5
4、使用视图
使用视图时,将其当作表进行操作即可,由于视图是虚拟表,所以无法使用其对真实表进行创建、更新和删除操作,仅能做查询用。
1 select * from v1
二、触发器
对某个表进行【增/删/改】操作的前后如果希望触发某个特定的行为时,可以使用触发器,触发器用于定制用户对表的行进行【增/删/改】前后的行为。
1、创建基本语法
1 # 插入前 2 CREATE TRIGGER tri_before_insert_tb1 BEFORE INSERT ON tb1 FOR EACH ROW 3 BEGIN 4 ... 5 END 6 7 # 插入后 8 CREATE TRIGGER tri_after_insert_tb1 AFTER INSERT ON tb1 FOR EACH ROW 9 BEGIN 10 ... 11 END 12 13 # 删除前 14 CREATE TRIGGER tri_before_delete_tb1 BEFORE DELETE ON tb1 FOR EACH ROW 15 BEGIN 16 ... 17 END 18 19 # 删除后 20 CREATE TRIGGER tri_after_delete_tb1 AFTER DELETE ON tb1 FOR EACH ROW 21 BEGIN 22 ... 23 END 24 25 # 更新前 26 CREATE TRIGGER tri_before_update_tb1 BEFORE UPDATE ON tb1 FOR EACH ROW 27 BEGIN 28 ... 29 END 30 31 # 更新后 32 CREATE TRIGGER tri_after_update_tb1 AFTER UPDATE ON tb1 FOR EACH ROW 33 BEGIN 34 ... 35 END
插入前触发器
1 delimiter // #修改sql默认以 ; 结束语句改为 // 结束语句 2 CREATE TRIGGER tri_before_insert_tb1 BEFORE INSERT ON tb1 FOR EACH ROW #TRIGGER触发器,tri_before_insert_tb1触发器名,EACH ROW每行 3 BEGIN #begin内是触发触发器后执行的代码 4 5 IF NEW. NAME == 'sun' THEN 6 INSERT INTO tb2 (NAME) 7 VALUES 8 ('aa') 9 END 10 END// 11 delimiter ; #再次修改会默认sql默认以 ; 结束
插入后触发器
1 elimiter // 2 CREATE TRIGGER tri_after_insert_tb1 AFTER INSERT ON tb1 FOR EACH ROW 3 BEGIN 4 IF NEW. num = 666 THEN 5 INSERT INTO tb2 (NAME) 6 VALUES 7 ('666'), 8 ('666') ; 9 ELSEIF NEW. num = 555 THEN 10 INSERT INTO tb2 (NAME) 11 VALUES 12 ('555'), 13 ('555') ; 14 END IF; 15 END// 16 delimiter ;
特别的:NEW表示即将插入的数据行,OLD表示即将删除的数据行。
2、删除触发器
1 DROP TRIGGER tri_after_insert_tb1;
3、使用触发器
触发器无法由用户直接调用,而知由于对表的【增/删/改】操作被动引发的。
1 insert into tb1(num) values(666)
三、函数
函数内不能写sql语句,会报错
MySQL中提供了许多内置函数,例如:
1、部分内置函数
1 CHAR_LENGTH(str) 2 返回值为字符串str 的长度,长度的单位为字符。一个多字节字符算作一个单字符。 3 对于一个包含五个二字节字符集, LENGTH()返回值为 10, 而CHAR_LENGTH()的返回值为5。 4 5 CONCAT(str1,str2,...) 6 字符串拼接 7 如有任何一个参数为NULL ,则返回值为 NULL。 8 CONCAT_WS(separator,str1,str2,...) 9 字符串拼接(自定义连接符) 10 CONCAT_WS()不会忽略任何空字符串。 (然而会忽略所有的 NULL)。 11 12 CONV(N,from_base,to_base) 13 进制转换 14 例如: 15 SELECT CONV('a',16,2); 表示将 a 由16进制转换为2进制字符串表示 16 17 FORMAT(X,D) 18 将数字X 的格式写为'#,###,###.##',以四舍五入的方式保留小数点后 D 位, 并将结果以字符串的形式返回。若 D 为 0, 则返回结果不带有小数点,或不含小数部分。 19 例如: 20 SELECT FORMAT(12332.1,4); 结果为: '12,332.1000' 21 INSERT(str,pos,len,newstr) 22 在str的指定位置插入字符串 23 pos:要替换位置其实位置 24 len:替换的长度 25 newstr:新字符串 26 特别的: 27 如果pos超过原字符串长度,则返回原字符串 28 如果len超过原字符串长度,则由新字符串完全替换 29 INSTR(str,substr) 30 返回字符串 str 中子字符串的第一个出现位置。 31 32 LEFT(str,len) 33 返回字符串str 从开始的len位置的子序列字符。 34 35 LOWER(str) 36 变小写 37 38 UPPER(str) 39 变大写 40 41 LTRIM(str) 42 返回字符串 str ,其引导空格字符被删除。 43 RTRIM(str) 44 返回字符串 str ,结尾空格字符被删去。 45 SUBSTRING(str,pos,len) 46 获取字符串子序列 47 48 LOCATE(substr,str,pos) 49 获取子序列索引位置 50 51 REPEAT(str,count) 52 返回一个由重复的字符串str 组成的字符串,字符串str的数目等于count 。 53 若 count <= 0,则返回一个空字符串。 54 若str 或 count 为 NULL,则返回 NULL 。 55 REPLACE(str,from_str,to_str) 56 返回字符串str 以及所有被字符串to_str替代的字符串from_str 。 57 REVERSE(str) 58 返回字符串 str ,顺序和字符顺序相反。 59 RIGHT(str,len) 60 从字符串str 开始,返回从后边开始len个字符组成的子序列 61 62 SPACE(N) 63 返回一个由N空格组成的字符串。 64 65 SUBSTRING(str,pos) , SUBSTRING(str FROM pos) SUBSTRING(str,pos,len) , SUBSTRING(str FROM pos FOR len) 66 不带有len 参数的格式从字符串str返回一个子字符串,起始于位置 pos。带有len参数的格式从字符串str返回一个长度同len字符相同的子字符串,起始于位置 pos。
使用 FROM的格式为标准 SQL 语法。也可能对pos使用一个负值。假若这样,则子字符串的位置起始于字符串结尾的pos 字符,而不是字符串的开头位置。
在以下格式的函数中可以对pos 使用一个负值。 67 68 mysql> SELECT SUBSTRING('Quadratically',5); 69 -> 'ratically' 70 71 mysql> SELECT SUBSTRING('foobarbar' FROM 4); 72 -> 'barbar' 73 74 mysql> SELECT SUBSTRING('Quadratically',5,6); 75 -> 'ratica' 76 77 mysql> SELECT SUBSTRING('Sakila', -3); 78 -> 'ila' 79 80 mysql> SELECT SUBSTRING('Sakila', -5, 3); 81 -> 'aki' 82 83 mysql> SELECT SUBSTRING('Sakila' FROM -4 FOR 2); 84 -> 'ki' 85 86 TRIM([{BOTH | LEADING | TRAILING} [remstr] FROM] str) TRIM(remstr FROM] str) 87 返回字符串 str , 其中所有remstr 前缀和/或后缀都已被删除。若分类符BOTH、LEADIN或TRAILING中没有一个是给定的,则假设为BOTH 。
remstr 为可选项,在未指定情况下,可删除空格。 88 89 mysql> SELECT TRIM(' bar '); 90 -> 'bar' 91 92 mysql> SELECT TRIM(LEADING 'x' FROM 'xxxbarxxx'); 93 -> 'barxxx' 94 95 mysql> SELECT TRIM(BOTH 'x' FROM 'xxxbarxxx'); 96 -> 'bar' 97 98 mysql> SELECT TRIM(TRAILING 'xyz' FROM 'barxxyz'); 99 -> 'barx'
更多函数:https://dev.mysql.com/doc/refman/5.7/en/functions.html
1、显示时间月份
1 select DATE_FORMAT(ctime, "%Y-%m"),count(1) from blog group DATE_FORMAT(ctime, "%Y-%m") 2 3 2017-11 2 4 2017-10 2
2、自定义函数(有返回值)
1 delimiter \\ 2 create function f1( #f1函数名 3 i1 int, #设置参数i1和i2的类型为int 4 i2 int) 5 returns int #设置returns返回值类型为int 6 BEGIN #begin内为执行的代码 7 declare num int; #声明变量 8 set num = i1 + i2; #函数的执行 9 return(num); #返回函数的执行结果 10 END \\ 11 delimiter ; 12 13 SELECT f1(1,100); #执行函数
#执行函数 select f1(1,100); +----------+ | f1(1,100) | +----------+ | 101 | +----------+
3、删除函数
1 drop function func_name;
4、执行函数
1 # 获取返回值 2 declare @i VARCHAR(32); 3 select UPPER('alex') into @i; 4 SELECT @i; 5 6 # 在查询中使用 7 select f1(11,nid) ,name from tb2;
四、存储过程
存储过程是一个SQL语句集合,保存在MySQL上的一个别名。当主动去调用存储过程别名时,其中内部的SQL语句集合会按照逻辑执行。
1、创建存储过程
无参数存储过程
1 -- 创建存储过程 2 create procedure p1() 3 BEGIN 4 select * from student; 5 INSERT into teacher(tname) values("ct"); 6 END 7 8 -- 执行存储过程 9 call p1() #sql中调用 10 cursor.callproc('p1') #pymysql中调用
对于存储过程,可以接收参数,其参数有三类:
- in 仅用于传入参数用
- out 仅用于返回值用
- inout 既可以传入又可以当作返回值
有参数的存储过程
1.1 参数in
1 -- 传参数in 2 delimiter // 3 create procedure p2( 4 in n1 int, 5 in n2 int 6 ) 7 BEGIN 8 select * from student where sid > n1; 9 END // 10 delimiter ; 11 12 call p2(12,2) # 12,2赋值给n1,n2传入存储过程中,用于sql语句的使用 13 cursor.callproc('p2',(12,2))
1.2 参数out
1 -- 参数out 2 delimiter // 3 create procedure p3( 4 in n1 int, 5 out n2 int 6 ) 7 BEGIN 8 set n2 = 123123; 9 select * from student where sid > n1; 10 END // 11 delimiter ; 12 13 set @v1 = 10; # 设置变量@V1 = 10,将10赋值给参数n2(out n2 int) 14 call p3(12,@v1) # 调用存储过程,查看执行的p3结果 15 select @v1; # 查看@v1的结果,因为上一步执行存储过程时n2的值已经被改变为123123 16 17 -- pycharm中调用 18 cursor.callproc('p3',(12,2)) #执行存储过程 19 r1 = cursor.fetchall() 20 print(r1) # 拿到的是执行存储过程p3的结果 21 22 # 获取执行完存储的参数 23 cursor.execute('select @_p3_0,@_p3_1') @_p3_0赋值为12即n1,@_p3_1为2即n2 24 r2 = cursor.fetchall() 25 print(r2) # 拿到的是存储过程p3的n2值 (12,123123)
参数inout具备in和out都有的功能,不举例进行模拟。
1.3 游标
1 # 创建A,B表 把A表数据插入B表 2 3 create table A( 4 id int, 5 num int 6 )engine=innodb default charset=utf8; 7 8 create table B( 9 id int not null auto_increment primary key, 10 num int 11 )engine=innodb default charset=utf8; 12 13 insert into A(id,num) values(1,100),(2,200),(3,300); 14 15 16 17 # 创建游标 18 19 delimiter // 20 create procedure p9() 21 begin 22 declare row_id int; 23 declare row_num int; 24 declare done INT DEFAULT FALSE; 25 declare temp int; 26 27 declare my_cursor CURSOR FOR select id,num from A; 28 declare CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; 29 30 open my_cursor; 31 xxoo: LOOP 32 fetch my_cursor into row_id,row_num; 33 if done then 34 leave xxoo; 35 END IF; 36 set temp = row_id + row_num; 37 insert into B(num) values(temp); 38 end loop xxoo; 39 close my_cursor; 40 41 end // 42 delimiter ; 43 44 45 46 #执行 检查 47 48 call p9(); 49 select * from B;
1.4动态执行sql
1 delimiter \\ 2 CREATE PROCEDURE p5 ( 3 in nid int 4 ) 5 BEGIN 6 PREPARE prod FROM 'select * from student where sid > ?'; 7 EXECUTE prod USING @nid; 8 DEALLOCATE prepare prod; 9 END\\ 10 delimiter ;
2、删除存储过程
1 drop procedure proc_name;
3、执行存储过程
执行存储过程
1 -- 无参数 2 call proc_name() 3 4 -- 有参数,全in 5 call proc_name(1,2) 6 7 -- 有参数,有in,out,inout 8 set @t1=0; 9 set @t2=3; 10 call proc_name(1,2,@t1,@t2)
pymysql执行存储过程
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 import pymysql 4 5 conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123', db='t1') 6 cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) 7 # 执行存储过程 8 cursor.callproc('p6', args=(1, 22, 3, 4)) 9 # 获取执行完存储的参数 10 cursor.execute("select @_p6_0,@_p6_1,@_p6_2,@_p6_3") 11 result = cursor.fetchall() 12 13 conn.commit() 14 cursor.close() 15 conn.close() 16 17 18 print(result)
五、事务
事务用于将某些操作的多个SQL作为原子性操作,一旦有某一个出现错误,即可回滚到原来的状态,从而保证数据库数据完整性。
支持事务的存储过程:
1 delimiter \\ 2 create PROCEDURE p7( 3 OUT p_return_code tinyint 4 ) 5 BEGIN 6 DECLARE exit handler for sqlexception 7 BEGIN 8 -- ERROR 9 set p_return_code = 1; 10 rollback; 11 END; 12 13 DECLARE exit handler for sqlwarning 14 BEGIN 15 -- WARNING 16 set p_return_code = 2; 17 rollback; 18 END; 19 20 START TRANSACTION; 21 DELETE from tb1; 22 insert into tb2(name)values('seven'); 23 COMMIT; 24 25 -- SUCCESS 26 set p_return_code = 0; 27 28 END\\ 29 delimiter ; 30 31 支持事务的存储过程
1 set @i =0; 2 call p7(@i); 3 select @i;
1.1.数据库开启事务命令
1 start transaction 开启事务 2 Rollback 回滚事务,即撤销指定的sql语句(只能回退insert delete update语句),回滚到上一次commit的位置 3 Commit 提交事务,提交未存储的事务 4 savepoint 保留点 ,事务处理中设置的临时占位符 你可以对它发布回退(与整个事务回退不同)
1.2.数据库事务操作演示
1 create table account( 2 id int, 3 name varchar(32), 4 balance double); 5 6 insert into account values(1,"alex",8000); 7 insert into account values(2,"egon",2000); 8 9 10 11 #方式一: 更改数据后回滚,数据回到原来 12 13 select * from account; 14 +------+------+---------+ 15 | id | name | balance | 16 +------+------+---------+ 17 | 1 | alex | 8000 | 18 | 2 | egon | 2000 | 19 +------+------+---------+ 20 21 start transaction; #开启事务后,更改数据发现数据变化 22 23 update account set balance=balance-1000 where id=1; #alex减去1000 24 select * from account; 25 +------+------+---------+ 26 | id | name | balance | 27 +------+------+---------+ 28 | 1 | alex | 7000 | 29 | 2 | egon | 2000 | 30 +------+------+---------+ 31 32 33 rollback; #回滚后,发现数据回到原来 34 35 select * from account; 36 +------+------+---------+ 37 | id | name | balance | 38 +------+------+---------+ 39 | 1 | alex | 8000 | 40 | 1 | egon | 2000 | 41 +------+------+---------+ 42 43 44 #方式二: 更改数据后提交 45 46 select * from account; 47 +------+------+---------+ 48 | id | name | balance | 49 +------+------+---------+ 50 | 1 | alex | 8000 | 51 | 2 | egon | 2000 | 52 +------+------+---------+ 53 54 update account set balance=balance-1000 where id=1; 55 pdate account set balance=balance+1000 where id=2; 56 Commit; 57 58 select * from account; 59 +------+------+---------+ 60 | id | name | balance | 61 +------+------+---------+ 62 | 1 | alex | 7000 | 63 | 2 | egon | 3000 | 64 +------+------+---------+
1.3. python中调用数据库启动事务
1 import pymysql 2 3 #添加数据 4 5 conn = pymysql.connect(host='10.37.129.3', port=3306, user='egon', passwd='123456', db='wuSir',charset="utf8") 6 7 cursor = conn.cursor() 8 9 10 try: 11 insertSQL0="INSERT INTO account (name,balance) VALUES ('oldboy',4000)" 12 insertSQL1="UPDATE account set balance=balance-1000 WHERE id=1" 13 insertSQL2="UPDATE account set balance=balance+1000 WHERE id=2" 14 15 cursor = conn.cursor() 16 17 cursor.execute(insertSQL0) 18 conn.commit() 19 20 21 #主动触发Exception,事务回滚,回滚到上面conn.commit() 22 cursor.execute(insertSQL1) 23 raise Exception 24 cursor.execute(insertSQL2) 25 cursor.close() 26 conn.commit() 27 28 except Exception as e: 29 30 conn.rollback() 31 conn.commit() 32 33 34 cursor.close() 35 conn.close()
六、索引
索引,是数据库中专门用于帮助用户快速查询数据的一种数据结构。类似于字典中的目录,查找字典内容时可以根据目录查找到数据的
存放位置,然后直接获取即可。
MySQL中常见索引有:
普通索引
唯一索引
主键索引
组合索引
1、普通索引
普通索引仅有一个功能:加速查询
1 create table in1( 2 nid int not null auto_increment primary key, 3 name varchar(32) not null, 4 email varchar(64) not null, 5 extra text, 6 index ix_name (name) 7 ) 8 9 创建表 + 索引
1 create index index_name on table_name(column_name)
1 drop index_name on table_name;
1 show index from table_name;
注意:对于创建索引时如果是BLOB 和 TEXT 类型,必须指定length。
1 create index ix_extra on in1(extra(32));
2、唯一索引
唯一索引有两个功能:加速查询 和 唯一约束(可含null)
1 create table in1( 2 nid int not null auto_increment primary key, 3 name varchar(32) not null, 4 email varchar(64) not null, 5 extra text, 6 unique ix_name (name) 7 ) 8 9 创建表 + 唯一索引
1 create unique index 索引名 on 表名(列名)
1 drop unique index 索引名 on 表名
3、主键索引
主键有两个功能:加速查询 和 唯一约束(不可含null)
1 create table in1( 2 nid int not null auto_increment primary key, 3 name varchar(32) not null, 4 email varchar(64) not null, 5 extra text, 6 index ix_name (name) 7 ) 8 9 OR 10 11 create table in1( 12 nid int not null auto_increment, 13 name varchar(32) not null, 14 email varchar(64) not null, 15 extra text, 16 primary key(ni1), 17 index ix_name (name) 18 ) 19 20 创建表 + 创建主键
1 alter table 表名 add primary key(列名);
1 alter table 表名 drop primary key; 2 alter table 表名 modify 列名 int, drop primary key;
4、组合索引(最左前缀匹配)
组合索引是将n个列组合成一个索引
其应用场景为:频繁的同时使用n列来进行查询,如:where n1 = 'alex' and n2 = 666。
1 create table in3( 2 nid int not null auto_increment primary key, 3 name varchar(32) not null, 4 email varchar(64) not null, 5 extra text 6 ) 7 8 创建表
1 create index ix_name_email on in3(name,email);
如上创建组合索引之后,查询:
- name and email -- 使用索引
- name -- 使用索引
- email -- 不使用索引
注意:对于同时搜索n个条件时,组合索引的性能好于多个单一索引合并。
其他:
1、条件语句
1 delimiter \\ 2 CREATE PROCEDURE proc_if () 3 BEGIN 4 5 declare i int default 0; 6 if i = 1 THEN 7 SELECT 1; 8 ELSEIF i = 2 THEN 9 SELECT 2; 10 ELSE 11 SELECT 7; 12 END IF; 13 14 END\\ 15 delimiter ; 16 17 if条件语句
2、循环语句
1 delimiter \\ 2 CREATE PROCEDURE proc_while () 3 BEGIN 4 5 DECLARE num INT ; 6 SET num = 0 ; 7 WHILE num < 10 DO 8 SELECT 9 num ; 10 SET num = num + 1 ; 11 END WHILE ; 12 13 END\\ 14 delimiter ; 15 16 while循环
1 delimiter \\ 2 CREATE PROCEDURE proc_repeat () 3 BEGIN 4 5 DECLARE i INT ; 6 SET i = 0 ; 7 repeat 8 select i; 9 set i = i + 1; 10 until i >= 5 11 end repeat; 12 13 END\\ 14 delimiter ; 15 16 repeat循环
1 BEGIN 2 3 declare i int default 0; 4 loop_label: loop 5 6 set i=i+1; 7 if i<8 then 8 iterate loop_label; 9 end if; 10 if i>=10 then 11 leave loop_label; 12 end if; 13 select i; 14 end loop loop_label; 15 16 END 17 18 loop
3、动态执行SQL语句
1 delimiter \\ 2 DROP PROCEDURE IF EXISTS proc_sql \\ 3 CREATE PROCEDURE proc_sql () 4 BEGIN 5 declare p1 int; 6 set p1 = 11; 7 set @p1 = p1; 8 9 PREPARE prod FROM 'select * from tb2 where nid > ?'; 10 EXECUTE prod USING @p1; 11 DEALLOCATE prepare prod; 12 13 END\\ 14 delimiter ; 15 16 动态执行SQL
补充:
btree索引和hash索引的区别
可能很多人又有疑问了,既然 Hash 索引的效率要比 B-Tree 高很多,为什么大家不都用 Hash 索引而还要使用 B-Tree 索引呢?任何事物都是有两面性的,Hash 索引也一样,虽然 Hash 索引效率高,但是 Hash 索引本身由于其特殊性也带来了很多限制和弊端,主要有以下这些。
(1)Hash 索引仅仅能满足"=","IN"和"<=>"查询,不能使用范围查询。
由于 Hash 索引比较的是进行 Hash 运算之后的 Hash 值,所以它只能用于等值的过滤,不能用于基于范围的过滤,因为经过相应的 Hash 算法处理之后的 Hash 值的大小关系,并不能保证和Hash运算前完全一样。
(2)Hash 索引无法被用来避免数据的排序操作。
由于 Hash 索引中存放的是经过 Hash 计算之后的 Hash 值,而且Hash值的大小关系并不一定和 Hash 运算前的键值完全一样,所以数据库无法利用索引的数据来避免任何排序运算;
(3)Hash 索引不能利用部分索引键查询。
对于组合索引,Hash 索引在计算 Hash 值的时候是组合索引键合并后再一起计算 Hash 值,而不是单独计算 Hash 值,所以通过组合索引的前面一个或几个索引键进行查询的时候,Hash 索引也无法被利用。
(4)Hash 索引在任何时候都不能避免表扫描。
前面已经知道,Hash 索引是将索引键通过 Hash 运算之后,将 Hash运算结果的 Hash 值和所对应的行指针信息存放于一个 Hash 表中,由于不同索引键存在相同 Hash 值,所以即使取满足某个 Hash 键值的数据的记录条数,也无法从 Hash 索引中直接完成查询,还是要通过访问表中的实际数据进行相应的比较,并得到相应的结果。
(5)Hash 索引遇到大量Hash值相等的情况后性能并不一定就会比B-Tree索引高。
对于选择性比较低的索引键,如果创建 Hash 索引,那么将会存在大量记录指针信息存于同一个 Hash 值相关联。这样要定位某一条记录时就会非常麻烦,会浪费多次表数据的访问,而造成整体性能低下。
B-Tree 索引是 MySQL 数据库中使用最为频繁的索引类型,除了 Archive 存储引擎之外的其他所有的存储引擎都支持 B-Tree 索引。不仅仅在 MySQL 中是如此,实际上在其他的很多数据库管理系统中B-Tree 索引也同样是作为最主要的索引类型,这主要是因为 B-Tree 索引的存储结构在数据库的数据检 索中有非常优异的表现。 一般来说, MySQL 中的 B-Tree 索引的物理文件大多都是以 Balance Tree 的结构来存储的,也就是所有实际需要的数据都存放于 Tree 的 Leaf Node ,而且到任何一个 Leaf Node 的最短路径的长度都是完全相同的,所以我们大家都称之为 B-Tree 索引当然,可能各种数据库(或 MySQL 的各种存储引擎)在存放自己的 B-Tree 索引的时候会对存储结构稍作改造。如 Innodb 存储引擎的 B-Tree 索引实际使用的存储结构实际上是 B+Tree ,也就是在 B-Tree 数据结构的基础上做了很小的改造,在每一个 Leaf Node 上面出了存放索引键的相关信息之外,还存储了指向与该 Leaf Node 相邻的后一个 LeafNode 的指针信息,这主要是为了加快检索多个相邻 Leaf Node 的效率考虑。 在 Innodb 存储引擎中,存在两种不同形式的索引,一种是 Cluster 形式的主键索引( Primary Key ),另外一种则是和其他存储引擎(如 MyISAM 存储引擎)存放形式基本相同的普通 B-Tree 索引,这种索引在 Innodb 存储引擎中被称为 Secondary Index 。下面我们通过图示来针对这两种索引的存放 形式做一个比较。
所以,在 Innodb 中如果通过主键来访问数据效率是非常高的,而如果是通过 Secondary Index 来访问数据的话, Innodb 首先通过 Secondary Index 的相关信息,通过相应的索引键检索到 Leaf Node之后,需要再通过 Leaf Node 中存放的主键值再通过主键索引来获取相应的数据行。MyISAM 存储引擎的主键索引和非主键索引差别很小,只不过是主键索引的索引键是一个唯一且非空 的键而已。而且 MyISAM 存储引擎的索引和 Innodb 的 Secondary Index 的存储结构也基本相同,主要的区别只是 MyISAM 存储引擎在 Leaf Nodes 上面出了存放索引键信息之外,再存放能直接定位到 MyISAM 数据文件中相应的数据行的信息(如 Row Number ),但并不会存放主键的键值信息。
索引补充:
1、索引
索引是表的目录,在查找内容之前可以先在目录中查找索引位置,以此快速定位查询数据。对于索引,会保存在额外的文件中。
2、索引种类
- 普通索引:仅加速查询
- 唯一索引:加速查询 + 列值唯一(可以有null)
- 主键索引:加速查询 + 列值唯一 + 表中只有一个(不可以有null)
- 组合索引:多列值组成一个索引,
专门用于组合搜索,其效率大于索引合并 - 全文索引:对文本的内容进行分词,进行搜索
索引合并,使用多个单列索引组合搜索
覆盖索引,select的数据列只用从索引中就能够取得,不必读取数据行,换句话说查询列要被所建的索引覆盖。
3、相关命令
1 - 查看表结构 2 desc 表名 3 4 - 查看生成表的SQL 5 show create table 表名 6 7 - 查看索引 8 show index from 表名 9 10 - 查看执行时间 11 set profiling = 1; 12 SQL... 13 show profiles;
4、使用索引和不使用索引
由于索引是专门用于加速搜索而生,所以加上索引之后,查询效率会快到飞起来。 # 有索引 mysql> select * from tb1 where name = 'sun-888'; +-----+-------------+---------------------+----------------------------------+---------------------+ | nid | name | email | radom | ctime | +-----+-------------+---------------------+----------------------------------+---------------------+ | 889 | sun-888 | sun888@live.com | 5312269e76a16a90b8a8301d5314204b | 2017-10-03 09:33:35 | +-----+-------------+---------------------+----------------------------------+---------------------+ 1 row in set (0.00 sec) # 无索引 mysql> select * from tb1 where email = 'sun888@live.com'; +-----+-------------+---------------------+----------------------------------+---------------------+ | nid | name | email | radom | ctime | +-----+-------------+---------------------+----------------------------------+---------------------+ | 889 | wupeiqi-888 | sun888@live.com | 5312269e76a16a90b8a8301d5314204b | 2017-10-03 09:33:35 | +-----+-------------+---------------------+----------------------------------+---------------------+ 1 row in set (1.23 sec)
5、正确使用索引
数据库表中添加索引后确实会让查询速度起飞,但前提必须是正确的使用索引来查询,如果以错误的方式使用,则即使建立索引也会不奏效。
即使建立索引,索引也不会生效:
1 - like '%xx' 2 select * from tb1 where name like '%cn'; 3 - 使用函数 4 select * from tb1 where reverse(name) = 'sun'; 5 - or 6 select * from tb1 where nid = 1 or email = 'seven@live.com'; 7 特别的:当or条件中有未建立索引的列才失效,以下会走索引 8 select * from tb1 where nid = 1 or name = 'seven'; 9 select * from tb1 where nid = 1 or email = 'seven@live.com' and name = 'alex' 10 - 类型不一致 11 如果列是字符串类型,传入条件是必须用引号引起来,不然... 12 select * from tb1 where name = 999; 13 - != 14 select * from tb1 where name != 'alex' 15 特别的:如果是主键,则还是会走索引 16 select * from tb1 where nid != 123 17 - > 18 select * from tb1 where name > 'alex' 19 特别的:如果是主键或索引是整数类型,则还是会走索引 20 select * from tb1 where nid > 123 21 select * from tb1 where num > 123 22 - order by 23 select email from tb1 order by name desc; 24 当根据索引排序时候,选择的映射如果不是索引,则不走索引 25 特别的:如果对主键排序,则还是走索引: 26 select * from tb1 order by nid desc; 27 28 - 组合索引最左前缀 29 如果组合索引为:(name,email) 30 name and email -- 使用索引 31 name -- 使用索引 32 email -- 不使用索引
6、其他注意事项
1 - 避免使用select * 2 - count(1)或count(列) 代替 count(*) 3 - 创建表时尽量时 char 代替 varchar 4 - 表的字段顺序固定长度的字段优先 5 - 组合索引代替多个单列索引(经常使用多个条件查询时) 6 - 尽量使用短索引 7 - 使用连接(JOIN)来代替子查询(Sub-Queries) 8 - 连表时注意条件类型需一致 9 - 索引散列值(重复少)不适合建索引,例:性别不适合
7、limit分页
无论是否有索引,limit分页是一个值得关注的问题
1 每页显示10条: 2 当前 118 120, 125 3 4 倒序: 5 大 小 6 970 7 6 6 5 54 43 32 7 19 98 8 下一页: 9 10 select 11 * 12 from 13 tb1 14 where 15 nid < (select nid from (select nid from tb1 where nid < 当前页最小值 order by nid desc limit 每页数据 *【页码-当前页】) A order by A.nid asc limit 1) 16 order by 17 nid desc 18 limit 10; 19 20 21 22 select 23 * 24 from 25 tb1 26 where 27 nid < (select nid from (select nid from tb1 where nid < 970 order by nid desc limit 40) A order by A.nid asc limit 1) 28 order by 29 nid desc 30 limit 10; 31 32 33 上一页: 34 35 select 36 * 37 from 38 tb1 39 where 40 nid < (select nid from (select nid from tb1 where nid > 当前页最大值 order by nid asc limit 每页数据 *【当前页-页码】) A order by A.nid asc limit 1) 41 order by 42 nid desc 43 limit 10; 44 45 46 select 47 * 48 from 49 tb1 50 where 51 nid < (select nid from (select nid from tb1 where nid > 980 order by nid asc limit 20) A order by A.nid desc limit 1) 52 order by 53 nid desc 54 limit 10;
8、执行计划
explain + 查询SQL - 用于显示SQL执行信息参数,根据参考信息可以进行SQL优化
1 mysql> explain select * from tb2; 2 +----+-------------+-------+------+---------------+------+---------+------+------+-------+ 3 | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 4 +----+-------------+-------+------+---------------+------+---------+------+------+-------+ 5 | 1 | SIMPLE | tb2 | ALL | NULL | NULL | NULL | NULL | 2 | NULL | 6 +----+-------------+-------+------+---------------+------+---------+------+------+-------+ 7 1 row in set (0.00 sec)
1 id 2 查询顺序标识 3 如:mysql> explain select * from (select nid,name from tb1 where nid < 10) as B; 4 +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+ 5 | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 6 +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+ 7 | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 9 | NULL | 8 | 2 | DERIVED | tb1 | range | PRIMARY | PRIMARY | 8 | NULL | 9 | Using where | 9 +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+ 10 特别的:如果使用union连接气值可能为null 11 12 13 select_type 14 查询类型 15 SIMPLE 简单查询 16 PRIMARY 最外层查询 17 SUBQUERY 映射为子查询 18 DERIVED 子查询 19 UNION 联合 20 UNION RESULT 使用联合的结果 21 ... 22 table 23 正在访问的表名 24 25 26 type 27 查询时的访问方式,性能:all < index < range < index_merge < ref_or_null < ref < eq_ref < system/const 28 ALL 全表扫描,对于数据表从头到尾找一遍 29 select * from tb1; 30 特别的:如果有limit限制,则找到之后就不在继续向下扫描 31 select * from tb1 where email = 'seven@live.com' 32 select * from tb1 where email = 'seven@live.com' limit 1; 33 虽然上述两个语句都会进行全表扫描,第二句使用了limit,则找到一个后就不再继续扫描。 34 35 INDEX 全索引扫描,对索引从头到尾找一遍 36 select nid from tb1; 37 38 RANGE 对索引列进行范围查找 39 select * from tb1 where name < 'alex'; 40 PS: 41 between and 42 in 43 > >= < <= 操作 44 注意:!= 和 > 符号 45 46 47 INDEX_MERGE 合并索引,使用多个单列索引搜索 48 select * from tb1 where name = 'alex' or nid in (11,22,33); 49 50 REF 根据索引查找一个或多个值 51 select * from tb1 where name = 'seven'; 52 53 EQ_REF 连接时使用primary key 或 unique类型 54 select tb2.nid,tb1.name from tb2 left join tb1 on tb2.nid = tb1.nid; 55 56 57 58 CONST 常量 59 表最多有一个匹配行,因为仅有一行,在这行的列值可被优化器剩余部分认为是常数,const表很快,因为它们只读取一次。 60 select nid from tb1 where nid = 2 ; 61 62 SYSTEM 系统 63 表仅有一行(=系统表)。这是const联接类型的一个特例。 64 select * from (select nid from tb1 where nid = 1) as A; 65 possible_keys 66 可能使用的索引 67 68 key 69 真实使用的 70 71 key_len 72 MySQL中使用索引字节长度 73 74 rows 75 mysql估计为了找到所需的行而要读取的行数 ------ 只是预估值 76 77 extra 78 该列包含MySQL解决查询的详细信息 79 “Using index” 80 此值表示mysql将使用覆盖索引,以避免访问表。不要把覆盖索引和index访问类型弄混了。 81 “Using where” 82 这意味着mysql服务器将在存储引擎检索行后再进行过滤,许多where条件里涉及索引中的列,当(并且如果)它读取索引时,就能被存储引擎检验,因此不是所有带where子句的查询都会显示“Using where”。有时“Using where”的出现就是一个暗示:查询可受益于不同的索引。 83 “Using temporary” 84 这意味着mysql在对查询结果排序时会使用一个临时表。 85 “Using filesort” 86 这意味着mysql会对结果使用一个外部索引排序,而不是按索引次序从表里读取行。mysql有两种文件排序算法,这两种排序方式都可以在内存或者磁盘上完成,explain不会告诉你mysql将使用哪一种文件排序,也不会告诉你排序会在内存里还是磁盘上完成。 87 “Range checked for each record(index map: N)” 88 这个意味着没有好用的索引,新的索引将在联接的每一行上重新估算,N是显示在possible_keys列中索引的位图,并且是冗余的。 89 90 详细
更多参见:
http://www.cnblogs.com/xiaoboluo768/p/5400990.html
http://dev.mysql.com/doc/refman/5.7/en/explain-output.html#jointype_system
9、慢日志查询
a、配置MySQL自动记录慢日志
slow_query_log = OFF 是否开启慢日志记录
long_query_time = 2 时间限制,超过此时间,则记录
slow_query_log_file = /usr/slow.log 日志文件
log_queries_not_using_indexes = OFF 为使用索引的搜索是否记录
注:查看当前配置信息:
show variables like '%query%'
修改当前配置:
set global 变量名 = 值
b、查看MySQL慢日志
mysqldumpslow -s at -a /usr/local/var/mysql/MacBook-Pro-3-slow.log
1 """ 2 --verbose 版本 3 --debug 调试 4 --help 帮助 5 6 -v 版本 7 -d 调试模式 8 -s ORDER 排序方式 9 what to sort by (al, at, ar, c, l, r, t), 'at' is default 10 al: average lock time 11 ar: average rows sent 12 at: average query time 13 c: count 14 l: lock time 15 r: rows sent 16 t: query time 17 -r 反转顺序,默认文件倒序拍。reverse the sort order (largest last instead of first) 18 -t NUM 显示前N条just show the top n queries 19 -a 不要将SQL中数字转换成N,字符串转换成S。don't abstract all numbers to N and strings to 'S' 20 -n NUM abstract numbers with at least n digits within names 21 -g PATTERN 正则匹配;grep: only consider stmts that include this string 22 -h HOSTNAME mysql机器名或者IP;hostname of db server for *-slow.log filename (can be wildcard), 23 default is '*', i.e. match all 24 -i NAME name of server instance (if using mysql.server startup script) 25 -l 总时间中不减去锁定时间;don't subtract lock time from total time 26 """