目录
- 一、SQL优化
- 1.1、插入数据
- 1.2、主键优化
- 1.3、order by优化
- 1.4、group by优化
- 1.5、limit优化
- 1.6、count优化
- 1.7、update优化
- 二、视图/存储过程/存储函数/触发器
- 2.1、视图
- 2.2、存储过程
- 2.3、存储函数
- 2.4、触发器
一、SQL优化
分为:插入数据优化,主键优化,order by优化,group by优化,limit优化,count优化,update优化
1.1、插入数据
-
批量插入数据,手动开启事务:若插入数据较多,不建议使用insert单条插入,MySQL自动开启事务,每一次insert都会开启并且提交事务,影响效率。建议批量插入数据,若数据较多,则手动开启事务,多次批量提交数据后 提交事务。
-
Load数据文件
:使用MySQL数据库提供的load指令插入大批量数据
# 1. 本地数据库 创建数据库,创建表结构 # 2. 将数据脚本文件上传至 本地/服务器 某路径下 # 3. 客户端连接服务端时,加上参数 mysql –-local-infile -u root -p # 4. 设置全局参数local_infile为1,开启从本地加载文件导入数据的开关 set global local_infile = 1; # 执行load指令将准备好的数据,加载到表结构中 # '/root/sql1.log'数据脚本文件路径, ',' 一行数据的各个列分隔符, '\n' 行和行的分隔符 load data local infile '/root/sql1.log' into table tb_user fields terminated by ',' lines terminated by '\n' ;
1.2、主键优化
-
数据组织方式
- InnoDB存储引擎中,表数据根据主键顺序组织存放的,以该存储方式存储的表为索引组织表
- 行数据存储在聚集索引的叶子节点上
- 表空间 -> 段 -> 区 -> 页
-
页分裂:主键乱序插入下,如果页满 会导致页分裂
每个页包含了2-N行数据(如果一行数据过大,会行溢出;只有1个数据 形成链表),根据主键排列
-
主键顺序插入:
-
主键乱序插入:
插入50,但应该放在1页后2页前,但1页和2页都满,因此产生页分裂
开启页3,移动元素23,47,并插入新数据50至新页面
再断开原1页 2页的双向链表,并插入分裂出来的新页面,维护左右的双向链表关系
-
-
页合并:删除操作并未 对记录进行物理删除,而是记录被逻辑标记为删除。且其空间变为允许被其他记录声明使用。当页中删除的记录达到 MERGE_THRESHOLD(默认为页的50%),InnoDB会开始寻找最靠近的页(前 或后)看看是否可以将两个页合并以优化空间使用。
-
主键设计原则:
- 满足业务需求的情况下,尽量降低主键的长度。
- 插入数据时,尽量选择顺序插入,选择使用AUTO_INCREMENT自增主键。
- 尽量不要使用UUID做主键或者是其他自然主键,如身份证号。
- 业务操作时,避免对主键的修改。
1.3、order by优化
MySQL的排序有两种方式(Explian的Extra内容:
- Using filesort : 通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sort buffer中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序
- Using index : 通过有序索引顺序扫描直接返回有序数据,不需要额外排序,操作效率高
Using index的性能高,Using filesort的性能低,在优化排序操作时,尽量要优化为 Using index
order by 语句需要与索引的顺序完全匹配,创建联合索引时默认升序排列
create index age_phodes_idx on tb_user(age asc, phone desc);
order by优化原则:
- 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则
- 尽量使用覆盖索引
- 多字段排序, 一个升序一个降序,需要注意联合索引在创建时的规则(ASC/DESC)
- 如果不可避免的出现filesort,大数据量排序 可以适当增大排序缓冲区大小 sort_buffer_size(默认256k)。
1.4、group by优化
在分组操作时,可以通过索引来提高效率。 分组操作时,索引的使用也满足最左前缀法则
满足联合索引的最左前缀法则 Explain语句中显示Using idex,不满足则显示Using temporary
1.5、limit优化
在数据量比较大时,如果进行limit分页查询,在查询时,越往后,分页查询效率越低。当在进行分页查询时,如果执行 limit 2000000,10 ,此时需要MySQL排序前2000010 记录,并仅返回 2000000 - 2000010 的记录,其他记录丢弃,查询排序代价非常大
优化:分页查询时,通过创建 覆盖索引能够提高性能,可以通过覆盖索引加子查询形式进行优化
。
# id 为主键索引 执行耗时11.46 sec, 低于直接limit查询的 19.39 sec
select s.* from tb_sku s, (select id from tb_sku order by id limit 9000000,10) as a wehre s.id = a.id;
1.6、count优化
select count(*) from tb_user ;
若数据量很大,在执行count操作时,是非常耗时的
- MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行
count(*)
时候会直接返回这个 数,效率很高; 但是如果是带条件的count,MyISAM也耗时 - InnoDB 引擎就麻烦了,执行 count(*) 的时候,需要把数据一行一行从引擎里面读取,然后累积计数
优化:自己计数(借助于redis这样的数 据库进行,但对于带条件的count也耗时)
count的用法:
-
count(*):InnoDB引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加。
-
count(主键):InnoDB 引擎遍历表,把每一行主键id值取出来返回给服务层。 服务层拿到主键后,直接按行进行累加(主键不可能为null)
-
count(字段):
- 没有not null 约束 : InnoDB 引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为null,不为null,计数累加。
- 有not null 约束:InnoDB 引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,直接按行进行累加。
-
count(数字):InnoDB 引擎遍历整张表,但不取值。服务层对于返回的每一行,放一个数字“1” 进去,直接按行进行累加。
按照效率排序:
count(字段) < count(主键 id) < count(1) ≈ count(*)
count(*) 和 count(1) 不取值,count(id)取值但不用判断null,count(字段)取值还需要判断null
所以尽量使用count(*)
。
1.7、update优化
update 表名 set 字段1 where 字段2 = 'xxx'
当 WHERE
子句中的字段没有索引时,数据库通常会使用表锁。表锁会锁定整个表,以确保在更新过程中没有其他事务可以修改表中的数据。可能会导致并发性能下降,因为其他事务无法同时访问该表的其他行。
当 WHERE
子句中的字段有索引时,数据库通常会使用行锁。行锁仅锁定满足条件的行,而不是整个表。这样可以提高并发性能,因为其他事务可以同时访问不受锁限制的其他行。
要根据索引字段进行更新,否则行级锁变成表级锁
InnoDB的行锁是针对索引,不是针对记录加的锁,并且该索引不能失效,否则会从行锁 升级为表锁
二、视图/存储过程/存储函数/触发器
2.1、视图
视图(View)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。
通俗的讲,视图只保存了查询的SQL逻辑,不保存查询结果。所以我们在创建视图的时候,主要的工作 就落在创建这条SQL查询语句上。
-
视图的创建、查看、修改、删除
# 创建 CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句 [ WITH [ CASCADED | LOCAL ] CHECK OPTION ] # 查询 SHOW CREATE VIEW 视图名称; # 查看创建视图语句 SELECT * FROM 视图名称 ...... ; # 查看视图数据 # 修改 方式一 CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句 [ WITH [ CASCADED | LOCAL ] CHECK OPTION ] # 修改 方式二 ALTER VIEW 视图名称[(列名列表)] AS SELECT语句 [ WITH [ CASCADED | LOCAL ] CHECK OPTION ] # 删除 DROP VIEW [IF EXISTS] 视图名称 [,视图名称] ...
-
操作视图,以插入基表中数据
可以像操作表一样去操作视图,但视图并不存储数据,数据都存放在基表中# 创建视图 create or replace view stu_v_1 as select id,name,no from student where id <= 10; # 通过操作视图 以在基表中插入数据 insert into stu_v_1 values('6,Tom'); # 基表插入数据成功 # 插入数据成功,但视图中看不到,因为创建视图时指定了 id<= 10 insert into stu_v_1 values(15,'Amy'); #需要在创建视图的时候加上后面的选项,再次插入id=15数据,报错 阻止了不满足视图的数据插入 create or replace view stu_v_1 as select id,name from student where id <= 10; with cascaded check option;
-
视图的检查选项
使用子句with check option
创建视图时,MYSQL会通过视图检查正在更改的每行(插入,更新,删除等操作)是否符合视图的定义。MySQL允许基于一个视图创建另一个视图,并检查依赖视图中的规则以保持一致性。为了确定检查的范围,提供了两个选项WITH CASCADED CHECK OPTION
是指在更新视图时,会检查视图中所有相关的视图和基表的约束。如果更新操作违反了任何一个相关表的约束条件,更新将被拒绝。WITH LOCAL CHECK OPTION
是指在更新视图时,仅检查当前视图的约束条件。如果更新操作违反了当前视图的约束条件,更新将被拒绝。不会检查其他相关表的约束条件。
-
视图的更新
要使视图可更新,视图中的行与基础表中的行之间必须存在一对一的关系。
如果视图包含以下任何一 项,则该视图不可更新:- 聚合函数或窗口函数(SUM()、 MIN()、 MAX()、 COUNT()等)
- DISTINCT C. GROUP BY D. HAVING E. UNION 或者 UNION ALL
举例:create view v1 as select count(*) from student; insert int v1 values(10);
报错,提示当前视图不能插入数据,因为count(*) 的结果 没有 和student一样是一一对应的关系
-
视图的作用:
- 简单:把复杂查询条件定义为视图,简化用户操作
- 安全:数据库可以通过视图 保证了某些数据的安全性,通过视图 用户只能查询和修改它们所能见到的数据
- 数据独立:帮助用户屏蔽真实结构变化带来的影响
2.2、存储过程
-
概念:是一段预先编译并保存在数据库中的可重复使用的代码块。存储过程由
一系列的 SQL 语句和控制流语句组成
,可以接受参数,并且可以返回结果。存储过程通常用于执行复杂的数据库操作,实现业务逻辑和数据处理等任务。 -
特点:
- 封装,复用。可以把某一业务SQL封装在存储过程中,使用时直接调用
- 可以接收参数,也可以返回数据。存储过程中,可以传递参数,也可以接收返回值
- 减少网络交互,效率提升。如果分步执行多条SQL,则每执行一次都是一次网络传输。 而如果封装在存储过程中,则只需要一次网络交互即可。
存储过程的 创建,调用,查看,删除
-
存储过程的 创建,调用,查看,删除
# ****************************创建**************************** CREATE PROCEDURE 存储过程名称 ([ 参数列表 ]) BEGIN -- SQL语句 END ; # ****************************调用**************************** CALL 名称 ([ 参数 ]); # ****************************查看**************************** -- 查询指定数据库的存储过程及状态信息 SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'xxx'; -- 查询某个存储过程的定义 SHOW CREATE PROCEDURE 存储过程名称; # ****************************删除**************************** DROP PROCEDURE [ IF EXISTS ] 存储过程名称; # ****************************举例**************************** delimiter $$ #设置MySQL语句结束符为$$ create procedure p1() beginselect count(*) from student; end$$ delimiter ; #再设置为默认初始的 ; call p1; # 调用p1 drop procedure if exists p1; # 删除p1
-
变量:在MySQL中变量分为三种类型: 系统变量、用户定义变量、局部变量。
-
系统变量:是MySQL服务器提供,非用户定义,属于服务器层面。分为全局变量、会话变量
全局变量(GLOBAL)
:全局变量针对于所有的会话会话变量(SESSION)
:会话变量针对于单个会话,在另外一个会话窗口就不生效了
注意:
如果没有指定SESSION/GLOBAL,默认是SESSION,会话变量
mysql服务重新启动之后,所设置的全局参数会失效,要想不失效,可以在 /etc/my.cnf 中配置。
-
-
用户定义变量:用户根据需要自己定义的变量,不用提前声明,使用时直接
“@变量名”
就可以,其作用域为当前连接
-
局部变量:是根据需要定义的在局部生效的变量,访问之前,需要
DECLARE声明
。可用作存储过程内的局部变量和输入参数,局部变量的范围是在其内声明的BEGIN … END块
# 查看系统变量 如果没有指定SESSION/GLOBAL,默认是SESSION,会话变量 SHOW [ SESSION | GLOBAL ] VARIABLES ; -- 查看所有系统变量 SHOW [ SESSION | GLOBAL ] VARIABLES LIKE 'xxx'; -- 可以通过LIKE模糊匹配方式查找变量 SELECT @@[SESSION | GLOBAL] 系统变量名; -- 查看指定变量的值 # 设置系统变量 SET [ SESSION | GLOBAL ] 系统变量名 = 值 ; SET @@[SESSION | GLOBAL]系统变量名 = 值 ;# 赋值 用户定义变量 赋值时,可以使用 = ,也可以使用 := SET @var_name = expr [, @var_name = expr] ... ; SET @var_name := expr [, @var_name := expr] ... ; # 使用 用户定义变量 用户定义的变量无需对其进行声明或初始化,只不过获取到的值为NULL SELECT @var_name ; SELECT 字段名 INTO 变量名 FROM 表名 ... ;# 声明 局部变量 DECLARE 变量名 变量类型 [DEFAULT ... ] ; # 赋值 局部变量 SET 变量名 = 值 ; SET 变量名 := 值 ; SELECT 字段名 INTO 变量名 FROM 表名 ... ;
-
if:if条件判断的结构中,ELSE IF 结构可以有多个,也可以没有。 ELSE结构可以有,也可以没有
IF 条件1 THEN..... # 条件1 成立执行该THEN后的语句 ELSEIF 条件2 THEN -- 可选..... # 条件2 成立执行该THEN后的语句 ELSE -- 可选..... # 以上条件都不成立 成立执行该THEN后的语句 END IF; # ---------------------举例:根据定义参数score,判定当前分数对应等级-------------------- drop procedure if exists p3; create procedure p3() begindeclare score int default 58; #声明变量score为58,判断其分数等级declare grade varchar(10); #用于接收等级if score >= 85 thenset grade := '优秀';elseif score >= 60 thenset grade := '及格';elseset grade := '不及格';end if;select grade; end; call p3; # 不及格
-
参数。参数的类型 主要分为以下三种:IN、OUT、INOUT。
- IN(默认):该类参数作为输入,也就是需要调用时传入值
- OUT:该类参数作为输出,也就是该参数可以作为返回值
- INOUT:既可以作为输入参数,也可以作为输出参数
CREATE PROCEDURE 存储过程名称 ([ IN/OUT/INOUT 参数名 参数类型 ]) BEGIN-- SQL语句 END ; #---------------------举例:根据传入的200分制的分数,返回换算成百分制分数--------------------- create procedure p5(inout score double) beginset score := score * 0.5; end; set @myscore := 180; call p5(@myscore); select @myscore; # 90 # ---------------------举例:根据传入参数score,判定并返回当前分数对应等级-------------------- drop procedure if exists p4; create procedure p4(in score int, out grade varchar(10)) beginif score >= 85 thenset grade := '优秀';elseif score >= 60 thenset grade := '及格';elseset grade := '不及格';end if; end; call p4(66,@mygrade); # 调用时,直接传入两个参数,使用用户自定义变量接收grade的值 select @mygrade;
-
case。case结构及作用,与基础篇中的流程控制函数类似,两种语法格式:
#语法1,含义: 当case_value的值为 when_value1时,执行statement_list1,当值为 when_value2时,执行statement_list2, 否则就执行 statement_list CASE case_valueWHEN when_value1 THEN statement_list1[ WHEN when_value2 THEN statement_list2] ...[ ELSE statement_list ] END CASE;#语法2,含义: 当条件search_condition1成立时,执行statement_list1,当条件search_condition2成立时,执行statement_list2, 否则就执行 statement_list CASEWHEN search_condition1 THEN statement_list1[WHEN search_condition2 THEN statement_list2] ...[ELSE statement_list] END CASE;#---------------------举例:根据传入的月份,判定所属季度--------------------- drop procedure if exists p6; create procedure p6(in month int) begindeclare ans varchar(10);casewhen month in (1,2,3)then set ans := '第一季度';when month in (4,5,6)then set ans := '第二季度';when month in (7,8,9)then set ans := '第三季度';when month in (10,11,12)then set ans := '第四季度';end case;select concat('您输入的月份为:',month, ',所在季度是:',ans); end;call p6(10); # 第四季度
-
while。有条件的循环控制语句。
满足条件后,再执行循环体中的SQL语句
-- 先判定条件,如果条件为true,则执行逻辑,否则,不执行逻辑 WHILE 条件 DOSQL逻辑... END WHILE;#---------------------举例:计算从1累加到n的和,n为传入的参数值--------------------- drop procedure if exists p7; create procedure p7(in n int) begindeclare sum int default 0;while n > 0 doset sum := sum + n;set n := n - 1;end while;select concat('从1到n的和为:',sum); end; call p7(10); #55
-
repeat。是有条件的循环控制语句,
当满足until声明的条件的时候,则退出循环
while是不满足条件则退出循环
,repeat是满足条件则退出循环
repeat先执行一次,再判断是否满足循环条件
-- 先执行一次逻辑,然后判定UNTIL条件是否满足,如果满足,则退出。如果不满足,则继续下一次循环 REPEATSQL逻辑...UNTIL 条件 END REPEAT;#---------------------举例:计算从1累加到n的和,n为传入的参数值--------------------- drop procedure if exists p8; create procedure p8(in n int) begindeclare sum int default 0;repeatset sum := sum + n;set n := n - 1;until n = 0end repeat;select concat('从1到n的和为:',sum); end; call p8(100); #5050
-
loop。实现简单的循环,如果不在SQL逻辑中增加退出循环的条件,可以用其来实现简单的死循环
LOOP可以配合以下两个语句使用:- LEAVE :
配合循环使用,退出循环
- ITERATE:
必须用在循环中,作用是跳过当前循环剩下的语句,直接进入下一次循环
[begin_label:] LOOPSQL逻辑... END LOOP [end_label];LEAVE label; -- 退出指定标记的循环体 ITERATE label; -- 直接进入下一次循环 # begin_label,end_label,label 都是自定义的标记#---------------------举例:计算从1累加到n的偶数和,n为传入的参数值--------------------- drop procedure if exists p10; create procedure p10(in n int) begindeclare sum int default 0;getsum:loop #循环代码if n = 0 thenleave getsum; # 退出循环 leaveend if;if n%2 = 1 thenset n := n - 1;ITERATE getsum;end if;set sum := sum + n;set n := n - 1;end loop getsum;select concat('从1到n的和为:',sum); end; call p10(10); #30
- LEAVE :
-
游标(CURSOR)。是用来
存储查询结果集的数据类型
, 在存储过程和函数中可以使用游标对结果集进 行循环的处理。游标的使用包括游标的声明、OPEN、FETCH 和 CLOSE# 声明游标 DECLARE 游标名称 CURSOR FOR 查询语句 ; # 打开游标 OPEN 游标名称 ; # 获取游标记录 FETCH 游标名称 INTO 变量 [, 变量 ] ; # 关闭游标 CLOSE 游标名称 ;
-
条件处理程序(Handler)。可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤
DECLARE handler_action HANDLER FOR condition_value [, condition_value] ... statement ;handler_action:CONTINUE: 继续执行当前程序EXIT: 终止执行当前程序condition_value:SQLSTATE sqlstate_value: 状态码,如 02000SQLWARNING: 所有以01开头的SQLSTATE代码的简写NOT FOUND: 所有以02开头的SQLSTATE代码的简写SQLEXCEPTION: 所有没有被SQLWARNING 或 NOT FOUND捕获的SQLSTATE代码的简写drop procedure if exists p12;#---------------------举例:根据传入的参数uage,查询用户表tb_user中所有用户年龄小于等于uage的name和profession,并将这两个字段插入到新表 (id,name,profession)中--------------------- create procedure p12(in user_age int) begin# 有先后顺序:先声明普通变量,再声明游标declare namenew varchar(100);declare profnew varchar(100);# 1.声明游标 存储查询结果集declare getInfo cursor forselect name,profession from tb_user where age <= user_age;# 定义 条件处理程序,当状态码为02000时触发条件处理程序 -> 退出操作,关闭游标declare exit handler for sqlstate '02000' close getInfo;# declare exit handler for not found close getInfo;# 2.创建新表的 表结构drop table if exists tb_user_new;create table if not exists tb_user_new(id int primary key auto_increment,name varchar(100),profession varchar(100));# 3.开启游标open getInfo;# 4.获取游标查询的结果集,循环遍历# 4.1 遍历的时候把查询结果集的name和profession赋值给两个局部变量,声明两个变量while true do# 获取游标记录fetch getInfo into namenew,profnew;# 4.3 插入新表数据insert into tb_user_new values(null,namenew,profnew);end while;# 关闭游标close getInfo; end;call p12(30);
2.3、存储函数
概念:存储函数是有返回值的存储过程,存储函数的参数只能是IN类型的
CREATE FUNCTION 存储函数名称 ([ 参数列表 ])
RETURNS type [characteristic ...] # 指定返回值类型
BEGIN-- SQL语句RETURN ...; # 返回结果
END ;
#---------------------举例:计算从1累加到n的值,n为传入的参数值---------------------
create function fun1(n int)
RETURNS int deterministic
begindeclare sum int default 0;while n > 0 doset sum := sum + n;set n := n - 1;end while;return sum;
end;
select fun1(100);
characteristic说明:
- DETERMINISTIC:相同的输入参数总是产生相同的结果
- NO SQL :不包含 SQL 语句
- READS SQL DATA:包含读取数据的语句,但不包含写入数据的语句
2.4、触发器
在 MySQL 中,触发器(Triggers)是一种数据库对象,它与表相关联,并在表上的特定事件发生时自动执行一系列动作。是在insert/update/delete之前(BEFORE)或之后(AFTER)
,触发并执行触发器中定义的SQL语句集合。
使用别名OLD和NEW来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。现在触发器还 只支持行级触发,不支持语句级触发。
old用来引用原来的记录内容,new用来引用新的记录内容
-
INSERT 型触发器:NEW 表示将要或者已经新增的数据
-
UPDATE 型触发器:OLD 表示修改之前的数据 , NEW 表示将要或已经修改后的数据
-
DELETE 型触发器: OLD 表示将要或者已经删除的数据
# 创建
CREATE TRIGGER trigger_name
BEFORE/AFTER INSERT/UPDATE/DELETE
ON tbl_name FOR EACH ROW -- 行级触发器
BEGINtrigger_stmt ;
END;
# 查看
SHOW TRIGGERS ;
# 删除
DROP TRIGGER [schema_name.]trigger_name ;
-- 如果没有指定 schema_name,默认为当前数据库