视图
视图相当于把查询结果转化成一个新表(虚拟存在的表),以后可以在这个新表上查询;
数据库中只存放视图的定义,而不存放视图对应的数据,这些数据仍存放在原来的基本表中。所以一旦基本表中发生数据变化,从视图中查询出的数据也就随之改变了;
1、创建或者修改视图
创建视图的语法为:
CREATE [OR REPLACE]
VIEW view_name [column_list...]
AS select_statement
示例:
#加上OR REPLACE参数,如果已经存在该视图,则可以直接替换该视图
#column_list列表的字段要与select查询返回的字段一样,可以起别名
create OR REPLACE
VIEW test_view(t_sellerid,t_name,t_nickname)
as
SELECT sellerid,name,nickname FROM tb_seller;
修改视图的语法为:
ALTER VIEW view_name [column_list...] AS select_statement
示例:
alter VIEW test_view(t_sellerid,t_name)
as
SELECT sellerid,name FROM tb_seller;
修改视图也可以使用创建的语法,只需要加上OR REPLACE参数即可!
2、查看视图
如果需要查询某个视图的定义,可以使用 SHOW CREATE VIEW 命令进行查看。因为视图本身也是一张表,所以查询视图数据的命令与查询表是一样的;
#查看视图的定义
show create view test_view;
#查看视图的数据
SELECT * from test_view;
3、删除视图
DROP VIEW [IF EXISTS] view_name [view_name...]
示例 , 删除视图test_view:
drop VIEW if EXISTS test_view;
4、视图与表的区别
视图与表的区别:
- 视图的创建和删除,不会影响到表
- 视图是已经编译好的SQL语句,是基于SQL语句的结果集的可视化的表,而表不是
- 视图用户只能查询或修改他们所能见到得到的字段与数据
- 视图是虚拟存在的,不占磁盘空间。而表是实际存在的并且是存储在磁盘空间中
为什么要选择视图,而不直接操作表?
- 安全:视图可以不给用户接触数据表,从而不知道表结构。用户只能查询或修改他们所能见到得到的数据,
- 简单:对很复杂的表(或很复杂并且需要频繁用到的查询)建立视图,可以直接过滤掉大部分字段与结果集,用户直接在此视图上操作可以大大提高用户的操作效率;
存储过程和函数
存储过程和函数是事先经过编译并存储在数据库中的一段 SQL 语句的集合,调用存储过程和函数可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。
存储过程和函数的区别在于函数必须有返回值,而存储过程没有。
过程
1、创建存储过程
CREATE PROCEDURE procedure_name ([parameter...])
begin
-- SQL语句
end ;
示例:
create procedure pro_test1()
begin
select 'Hello Mysql' ;
end
2、调用存储过程
call procedure_name() ;
3、 查看存储过程
-- 查询存储过程的状态信息
show procedure status;
-- 查询某个存储过程的定义,需指定数据库。如不指定,默认查询当前会话所属的数据库
show create procedure test.pro_test1 ;
4、删除存储过程
DROP PROCEDURE [IF EXISTS] procedure_name;
5、变量
存储过程是可以编程的,意味着可以使用变量,表达式,控制结构 , 来完成比较复杂的功能。
5.1、DECLARE
通过 DECLARE 可以定义一个局部变量,该变量的作用范围只能在 BEGIN…END 块中
DECLARE var_name type [DEFAULT value],...
示例 :
create procedure pro_test2()
begin
declare num int default 5;
select num+ 10;
end
5.2、SET
直接赋值使用 SET,可以赋常量或者赋表达式,具体语法如下:
SET var_name = expr
示例:
CREATE PROCEDURE pro_test3()
BEGIN
DECLARE NAME VARCHAR(20);
SET NAME = 'MYSQL';
SELECT NAME ;
END
也可以通过select ... into 方式进行赋值操作 :
CREATE PROCEDURE pro_test5()
BEGIN
declare countnum int;
select count(*) into countnum from city;
select countnum;
END
5.3、if条件判断
语法结构如下:
if 判断条件 then 操作
[elseif 判断条件 then 操作] ...
[else 操作]
end if;
需求: 根据定义的身高变量,判定当前身高的所属的身材类型(170 以下一般身材,170 - 180标准身材,180 及以上身材高挑)
create procedure pro_test6()
begin
declare height int default 175;
declare description varchar(50);
if height >= 180 then
set description = '身材高挑';
elseif height >= 170 and height < 180 then
set description = '标准身材';
else
set description = '一般身材';
end if;
select description ;
end
5.4、传递参数
create procedure procedure_name([in/out/inout] 参数名 参数类型)
IN : 该参数可以作为输入,也就是需要调用方传入值 , 默认
OUT: 该参数作为输出,也就是该参数可以作为返回值
INOUT: 既可以作为输入参数,也可以作为输出参数
IN - 输入示例,需求:根据定义的身高变量,判定当前身高的所属的身材类型
create procedure pro_test5(in height int)
begin
declare description varchar(50) default '';
if height >= 180 then
set description='身材高挑';
elseif height >= 170 and height < 180 then
set description='标准身材';
else
set description='一般身材';
end if;
select concat('身高 ', height , '对应的身材类型为:',description);
end
OUT-输出示例,需求:根据传入的身高变量,获取当前身高的所属的身材类型
create procedure pro_test5(in height int , out description varchar(100))
begin
if height >= 180 then
set description='身材高挑';
elseif height >= 170 and height < 180 then
set description='标准身材';
else
set description='一般身材';
end if;
end
调用:
call pro_test5(168, @description)
select @description
小知识:
- @description:在变量名称前面加上“@”符号,叫做用户会话变量,代表整个会话过程他都是有作用的,这个类似于全局变量一样
- @@sort_buffer_size:在变量前加上 "@@" 符号, 叫做 系统变量
5.5、case结构
语法结构如下:
CASE
WHEN 判断条件 THEN 操作
[WHEN 判断条件 THEN 操作] ...
[ELSE 操作]
END CASE;
需求:给定一个月份, 然后计算出所在的季度
create procedure pro_test9(month int)
begin
declare result varchar(20);
case
when month >= 1 and month <=3 then
set result = '第一季度';
when month >= 4 and month <=6 then
set result = '第二季度';
when month >= 7 and month <=9 then
set result = '第三季度';
when month >= 10 and month <=12 then
set result = '第四季度';
end case;
select concat('您输入的月份为 :', month , ' , 该月份为 : ' , result) as content ;
end
5.6、while循环
语法结构:如下:
while 判断条件 do
操作
end while;
需求:计算从1加到n的值
create procedure pro_test8(n int)
begin
declare total int default 0;
declare num int default 1;
while num<=n do
set total = total + num;
set num = num + 1;
end while;
select total;
end
5.7、repeat结构
循环控制语句, 当满足条件的时候退出循环 。while 是满足条件才执行,repeat 是满足条件就退出循环。
语法结构如下:
REPEAT
操作
UNTIL 判断条件
END REPEAT;
需求:计算从1加到n的值
create procedure pro_test10(n int)
begin
declare total int default 0;
repeat
set total = total + n;
set n = n - 1;
until n=0
end repeat;
select total ;
end
5.8、loop语句
LOOP 实现简单的循环,退出循环的条件需要使用其他的语句定义,通常可以使用 LEAVE 语句实现,具体语法如下:
[begin_label:] LOOP
操作
END LOOP [end_label]
如果不在操作中增加退出循环的语句,那么 LOOP 语句可以用来实现简单的死循环。
5.9、leave语句
用来从标注的流程构造中退出,通常和 BEGIN ... END 或者循环一起使用。下面是一个使用 LOOP 和 LEAVE 的简单例子 , 退出循环:
CREATE PROCEDURE pro_test11(n int)
BEGIN
declare total int default 0;
ins: LOOP
IF n <= 0 then
leave ins;
END IF;
set total = total + n;
set n = n - 1;
END LOOP ins;
select total;
END
5.10、游标/光标
游标是用来存储查询结果集的数据类型 , 在存储过程和函数中可以使用光标对结果集进行循环的处理。光标的使用包括光标的声明、OPEN、FETCH 和 CLOSE,其语法分别如下:
#声明光标
DECLARE cursor_name CURSOR FOR select_statement ;
#OPEN 光标
OPEN cursor_name ;
#FETCH 光标
FETCH cursor_name INTO [var_name...]
#CLOSE 光标
CLOSE cursor_name ;
示例 :
初始化脚本:
create table emp(
id int(11) not null auto_increment ,
name varchar(50) not null comment '姓名',
age int(11) comment '年龄',
salary int(11) comment '薪水',
primary key(`id`)
)engine=innodb default charset=utf8 ;
insert into emp(id,name,age,salary) values(null,'金毛狮王',55,3800),(null,'白眉鹰王',60,4000),(null,'青翼蝠王',38,2800),(null,'紫衫龙王',42,1800);
-- 查询emp表中数据, 并逐行获取进行展示
create procedure pro_test11()
begin
declare e_id int(11);
declare e_name varchar(50);
declare e_age int(11);
declare e_salary int(11);
declare emp_result cursor for select * from emp;
open emp_result;
fetch emp_result into e_id,e_name,e_age,e_salary;
select concat('id=',e_id , ', name=',e_name, ', age=', e_age, ', 薪资为: ',e_salary);
fetch emp_result into e_id,e_name,e_age,e_salary;
select concat('id=',e_id , ', name=',e_name, ', age=', e_age, ', 薪资为: ',e_salary);
fetch emp_result into e_id,e_name,e_age,e_salary;
select concat('id=',e_id , ', name=',e_name, ', age=', e_age, ', 薪资为: ',e_salary);
fetch emp_result into e_id,e_name,e_age,e_salary;
select concat('id=',e_id , ', name=',e_name, ', age=', e_age, ', 薪资为: ',e_salary);
#这一行会执行报错,No data - zero rows fetched, selected, or processed。因为当前指针指向已经没有数据了
fetch emp_result into e_id,e_name,e_age,e_salary;
select concat('id=',e_id , ', name=',e_name, ', age=', e_age, ', 薪资为: ',e_salary);
close emp_result;
end
通过循环结构 , 获取游标中的数据 :
create procedure pro_test12()
begin
DECLARE id int(11);
DECLARE name varchar(50);
DECLARE age int(11);
DECLARE salary int(11);
DECLARE has_data int default 1;
DECLARE emp_result CURSOR FOR select * from emp;
#可通过这一行定义解决上述取值报错的问题
DECLARE EXIT HANDLER FOR NOT FOUND set has_data = 0;
open emp_result;
repeat
fetch emp_result into id , name , age , salary;
select concat('id为',id, ', name 为' ,name , ', age为 ' ,age , ', 薪水为: ', salary);
until has_data = 0
end repeat;
close emp_result;
end
函数
语法结构如下:
CREATE FUNCTION function_name([param type ... ])
RETURNS type
BEGIN
...
END;
案例:定义一个函数, 请求满足条件的总记录数 ;
create function count_city(countryId int)
returns int
begin
declare cnum int ;
select count(*) into cnum from city where country_id = countryId;
return cnum;
end
调用:
select count_city(1);
如果执行函数提示:
This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)
这是因为mysql的设置默认是不允许创建函数;
解决办法1:执行:SET GLOBAL log_bin_trust_function_creators = 1;(此方法重启后就失效了)
解决办法2:在my.cnf里面设置log-bin-trust-function-creators=1,不过这个需要重启服务
触发器
触发器是与表有关的数据库对象,指在 insert/update/delete 之前或之后,触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性 , 日志记录 , 数据校验等操作 。
使用别名 OLD 和 NEW 来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。现在触发器还只支持行级触发,不支持语句级触发
触发器类型 | NEW 和 OLD的使用 |
---|---|
INSERT 型触发器 | NEW 表示将要或者已经新增的数据 |
UPDATE 型触发器 | OLD 表示修改之前的数据 , NEW 表示将要或已经修改后的数据 |
DELETE 型触发器 | OLD 表示将要或者已经删除的数据 |
1、创建触发器
语法结构 :
create trigger trigger_name
before/after insert/update/delete
on tbl_name
[ for each row ] -- 行级触发器
begin
trigger_stmt ;
end;
需求:通过触发器记录 emp 表的数据变更日志 , 包含增加, 修改 , 删除 ;
首先创建一张日志表 :
create table emp_logs(
id int(11) not null auto_increment,
operation varchar(20) not null comment '操作类型, insert/update/delete',
operate_time datetime not null comment '操作时间',
operate_id int(11) not null comment '操作表的ID',
operate_params varchar(500) comment '操作参数',
primary key(`id`)
)engine=innodb default charset=utf8;
创建 insert 型触发器,完成插入数据时的日志记录 :
create trigger emp_logs_insert_trigger
after insert
on emp
for each row
begin
insert into emp_logs (id,operation,operate_time,operate_id,operate_params) values(null,'insert',now(),new.id,concat('插入后(id:',new.id,', name:',new.name,', age:',new.age,', salary:',new.salary,')'));
end
创建 update 型触发器,完成更新数据时的日志记录 :
create trigger emp_logs_update_trigger
after update
on emp
for each row
begin
insert into emp_logs (id,operation,operate_time,operate_id,operate_params) values(null,'update',now(),new.id,concat('修改前(id:',old.id,', name:',old.name,', age:',old.age,', salary:',old.salary,') , 修改后(id',new.id, 'name:',new.name,', age:',new.age,', salary:',new.salary,')'));
end
创建delete 行的触发器 , 完成删除数据时的日志记录 :
create trigger emp_logs_delete_trigger
after delete
on emp
for each row
begin
insert into emp_logs (id,operation,operate_time,operate_id,operate_params) values(null,'delete',now(),old.id,concat('删除前(id:',old.id,', name:',old.name,', age:',old.age,', salary:',old.salary,')'));
end
测试:
insert into emp(id,name,age,salary) values(null, '光明左使',30,3500);
update emp set age = 39 where id = 3;
delete from emp where id = 5;
2、删除触发器
语法结构 :
drop trigger [schema_name.]trigger_name
如果没有指定 schema_name,默认为当前数据库 。
3、查看触发器
可以通过执行 SHOW TRIGGERS 命令查看触发器的状态、语法等信息。
语法结构 :
show triggers;
存储引擎
1、概述
存储引擎就是存储数据,建立索引,更新查询数据等等技术的实现方式 。存储引擎是基于表的,而不是基于库的。所以存储引擎也可被称为表类型;
Oracle,SqlServer等数据库只有一种存储引擎。MySQL提供了插件式的存储引擎架构。所以MySQL存在多种存储引擎,可以根据需要使用相应引擎,或者编写存储引擎;
可以通过命令 show engines , 来查询当前数据库支持的存储引擎 :
创建新表时如果不指定存储引擎,那么系统就会使用默认的存储引擎,MySQL5.5之前的默认存储引擎是MyISAM,5.5之后就改为了InnoDB。
查看Mysql数据库默认的存储引擎 , 指令 :show variables like '%storage_engine%' ;
InnoDB与MyISAM的差别(着重以下三点):
特点 | InnoDB | MyISAM |
---|---|---|
事务安全 | 支持 | 不支持 |
锁机制 | 行锁(适合高并发) | 表锁 |
支持外键 | 支持 | 不支持 |
2、InnoDB
InnoDB存储引擎是Mysql的默认存储引擎,但是对比MyISAM的存储引擎,InnoDB写的处理效率差一些,并且会占用更多的磁盘空间以保留数据和索引。
InnoDB存储引擎不同于其他存储引擎的特点:
事务控制
InnoDB存储引擎提供了具有提交、回滚、崩溃恢复能力的事务安全:
start transaction;
#插入后,在另一台机器上访问数据,发现并没有添加进去。
insert into emp(id,name,age,salary) values(null, '光明左使',30,3500);
commit;
测试,发现在InnoDB中是存在事务的 ;
外键约束
MySQL支持外键的存储引擎只有InnoDB , 在创建外键的时候, 要求父表必须有对应的索引 , 子表在创建外键的时候, 也会自动的创建对应的索引;
存储方式
InnoDB 存储表和索引有以下两种方式 :
-
使用共享表空间存储, 这种方式创建的表的表结构保存在.frm文件中, 数据和索引保存在 innodb_data_home_dir 和 innodb_data_file_path定义的表空间中,可以是多个文件。
-
使用多表空间存储, 这种方式创建的表的表结构仍然存在 .frm 文件中,但是每个表的数据和索引单独保存在 .ibd 中。
MySql的左连接,右连接,内连接,全连接
本地数据库中有两张表 tcount_tbl 和 runoob_tbl。两张数据表数据如下:
左连接(left join)
LEFT JOIN 会读取左边数据表的全部数据,即便右边表无对应数据;
右连接(right join)
RIGHT JOIN 会读取右边数据表的全部数据,即便左边边表无对应数据;
内连接(inner join)
获取两个表中字段匹配关系的记录;
全连接(full join)
mysql不支持全连接,但可以通过左外连接+ union+右外连接实现;
事务(ACID)
事务一般都是以下四点:
- 原子性(Atomicity)
- 一致性 (Consistency)
- 隔离性 (Isolation)
- 持续性 (Durability)
1、原子性(Atomicity)
原子是世界上的最小单位,具有不可分割性;
事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做 ;
2、一致性 (Consistency)
ACID里的AID都是数据库的特征,也就是依赖数据库的具体实现。而唯独这个C,实际上它依赖于应用层,也就是依赖于开发者。(这里的一致性是指系统从一个正确的状态,迁移到另一个正确的状态。什么叫正确的状态呢?就是当前的状态满足预定的约束就叫做正确的状态。) 而事务具备ACID里C的特性是说通过事务的AID来保证我们的一致性.
这里我们举个大家都在说的财务系统的例子:
A要向B支付100元,而A的账户中只有90元,并且我们给定账户余额这一列的约束是,不能小于0。那么很明显这条事务执行会失败,因为90-100=-10,小于我们给定的约束了。
这个例子里,支付之前我们数据库里的数据都是符合约束的,但是如果事务执行成功了,我们的数据库数据就破坏约束了,因此事务不能成功,这里我们说事务提供了一致性的保证。然后我们再看个例子
A要向B支付100元,而A的账户中只有90元,我们的账户余额列没有任何约束。但是我们业务上不允许账户余额小于0,因此支付完成后我们会检查A的账户余额,发现余额小于0了,于是我们进行了事务的回滚。
这个例子里,如果事务执行成功,虽然没有破坏数据库的约束,但是破坏了我们应用层的约束。而事务的回滚保证了我们的约束,因此也可以说事务提供了一致性保证(ps:事实上,是我们应用层利用事务回滚保证了我们的约束不被破坏)。最后我们再看个例子
A要向B支付100元,而A的账户中只有90元,我们的账户余额列没有任何约束。然后支付成功了。
这里,如果按照很多人的理解,事务不是保证一致性么?直观上账户余额为什么能为负呢。但这里事务执行前和执行后,我们的系统没有任何的约束被破坏。一直都是保持正确的状态。
所以,综上。你可以理解一致性就是:应用系统从一个正确的状态到另一个正确的状态。而ACID就是说事务能够通过AID来保证这个C的过程。C是目的,AID都是手段。
3、隔离性 (Isolation)
一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
4、持续性 (Durability)
也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。
事务的隔离级别
CREATE TABLE `demo` (
`id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `demo_01`.`demo`(`id`, `name`) VALUES (1, 'a');
INSERT INTO `demo_01`.`demo`(`id`, `name`) VALUES (5, 'b');
INSERT INTO `demo_01`.`demo`(`id`, `name`) VALUES (7, 'c');
INSERT INTO `demo_01`.`demo`(`id`, `name`) VALUES (9, 'd');
Read uncommitted(读未提交)
如果一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据。这样就避免了更新丢失,却可能出现脏读,也就是说事务B读取到了事务A未提交的数据;
但还是可能会出现脏读:
事务一 | 事务二 |
---|---|
Read committed(读已提交)
如果是一个读事务,则允许其他事务读写,如果是写事务将会禁止其他事务访问该行数据,该隔离级别避免了脏读,但是可能出现不可重复读。事务A事先读取了数据,事务B紧接着更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。
但还是可能会出现不可重复读(重复读数据就变了):
事务一 | 事务二 |
---|---|
Repeatable read(可重复读)
可重复读取是指在一个事务内,多次读同一个数据,读到的数据是一样的,因此称为是可重复读隔离级别。
但是在使用当前读的情况下还会出现幻读(其实也会出现不可重复读,不过根据官方的话来说MySQL是允许这样的):
这不是bug,而是刻意为之
“This is not a bug but an intended and documented behavior.”
事务一 | 事务二 |
---|---|
Serializable(可串行化)
提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。序列化是最高的事务隔离级别,同时代价也是最高的,性能很低,一般很少使用!
在MYSQL数据库中,支持上面四种隔离级别,默认的为Repeatable read(可重复读);而在Oracle数据库中,只支Serializeble(串行化)级别和Read committed(读已提交)这两种级别,其中默认的为Read committed级别;
在MYSQL数据库中查看或修改当前事务的隔离级别:
#设置事务隔离级别为可重复读
set session transaction isolation level REPEATABLE READ;
#查看当前事务隔离级别
show VARIABLES like 'transaction_isolation';
为什么一个事务能读取到其他事务修改后未提交的数据? 在没有正式提交之前,数据是先更新到日志之中的,只有commit提交之后,才真正再从日志中更新到mysql表中;
当设置事务隔离级别为Read Ucommitted的时候,一个事务(A)能读取到另一个事务(B)修改后【未提交】的数据。
不可重复读,幻读和脏读
脏读 :
脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问这个数据,然后使用了这个数据。
不可重复读 :
是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据并进行修改。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,第一个事务两次读到的的数据可能是不一样的。
幻读 :
一个事务在前后两次查询同一个范围的时候、后一次查询看到了前一次查询未看到的行
注意:
- 在可重复读隔离级别下、普通查询是快照读、不会看到别的事务插入数据, 所以、只会在当前读下才会出现幻读;
- 幻读仅指新插入的行被读到、修改后满足条件而被读取到,不能称为幻读;
快照读与当前读
当前读:select...lock in share mode; select...for update;
当前读:update、insert、delete
快照读:select
快照读
当执行select的时候,innodb默认会执行快照读,相当于就是给目前的数据照了一张照片,以后执行select 的时候就会返回当前照片里面的数据,当其他事务提交了也对你不造成影响,和你没关系,这就实现了可重复读了。
那这个照片是什么时候生成的呢?不是开启事务的时候,是当你第一次执行select的时候,也就是说,当A开启了事务,然后没有执行任何操作,这时候B insert了一条数据然后commit,这时候A执行 select,那么返回的数据中就会有B添加的那条数据......之后无论再有其他事务commit都没有关系,因为照片已经生成了,而且不会再生成了,以后都会参考这张照片!
当前读
当前读会读取最新的记录,也就是说别的事务虽然还没提交的数据你也可以看到;
索引
1、什么是索引
索引在MySQL中也叫做“键”或者"key"(primary key,unique key,还有一个index key),是存储引擎用于快速找到记录的一种数据结构。索引对于良好的性能非常关键,尤其是当表中的数据量越来越大时,索引对于性能的影响愈发重要,减少io次数,加速查询。(其中primary key和unique key,除了有加速查询的效果之外,还有约束的效果,primary key 不为空且唯一,unique key 唯一,而index key只有加速查询的效果,没有约束效果)
索引相当于字典的音序表,如果要查某个字,如果不使用音序表,则需要从几百页中逐页去查。
一旦为表创建了索引,以后的查询最好先查索引,再根据索引定位的结果去找数据
示例:
SELECT * FROM Employee WHERE Employee_Name = 'Jesus'
一旦我们运行这个查询,在查找名字为Jesus的雇员的过程中,究竟会发生什么?数据库不得不去遍历Employee表中的每一行并确定雇员的名字(Employee_Name)是否为 ‘Jesus’。由于我们想要得到每一个名字为Jesus的雇员信息,在查询到第一个符合条件的行后,不能停止查询,因为可能还有其他符合条件的行。所以,必须一行一行的查找直到最后一行,这就意味数据库不得不检查上千行数据才能找到所以名字为Jesus的雇员。这就是所谓的全表扫描。
2、索引的分类
1) 单值索引 :即一个索引只包含单个列,一个表可以有多个单列索引
2) 唯一索引 :索引列的值必须唯一,但允许有空值
3) 复合索引 :即一个索引包含多个列
3、聚集索引与普通索引
InnoDB有两大类索引:
- 聚集索引(Clustered Index):数据的物理存放顺序与索引顺序是一致的,叶子节点存储行记录,默认通过主键聚集数据,如果没有定义主键,则选择第一个非空的唯一索引,如果没有非空唯一索引,则选择rowid来作为聚集索引;
- 非聚集索引(Secondary Index):叶子节点存储主键值,除此之外的表上的每个非聚簇索引都是二级索引,又叫辅助索引;
所以,对于聚集索引来说,你创建主键的时候,自动就创建了主键的聚集索引,并且不管怎样都会有聚集索引存在!
- normal:表示普通索引;
- unique:表示唯一的,不允许重复的索引,如果该字段信息保证不会重复例如身份证号用作索引时,可设置为unique;
- full textl: 表示 全文搜索的索引, FULLTEXT用于搜索很长一篇文章的时候,效果最好。用在比较短的文本,如果就一两行字的,普通的INDEX也可以;
总结,索引的类别由建立索引的字段内容特性来决定,通常normal最常见。
4、索引覆盖
如果一个索引包含(或覆盖)所有需要查询的字段的值,称为‘覆盖索引’。即只需扫描索引而无须回表。
回表:先通过普通索引定位到主键值(普通索引叶子节点存储的是主键值),在通过聚集索引定位到行记录。这就是所谓的回表查询,先定位主键值,再定位行记录,它的性能较扫一遍索引树更低。
5、索引的使用操作
5.1创建索引
CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name [USING index_type] ON tbl_name(index_col_name,...)
示例:
CREATE INDEX index_city_name ON city(city_name);
5.2查看索引
show index from table_name;
示例:
show INDEX from city;
5.3删除索引
DROP INDEX index_name ON tbl_name;
示例:
DROP INDEX index_city_name ON city;
5.4ALTER命令
1). alter table tb_name add primary key(column_list);
该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL
2). alter table tb_name add unique index_name(column_list);
这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)
3). alter table tb_name add index index_name(column_list);
添加普通索引, 索引值可以出现多次。
4). alter table tb_name add fulltext index_name(column_list);
该语句指定了索引为FULLTEXT, 用于全文索引
6、避免索引失效
1). 全值匹配 ,对索引中所有列都指定具体值。
该情况下,索引生效,执行效率高。
explain select * from tb_seller where name='小米科技' and status='1' and address='北京市';
2). 最左前缀法则
如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始,并且不跳过索引中的列。
匹配最左前缀法则,走索引:
explain select * from tb_seller where name='小米科技';
explain select * from tb_seller where name='小米科技' and status='1';
explain select * from tb_seller where name='小米科技' and status='1' and address='北京市';
违法最左前缀法则 , 索引失效:
explain select * from tb_seller where status='1';
explain select * from tb_seller where status='1' and address='北京市';
如果符合最左法则,但是出现跳跃某一列,只有最左列索引生效:
explain select * from tb_seller where name='小米科技' and address='北京市';
最左前缀法则就像上楼一样,不先上一楼怎么上二楼三楼,上了一楼,又不上二楼怎么上三楼!
3). 范围查询右边的列,不能使用索引 。
#address索引失效
explain select * from tb_seller where name='小米科技' and status>'1' and address='北京市';
根据前面的两个字段name , status 查询是走索引的, 但是最后一个条件address 没有用到索引。
4). 不要在索引列上进行运算操作, 索引将失效。
explain select * from tb_seller where SUBSTR(name,3,2)='科技';
5). 字符串不加单引号,造成索引失效。
#name,status 是走索引的
explain select * from tb_seller where name='小米科技' and status='1';
#只有name走索引了
explain select * from tb_seller where name='小米科技' and status=1;
原因:在查询时如果没有对字符串加单引号,MySQL的查询优化器,会自动的进行类型转换,造成索引失效。
6). 尽量使用覆盖索引,避免select *
只访问索引的查询(索引列完全包含查询列) 。
explain select * from tb_seller where name='小米科技' and status='1' and address='北京市';
explain select name from tb_seller where name='小米科技' and status='1' and address='北京市';
explain select name,status from tb_seller where name='小米科技' and status='1' and address='北京市';
explain select name,status,address from tb_seller where name='小米科技' and status='1' and address='北京市';
如果查询列,超出索引列,也会降低性能。
#password 字段还需要回表查询
explain select name,status,address,password from tb_seller where name='小米科技' and status='1' and address='北京市';
TIP :
using index :使用覆盖索引的时候就会出现
using where:在查找使用索引的情况下,需要回表去查询所需的数据
using index condition:查找使用了索引,但是需要回表查询数据
using index ; using where:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据
7). 用or分割开的条件, 如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。
示例,name字段是索引列 ,而createtime不是索引列,中间是or进行连接是不走索引的 :
explain select * from tb_seller where name='黑马程序员' or createtime = '2088-01-01 12:00:00';
8). 以%开头的Like模糊查询,索引失效。
如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。
#索引失效
explain select * from tb_seller where name like '%小米科技';
#走索引
explain select * from tb_seller where name like '小米科技%';
#索引失效
explain select * from tb_seller where name like '%小米科技%';
解决方案 : 通过覆盖索引来解决
explain select name from tb_seller where name like '%小米科技';
explain select name from tb_seller where name like '%小米科技%';
9). 如果MySQL评估使用索引比全表更慢,则不使用索引。
#创建address索引
CREATE INDEX index_address ON tb_seller(address);
#不走address索引
explain select * from tb_seller where address = '北京市';
#走address索引
explain select * from tb_seller where address = '西安市';
原因如下图所示:
10). is NULL , is NOT NULL 有时索引会失效(原因与第9点一样)。
11). IN 走索引, NOT IN 索引失效。
#走索引
explain select * from tb_seller where name IN ('小米科技');
#不走索引
explain select * from tb_seller where name NOT IN ('小米科技');
12). 单列索引和复合索引。
尽量使用复合索引,而少使用单列索引 。
create index idx_name_sta_address on tb_seller(name, status, address);
创建复合索引 就相当于创建了三个索引 :
name
name + status
name + status + address
创建单列索引
create index idx_seller_name on tb_seller(name);
create index idx_seller_status on tb_seller(status);
create index idx_seller_address on tb_seller(address);
数据库会选择一个最优的索引(辨识度最高索引)来使用,并不会使用全部索引 。
7、查看索引使用情况
show status like 'Handler_read%';
show global status like 'Handler_read%';
各个值的释义如下:
Handler_read_first:索引中第一条被读的次数。如果较高,表示服务器正执行大量全索引扫描(这个值越低越好)。
Handler_read_key:如果索引正在工作,这个值代表一个行被索引值读的次数,如果值越低,表示索引得到的性能改善不高,因为索引不经常使用(这个值越高越好)。
Handler_read_next :按照键顺序读下一行的请求数。如果你用范围约束或如果执行索引扫描来查询索引列,该值增加。
Handler_read_prev:按照键顺序读前一行的请求数。该读方法主要用于优化ORDER BY ... DESC。
Handler_read_rnd :根据固定位置读一行的请求数。如果你正执行大量查询并需要对结果进行排序该值较高。你可能使用了大量需要MySQL扫描整个表的查询或你的连接没有正确使用键。这个值较高,意味着运行效率低,应该建立索引来补救。
Handler_read_rnd_next:在数据文件中读下一行的请求数。如果你正进行大量的表扫描,该值较高。通常说明你的表索引不正确或写入的查询没有利用索引。
优化SQL步骤
当面对一个有 SQL 性能问题的数据库时,我们应该从何处入手来进行系统的分析,使得能够尽快定位问题 SQL 并尽快解决问题。
1、查看SQL执行频率
MySQL 客户端连接成功后,通过 show [session|global] status 命令可以提供服务器状态信息。
- session:当前连接的统计结果(默认)
- global:自数据库上次启动至今的统计结果
例如,下面的命令显示了当前 session 中所有统计参数的值:
show status;
show status like 'Com_______';
show status like 'Innodb_rows_%';
Com_xxx 表示每个 xxx 语句执行的次数,我们通常比较关心的是以下几个统计参数:
参数 | 含义 |
---|---|
Com_select | 执行 select 操作的次数,一次查询只累加 1 |
Com_insert | 执行 INSERT 操作的次数,对于批量插入的 INSERT 操作,只累加一次 |
Com_update | 执行 UPDATE 操作的次数 |
Com_delete | 执行 DELETE 操作的次数 |
Innodb_rows_read | select 查询返回的行数 |
Innodb_rows_inserted | 执行 INSERT 操作插入的行数 |
Innodb_rows_updated | 执行 UPDATE 操作更新的行数 |
Innodb_rows_deleted | 执行 DELETE 操作删除的行数 |
Connections | 试图连接 MySQL 服务器的次数 |
Uptime | 服务器工作时间 |
Slow_queries | 慢查询的次数 |
注意:
- Com_***:这些参数对于所有存储引擎的表操作都会进行累计
- Innodb_***:这几个参数只是针对InnoDB 存储引擎的,累加的算法也略有不同
2、定位低效率执行SQL
可以通过以下两种方式定位执行效率较低的 SQL 语句:
- 慢查询日志:通过慢查询日志定位那些执行效率较低的 SQL 语句,用--log-slow-queries[=file_name]选项启动时,mysql会写一个包含所有执行时间超过 long_query_time 秒的 SQL 语句的日志文件;
- show processlist:查看当前MySQL在进行的线程,包括线程的状态、是否锁表等,可以实时地查看 SQL 的执行情况,同时对一些锁表操作进行优化
对应字段释义如下:
1) id列:用户登录mysql时,系统分配的"connection_id",可以使用函数connection_id()查看
2) user列:显示当前用户。如果不是root,这个命令就只显示用户权限范围的sql语句
3) host列:显示这个语句是从哪个ip的哪个端口上发的,可以用来跟踪出现问题语句的用户
4) db列:显示这个进程目前连接的是哪个数据库
5) command列:显示当前连接的执行的命令,一般取值为休眠(sleep),查询(query),连接(connect)等
6) time列:显示这个状态持续的时间,单位是秒
7) state列:显示使用当前连接的sql语句的状态,很重要的列。state描述的是语句执行中的某一个状态。一个sql语句,以查询为例,可能需要经过copying to tmp table、sorting result、sending data等状态才可以完成
8) info列:显示这个sql语句,是判断问题语句的一个重要依据
3、explain分析执行计划
通过以上步骤查询到效率低的 SQL 语句后,可以通过 EXPLAIN或者 DESC命令获取 MySQL如何执行 SELECT 语句的信息,包括在 SELECT 语句执行过程中表如何连接和连接的顺序。
查询SQL语句的执行计划 :
explain select * from tb_item where id = 1;
对应字段释义如下:
1) id列:select查询的序列号,是一组数字,表示的是查询中执行select子句或者是操作表的顺序
2) select_type列:表示 SELECT 的类型,常见的取值有 SIMPLE(简单表,即不使用表连接或者子查询)、PRIMARY(主查询,即外层的查询)UNION(UNION 中的第二个或者后面的查询语句)、SUBQUERY(子查询中的第一个 SELECT)等
3) table列:输出结果集的表
4) type列:表示表的连接类型,性能由好到差的连接类型为( system ---> const -----> eq_ref ------> ref -------> ref_or_null----> index_merge ---> index_subquery -----> range -----> index ------> all )
5) possible_keys列:表示查询时,可能使用的索引(一个或多个)
6) key列:表示实际使用的索引(如果为NULL, 则没有使用索引)
7) key_len列:索引字段的长度(该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下, 长度越短越好)
8) rows列:扫描行的数量
9) extra列:执行情况的说明和描述
以下着重讲解一些比较难理解的字段:
3.1、explain 之 id
id 情况共分为三种 :
- id 相同:表示加载表的顺序是从上到下
- id 不同:id值越大,优先级越高,越先被执行
- id 有相同,也有不同:id相同的可以认为是一组,从上往下顺序执行,在所有的组中,id的值越大,优先级越高,越先执行
3.2、explain 之 select_type
select_type 常见的取值,如下表所示:
select_type | 含义 |
---|---|
SIMPLE | 简单的select查询,查询中不包含子查询或者UNION |
PRIMARY | 查询中若包含任何复杂的子查询,最外层查询标记为该标识 |
SUBQUERY | 在SELECT 或 WHERE 列表中包含了子查询 |
DERIVED | 在FROM 列表中包含的子查询,被标记为 DERIVED(衍生) MYSQL会递归执行这些子查询,把结果放在临时表中 |
UNION | 若第二个SELECT出现在UNION之后,则标记为UNION ; 若UNION包含在FROM子句的子查询中,外层SELECT将被标记为DERIVED |
UNION RESULT | 从UNION表获取结果的SELECT |
3.3、explain 之 type
type 显示的是访问类型,是较为重要的一个指标,可取值为:
type | 含义 |
---|---|
NULL | MySQL不访问任何表,索引,直接返回结果 |
system | 表只有一行记录(等于系统表),这是const类型的特例,一般不会出现 |
const | 表示通过索引一次就找到了,const 用于比较primary key 或者 unique 索引。因为只匹配一行数据,所以很快。如将主键置于where列表中,MySQL 就能将该查询转换为一个常亮。const于将 "主键" 或 "唯一" 索引的所有部分与常量值进行比较 |
eq_ref | 类似ref,区别在于使用的是唯一索引,使用主键的关联查询,关联查询出的记录只有一条。常见于主键或唯一索引扫描 |
ref | 非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,返回所有匹配某个单独值的所有行(多个) |
range | 只检索给定返回的行,使用一个索引来选择行。 where 之后出现 between , < , > , in 等操作。 |
index | index 与 ALL的区别为 index 类型只是遍历了索引树, 通常比ALL 快, ALL 是遍历数据文件。 |
all | 将遍历全表以找到匹配的行 |
结果值从最好到最坏以此是:NULL > system > const > eq_ref > ref > range > index > ALL
3.4、explain 之 extra
其他的额外的执行计划信息,在该列展示
extra | 含义 |
---|---|
using filesort | 说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取, 称为 “文件排序”, 效率低。 |
using temporary | 使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于 order by 和 group by; 效率低 |
using index | 表示相应的select操作使用了覆盖索引, 避免访问表的数据行, 效率不错。 |
4、show profile分析SQL
show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。
#查看当前MySQL是否支持profile
SELECT @@have_profiling;
#查看当前MySQL是否支持profile
SELECT @@profiling;
#开启profiling 开关
set profiling=1;
通过profile,我们能够更清楚地了解SQL执行的过程,例如:我们可以执行以下一系列的操作
show databases;
use demo_01;
show tables;
select * from tb_item where id < 5;
select count(*) from tb_item;
再执行show profiles 指令, 来查看SQL语句执行的耗时:
通过show profile for query query_id 语句可以查看到该SQL执行过程中每个线程的状态和消耗的时间:
注意:Sending data 状态表示MySQL线程开始访问数据行并把结果返回给客户端,而不仅仅是返回个客户端。由于在Sending data状态下,MySQL线程往往需要做大量的磁盘读取操作,所以经常是整各查询中耗时最长的状态
在获取到最消耗时间的线程状态后,MySQL支持进一步选择all、cpu、block io 、context switch、page faults等明细类型类查看MySQL在使用什么资源上耗费了过高的时间。例如,选择查看CPU的耗费时间 :
各个字段的含义:
字段 | 含义 |
---|---|
Status | sql 语句执行的状态 |
Duration | sql 执行过程中每一个步骤的耗时 |
CPU_user | 当前用户占有的cpu |
CPU_system | 系统占有的cpu |
5、trace分析优化器执行计划
通过trace文件能够进一步了解为什么优化器选择A计划, 而不是选择B计划。
#打开trace ,设置格式为 JSON
SET optimizer_trace="enabled=on",end_markers_in_json=on;
#设置trace最大能够使用的内存大小,避免解析过程中因为默认内存过小而不能够完整展示
set optimizer_trace_max_mem_size=1000000;
#查询语句
select * from tb_item WHERE id<5;
#查看MySQL是如何执行SQL
select * from information_schema.OPTIMIZER_TRACE;
该查询语句trace如下:
{
"steps": [
{
"join_preparation": {
"select#": 1,
"steps": [
{
"expanded_query": "/* select#1 */ select `tb_item`.`id` AS `id`,`tb_item`.`title` AS `title`,`tb_item`.`price` AS `price`,`tb_item`.`num` AS `num`,`tb_item`.`categoryid` AS `categoryid`,`tb_item`.`status` AS `status`,`tb_item`.`sellerid` AS `sellerid`,`tb_item`.`createtime` AS `createtime`,`tb_item`.`updatetime` AS `updatetime` from `tb_item` where (`tb_item`.`id` < 5)"
}
]
}
},
{
"join_optimization": {
"select#": 1,
"steps": [
{
"condition_processing": {
"condition": "WHERE",
"original_condition": "(`tb_item`.`id` < 5)",
"steps": [
{
"transformation": "equality_propagation",
"resulting_condition": "(`tb_item`.`id` < 5)"
},
{
"transformation": "constant_propagation",
"resulting_condition": "(`tb_item`.`id` < 5)"
},
{
"transformation": "trivial_condition_removal",
"resulting_condition": "(`tb_item`.`id` < 5)"
}
]
}
},
{
"substitute_generated_columns": {}
},
{
"table_dependencies": [
{
"table": "`tb_item`",
"row_may_be_null": false,
"map_bit": 0,
"depends_on_map_bits": []
}
]
},
{
"ref_optimizer_key_uses": []
},
{
"rows_estimation": [
{
"table": "`tb_item`",
"range_analysis": {
"table_scan": {
"rows": 656482,
"cost": 68907
},
"potential_range_indexes": [
{
"index": "PRIMARY",
"usable": true,
"key_parts": [
"id"
]
}
],
"setup_range_conditions": [],
"group_index_range": {
"chosen": false,
"cause": "not_group_by_or_distinct"
},
"skip_scan_range": {
"potential_skip_scan_indexes": [
{
"index": "PRIMARY",
"usable": false,
"cause": "query_references_nonkey_column"
}
]
},
"analyzing_range_alternatives": {
"range_scan_alternatives": [
{
"index": "PRIMARY",
"ranges": [
"id < 5"
],
"index_dives_for_eq_ranges": true,
"rowid_ordered": true,
"using_mrr": false,
"index_only": false,
"rows": 4,
"cost": 1.3666,
"chosen": true
}
],
"analyzing_roworder_intersect": {
"usable": false,
"cause": "too_few_roworder_scans"
}
},
"chosen_range_access_summary": {
"range_access_plan": {
"type": "range_scan",
"index": "PRIMARY",
"rows": 4,
"ranges": [
"id < 5"
]
},
"rows_for_plan": 4,
"cost_for_plan": 1.3666,
"chosen": true
}
}
}
]
},
{
"considered_execution_plans": [
{
"plan_prefix": [],
"table": "`tb_item`",
"best_access_path": {
"considered_access_paths": [
{
"rows_to_scan": 4,
"access_type": "range",
"range_details": {
"used_index": "PRIMARY"
},
"resulting_rows": 4,
"cost": 1.7666,
"chosen": true
}
]
},
"condition_filtering_pct": 100,
"rows_for_plan": 4,
"cost_for_plan": 1.7666,
"chosen": true
}
]
},
{
"attaching_conditions_to_tables": {
"original_condition": "(`tb_item`.`id` < 5)",
"attached_conditions_computation": [],
"attached_conditions_summary": [
{
"table": "`tb_item`",
"attached": "(`tb_item`.`id` < 5)"
}
]
}
},
{
"finalizing_table_conditions": [
{
"table": "`tb_item`",
"original_table_condition": "(`tb_item`.`id` < 5)",
"final_table_condition ": "(`tb_item`.`id` < 5)"
}
]
},
{
"refine_plan": [
{
"table": "`tb_item`"
}
]
}
]
}
},
{
"join_execution": {
"select#": 1,
"steps": []
}
}
]
}
SQL优化
1、大批量插入数据
当使用load 命令导入数据的时候,适当的设置可以提高导入的效率。对于 InnoDB 类型的表,有以下几种方式可以提高导入的效率:
1) 主键顺序插入
因为InnoDB类型的表是按照主键的顺序保存的,所以将导入的数据按照主键的顺序排列,可以有效的提高导入数据的效率。如果InnoDB表没有主键,那么系统会自动默认创建一个内部列作为主键,所以如果可以给表创建一个主键,将可以利用这点,来提高导入数据的效率
2) 关闭唯一性校验
在导入数据前执行 SET UNIQUE_CHECKS=0,关闭唯一性校验,在导入结束后执行SET UNIQUE_CHECKS=1,恢复唯一性校验,可以提高导入的效率。
3) 手动提交事务
如果应用使用自动提交的方式,建议在导入前执行 SET AUTOCOMMIT=0,关闭自动提交,导入结束后再执行 SET AUTOCOMMIT=1,打开自动提交,也可以提高导入的效率。
2、优化insert语句
当进行数据的insert操作的时候,可以考虑采用以下几种优化方案。
如果需要同时对一张表插入很多行数据时,应该尽量使用多个值表的insert语句,这种方式将大大的缩减客户端与数据库之间的连接、关闭等消耗。使得效率比分开执行的单个insert语句快;
示例:
#原始方式为:
insert into tb_test values(1,'Tom');
insert into tb_test values(2,'Cat');
insert into tb_test values(3,'Jerry');
#优化后的方案为 :
insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry');
在事务中进行数据插入。
start transaction;
insert into tb_test values(1,'Tom');
insert into tb_test values(2,'Cat');
insert into tb_test values(3,'Jerry');
commit;
数据有序插入
#原始方式为:
insert into tb_test values(4,'Tim');
insert into tb_test values(1,'Tom');
insert into tb_test values(3,'Jerry');
insert into tb_test values(5,'Rose');
insert into tb_test values(2,'Cat');
#优化后的方案为 :
insert into tb_test values(1,'Tom');
insert into tb_test values(2,'Cat');
insert into tb_test values(3,'Jerry');
insert into tb_test values(4,'Tim');
insert into tb_test values(5,'Rose');
2、优化order by语句
order by有两种排序方式,分别如下:
- 通过对返回数据进行排序,也就是通常说的 filesort 排序,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序
- 通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高
Filesort 的优化
通过创建合适的索引,能够减少 Filesort 的出现,但是在某些情况下,条件限制不能让Filesort消失,那就需要加快 Filesort的排序操作。对于Filesort , MySQL 有两种排序算法:
- 两次扫描算法 :MySQL4.1 之前,使用该方式排序(这里就不再深入)。
- 一次扫描算法:一次性取出满足条件的所有字段,然后在排序区 sort buffer 中排序后直接输出结果集。排序时内存开销较大,但是排序效率比两次扫描算法要高。
MySQL 通过比较系统变量 max_length_for_sort_data 的大小和Query语句取出的字段总大小, 来判定是否那种排序算法,如果max_length_for_sort_data 更大,那么使用第二种优化之后的算法;否则使用第一种。
可以适当提高 sort_buffer_size 和 max_length_for_sort_data 系统变量,来增大排序区的大小,提高排序的效率。
show VARIABLES LIKE'max_length_for_sort_data';
show VARIABLES like 'sort_buffer_size';
优化group by 语句
由于GROUP BY 实际上也同样会进行排序操作,而且与ORDER BY 相比,GROUP BY 主要只是多了排序之后的分组操作。当然,如果在分组的时候还使用了其他的一些聚合函数,那么还需要一些聚合函数的计算。所以,在GROUP BY 的实现过程中,与 ORDER BY 一样也可以利用到索引。
如果查询包含 group by 但是用户想要避免排序结果的消耗, 则可以执行order by null 禁止排序。如下 :
explain select age,count(*) from emp group by age;
优化后
explain select age,count(*) from emp group by age order by null;
从上面的例子可以看出,第一个SQL语句需要进行"filesort",而第二个SQL由于order by null 不需要进行 "filesort", 而上文提过Filesort往往非常耗费时间。
优化嵌套查询
使用子查询可以一次性的完成很多逻辑上需要多个步骤才能完成的SQL操作,同时也可以避免事务或者表锁死,并且写起来也很容易。但是,有些情况下,子查询是可以被更高效的连接(JOIN)替代。
示例 ,查找有角色的所有的用户信息 :
explain select * from t_user where id in (select user_id from user_role );
优化后 :
explain select * from t_user u , user_role ur where u.id = ur.user_id;
连接(Join)查询之所以更有效率一些 ,是因为MySQL不需要在内存中创建临时表来完成这个逻辑上需要两个步骤的查询工作。
优化OR条件
对于包含OR的查询子句,如果要利用索引,则OR之间的每个条件列都必须用到索引 ,而且不能使用到复合索引; 如果没有索引,则应该考虑增加索引。
示例 :
explain select * from emp where id = 1 or age = 30;
建议使用 union 替换 or ;
优化分页查询
一般分页查询时,通过创建覆盖索引能够比较好地提高性能。一个常见又非常头疼的问题就是 limit 200000,10 ,此时需要MySQL排序前200010 记录,仅仅返回200000 - 200010 的记录,其他记录丢弃,查询排序的代价非常大 。
优化思路一:在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容。
EXPLAIN SELECT * from tb_item t,(SELECT id from tb_item ORDER BY id LIMIT 200000,10) a WHERE a.id = t.id;
优化思路二:该方案适用于主键自增的表,可以把Limit 查询转换成某个位置的查询 。
EXPLAIN SELECT * from tb_item t WHERE id > 200000 LIMIT 10;
使用SQL提示
SQL提示,是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。
- USE INDEX:在查询语句中表名的后面,添加 use index 来提供希望MySQL去参考的索引列表,就可以让MySQL不再考虑其他可用的索引;
- IGNORE INDEX:只是单纯的想让MySQL忽略一个或者多个索引,则可以使用 ignore index;
- FORCE INDEX:为强制MySQL使用一个特定的索引,可在查询中使用 force index;
示例:
EXPLAIN SELECT * from tb_seller use index(index_name) WHERE name = '小米科技';
explain select * from tb_seller ignore index(index_name) where name = '小米科技';
explain select * from tb_seller FORCE index(index_name) where name = '小米科技';