去重使用关键字:distinct
DDL语句(DDL,数据定义语言)
- ddl-数据库操作
- show databases;
- create database 数据库名;
- use 数据库名;
- select database();#返回当前连接的数据库名称
- drop database数据库名;
- ddl-表操作
- show tables;
- create table 表名(字段类型,字段类型,字段类型);
- desc 表名;#查看当前这张表有哪些字段
- show create table 表名;#查询这张表的建表语句
- alter table 表名 add/modify/change/drop/rename to …;#表结构修改,add添加字段,modify修改字段类型,change修改字段名称以及类型,drop删除字段,rename to修改表名
- drop table表名;
DML语句(DML,数据操作语言)
- 添加数据
insert into 表名(字段1,字段2,…)values(值1,值2,…)[(值1,值2,…)…];#如果没有指定字段,就是表的全部字段 - 修改数据
update 表名 set 字段1=值1,字段2=值2[where条件】; - 删除数据
delete from 表名【where条件】;
DQL语句(DQL,数据查询语言)
where子句对检索结果中每一条记录第一次过滤后,group by对每条记录进行分组,having对_各个组中_的记录进行再次过滤。因此是先通过where子句过滤、再通过group by子句分组、最后通过having子句对组中记录再次进行过滤。
DCL语句(DCL,数据控制语言)
用户管理:管理哪些用户可以访问当前mysql数据库()
权限控制:我们当前用户连接上mysql后可以访问mysql数据库中的哪些数据库,哪些表,以及对这些表执行什么样的操作
一个是谁可以访问数据库,另一个是用户可以对数据库做什么
函数
- 字符串函数
- 数值函数
生成一个随机的6位数,就用round(RAND()*1000000,0);
但是有一个问题,如果生成的例如0.01111,乘以1000000是五位数,所以需要在左边接着补数字,用到字符串函数的LPAD() - 日期函数
例如:
date_add(now(),interval 70 YEAR);
select datediff(‘2021-01-01’,‘2022-01-01’);
– 案例: 查询所有员工的入职天数,并根据入职天数倒序排序。
select name, datediff(curdate(), entrydate) as ‘entrydays’ from emp order by entrydays desc; #as 是起别名 desc表示降序 - 流程函数
select
id,
name,
(case when math >= 85 then '优秀' when math >=60 then '及格' else '不及格' end ) '数学',
(case when english >= 85 then '优秀' when english >=60 then '及格' else '不及格' end ) '英语',
(case when chinese >= 85 then '优秀' when chinese >=60 then '及格' else '不及格' end ) '语文'
from score;
select
name,
( case workaddress when '北京' then '一线城市' when '上海' then '一线城市' else '二线城市' end ) as '工作地址'
from emp;
上面这个是第四个语句的示例,他其实可以用第三个语句写的
约束
约束是作用于表中字段的,可以在创建表/修改表的时间添加约束
create table user(
id int primary key auto_increment comment '主键',
name varchar(10) not null unique comment '姓名',
age int check ( age > 0 && age <= 120 ) comment '年龄',
status char(1) default '1' comment '状态',
gender char(1) comment '性别'
) comment '用户表';
外键所关联的表叫父表(主表),有外键的表就是子表(从表)
-- 添加外键
alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id);
fk_emp_dept_id是一个外键约束的名称。
这个名称是你自己定义的,用于标识这个特定的外键约束。
给外键约束命名(在这个例子中是fk_emp_dept_id)是可选的,
但是这样做有助于管理和维护数据库。
如果你需要修改或删除这个外键约束,你可以通过这个名称来引用它。
-- 删除外键
alter table emp drop foreign key fk_emp_dept_id;
外键删除更新行为
-- 外键的删除和更新行为
alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id) on update cascade on delete cascade ;
alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id) on update set null on delete set null ;
多表查询
- 多对多
- 案例:学生与课程的关系
- 关系:一个学生可以选修多门课程,一门课程也可以供多个学生选择
- 实现:建立第三张中间表,中间表至少包含两个外键,分别关联两方主键
- 一对一
- 案例:用户与用户详情的关系
- 关系:一对一关系,多用于单表拆分,将一张表的基础字段放在一张表中,其他详情宇段放在另一张表中,以提升操作效率。
- 实现:在任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的(unique)
- 一对多(多对一)
- 案例:部门(一)与员工(多)的关系
- 关系:一个部门对应多个员工,一个员工对应一个部门
- 实现:在多的一方建立外键,指向一的一方的主键;如果是在一的一方建立外键,那他是不是会有多个员工,所以实现不了
select * form emp,dept;#会出现笛卡尔积现象
-- 多表查询 -- 笛卡尔积
select * from emp , dept where emp.dept_id = dept.id;
内连接
-- 内连接演示
-- 1. 查询每一个员工的姓名 , 及关联的部门的名称 (隐式内连接实现)
-- 表结构: emp , dept
-- 连接条件: emp.dept_id = dept.id
select emp.name , dept.name from emp , dept where emp.dept_id = dept.id ;
select e.name,d.name from emp e , dept d where e.dept_id = d.id;
-- 2. 查询每一个员工的姓名 , 及关联的部门的名称 (显式内连接实现) --- INNER JOIN ... ON ...
-- 表结构: emp , dept
-- 连接条件: emp.dept_id = dept.id
select e.name, d.name from emp e inner join dept d on e.dept_id = d.id;
select e.name, d.name from emp e join dept d on e.dept_id = d.id;
外连接
-- 外连接演示
-- 1. 查询emp表的所有数据, 和对应的部门信息(左外连接)
-- 表结构: emp, dept
-- 连接条件: emp.dept_id = dept.id
-- left代表要[outer]join左边的全部数据,right代表要[outer]join右边的全部数据
select e.*, d.name from emp e left outer join dept d on e.dept_id = d.id;
select e.*, d.name from emp e left join dept d on e.dept_id = d.id;
-- 2. 查询dept表的所有数据, 和对应的员工信息(右外连接)
select d.*, e.* from emp e right outer join dept d on e.dept_id = d.id;
select d.*, e.* from dept d left outer join emp e on e.dept_id = d.id;
自连接
-- 自连接
-- 1. 查询员工 及其 所属领导的名字
-- 表结构: emp
select a.name , b.name from emp a , emp b where a.managerid = b.id;
-- 2. 查询所有员工 emp 及其领导的名字 emp , 如果员工没有领导, 也需要查询出来
-- 表结构: emp a , emp b
select a.name '员工', b.name '领导' from emp a left join emp b on a.managerid = b.id;
联合查询
有all的时候,不会去重;没有all,就会去重
使用联合查询是有条件的:两个查询的返回的字段数和字段类型都一样一致
子查询
标量子查询
-- 标量子查询
-- 1. 查询 "销售部" 的所有员工信息
-- a. 查询 "销售部" 部门ID
select id from dept where name = '销售部';
-- b. 根据销售部部门ID, 查询员工信息
select * from emp where dept_id = (select id from dept where name = '销售部');
-- 2. 查询在 "方东白" 入职之后的员工信息
-- a. 查询 方东白 的入职日期
select entrydate from emp where name = '方东白';
-- b. 查询指定入职日期之后入职的员工信息
select * from emp where entrydate > (select entrydate from emp where name = '方东白');
列子查询、
-- 列子查询
-- 1. 查询 "销售部" 和 "市场部" 的所有员工信息
-- a. 查询 "销售部" 和 "市场部" 的部门ID
select id from dept where name = '销售部' or name = '市场部';
-- b. 根据部门ID, 查询员工信息
select * from emp where dept_id in (select id from dept where name = '销售部' or name = '市场部');
-- 2. 查询比 财务部 所有人工资都高的员工信息
-- a. 查询所有 财务部 人员工资
select id from dept where name = '财务部';
select salary from emp where dept_id = (select id from dept where name = '财务部');
-- b. 比 财务部 所有人工资都高的员工信息
select * from emp where salary > all ( select salary from emp where dept_id = (select id from dept where name = '财务部') );
-- 3. 查询比研发部其中任意一人工资高的员工信息
-- a. 查询研发部所有人工资
select salary from emp where dept_id = (select id from dept where name = '研发部');
-- b. 比研发部其中任意一人工资高的员工信息
select * from emp where salary > some ( select salary from emp where dept_id = (select id from dept where name = '研发部') );
行子查询
-- 行子查询
-- 题目重申:查询与张无忌拥有相同直属领导,且与张无忌薪酬相同的员工信息。
-- 1. 查询与 "张无忌" 的薪资及直属领导相同的员工信息 ;
-- a. 查询 "张无忌" 的薪资及直属领导
select salary, managerid from emp where name = '张无忌';
-- b. 查询与 "张无忌" 的薪资及直属领导相同的员工信息 ;
select * from emp where (salary,managerid) = (select salary, managerid from emp where name = '张无忌');
表子查询
-- 表子查询
-- 1. 查询与 "鹿杖客" , "宋远桥" 的职位和薪资相同的员工信息
-- a. 查询 "鹿杖客" , "宋远桥" 的职位和薪资
select job, salary from emp where name = '鹿杖客' or name = '宋远桥';
-- b. 查询与 "鹿杖客" , "宋远桥" 的职位和薪资相同的员工信息
select * from emp where (job,salary) in ( select job, salary from emp where name = '鹿杖客' or name = '宋远桥' );
-- 2. 查询入职日期是 "2006-01-01" 之后的员工信息 , 及其部门信息
-- a. 入职日期是 "2006-01-01" 之后的员工信息
select * from emp where entrydate > '2006-01-01';
-- b. 查询这部分员工, 对应的部门信息;
select e.*, d.* from (select * from emp where entrydate > '2006-01-01') e left join dept d on e.dept_id = d.id ;
-- ---------------------------------------> 多表查询案例 <----------------------------------
create table salgrade(
grade int,
losal int,
hisal int
) comment '薪资等级表';
insert into salgrade values (1,0,3000);
insert into salgrade values (2,3001,5000);
insert into salgrade values (3,5001,8000);
insert into salgrade values (4,8001,10000);
insert into salgrade values (5,10001,15000);
insert into salgrade values (6,15001,20000);
insert into salgrade values (7,20001,25000);
insert into salgrade values (8,25001,30000);
-- 1. 查询员工的姓名、年龄、职位、部门信息 (隐式内连接)
-- 表: emp , dept
-- 连接条件: emp.dept_id = dept.id
select e.name , e.age , e.job , d.name from emp e , dept d where e.dept_id = d.id;
-- 2. 查询年龄小于30岁的员工的姓名、年龄、职位、部门信息(显式内连接)
-- 表: emp , dept
-- 连接条件: emp.dept_id = dept.id
select e.name , e.age , e.job , d.name from emp e inner join dept d on e.dept_id = d.id where e.age < 30;
-- 3. 查询拥有员工的部门ID、部门名称
-- 表: emp , dept
-- 连接条件: emp.dept_id = dept.id
select distinct d.id , d.name from emp e , dept d where e.dept_id = d.id;
-- 4. 查询所有年龄大于40岁的员工, 及其归属的部门名称; 如果员工没有分配部门, 也需要展示出来
-- 表: emp , dept
-- 连接条件: emp.dept_id = dept.id
-- 外连接
select e.*, d.name from emp e left join dept d on e.dept_id = d.id where e.age > 40 ;
-- 5. 查询所有员工的工资等级
-- 表: emp , salgrade
-- 连接条件 : emp.salary >= salgrade.losal and emp.salary <= salgrade.hisal
select e.* , s.grade , s.losal, s.hisal from emp e , salgrade s where e.salary >= s.losal and e.salary <= s.hisal;
select e.* , s.grade , s.losal, s.hisal from emp e , salgrade s where e.salary between s.losal and s.hisal;
-- 6. 查询 "研发部" 所有员工的信息及 工资等级
-- 表: emp , salgrade , dept
-- 连接条件 : emp.salary between salgrade.losal and salgrade.hisal , emp.dept_id = dept.id
-- 查询条件 : dept.name = '研发部'
select e.* , s.grade from emp e , dept d , salgrade s where e.dept_id = d.id and ( e.salary between s.losal and s.hisal ) and d.name = '研发部';
-- 7. 查询 "研发部" 员工的平均工资
-- 表: emp , dept
-- 连接条件 : emp.dept_id = dept.id
select avg(e.salary) from emp e, dept d where e.dept_id = d.id and d.name = '研发部';
-- 8. 查询工资比 "灭绝" 高的员工信息。
-- a. 查询 "灭绝" 的薪资
select salary from emp where name = '灭绝';
-- b. 查询比她工资高的员工数据
select * from emp where salary > ( select salary from emp where name = '灭绝' );
-- 9. 查询比平均薪资高的员工信息
-- a. 查询员工的平均薪资
select avg(salary) from emp;
-- b. 查询比平均薪资高的员工信息
select * from emp where salary > ( select avg(salary) from emp );
-- 10. 查询低于本部门平均工资的员工信息
-- a. 查询指定部门平均薪资 1
select avg(e1.salary) from emp e1 where e1.dept_id = 1;
select avg(e1.salary) from emp e1 where e1.dept_id = 2;
-- b. 查询低于本部门平均工资的员工信息
select * from emp e2 where e2.salary < ( select avg(e1.salary) from emp e1 where e1.dept_id = e2.dept_id );
-- 11. 查询所有的部门信息, 并统计部门的员工人数
select d.id, d.name , ( select count(*) from emp e where e.dept_id = d.id ) '人数' from dept d;
select count(*) from emp where dept_id = 1;
-- 12. 查询所有学生的选课情况, 展示出学生名称, 学号, 课程名称
-- 表: student , course , student_course
-- 连接条件: student.id = student_course.studentid , course.id = student_course.courseid
select s.name , s.no , c.name from student s , student_course sc , course c where s.id = sc.studentid and sc.courseid = c.id ;
事务事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
set @@autocommit=0,将事务的提交方式设置为手动
start transaction和bigin也都是将事务的提交方式设置为手动的
原子性(atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
一致性(consistency):事务完成时,必须使所有的数据都保持一致状态。
隔离性(isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
持久性(durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
并发事务问题
多个并发事务在执行的过程中所出现的脏读,不可重复读,幻读的问题
mysql的隔离级别默认是可重复读,也就是说未提交的数据根本读不到,这里只是告诉你有这种情况产生,而数据库有隔离级别不会产生脏读
幻读是在可重复读的前提下产生的,就是select insert select,两次select的值是相同的,但是在第一次select之后,其实数据库已经发生改变
上面的并发事务问题,脏读是B事务未提交,A却可以读到了
不可重复读是,A先进行第一次读,B事务提交,A再读,A两次读的结果不一样
幻读是:A先第一次读,B事务(插入)提交,A进行插入(AB两次插入主键一样),A再读,出现A读都是没有该主键,但是不能插入的情况
那不出现幻读,也就是正常情况是:A读,B进行插入,插入无返回,一直在等;A成功插入,B发生报错;
事务的隔离级别
orcal的默认隔离级别是read uncommitted,也就是这三个问题都会出现
上面的隔离级别,数据安全性和性能成反比
session是会话级别,仅代表对当前客户端窗口有效
global指的是针对于所有客户端的会话窗口有效
**
MySQL体系结构
- 连接层
最上层是一些客户端和链接服务,主要完成一些类似于连接处理,授权认证,及相关的安全方案。服务器也会为安全接入的每个客户端验证它所具有的操作权限。 - 服务层
第二层梁构主要完成大多数的核心服务功能,如 sql接口,并完成缓存的查询,sql的分析和优化,部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程,函数等。 - 引擎层
存储引擎真正的负责了mysql中数据的存储和提取,服务器通过api 和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。 - 存储层
主要是将数据存储在文件系统之上,并完成与存储引擎的交互。
存储引擎 存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。存储引擎是基于表的,而不是 基于库的,所以存储引擎也可被称为表类型。我们可以在创建表的时候,来指定选择的存储引擎,如果 没有指定将自动选择默认的存储引擎。
CREATE TABLE 表名(
字段1 字段1类型 [ COMMENT 字段1注释 ] ,
......
字段n 字段n类型 [COMMENT 字段n注释 ]
) ENGINE = INNODB [ COMMENT 表注释 ] ;
show engines;
存储引擎的特点
- InnoDB
- 介绍 InnoDB是一种兼顾高可靠性和高性能的通用存储引擎,在 MySQL 5.5 之后,InnoDB是默认的 MySQL 存储引擎。
- 特点 DML操作遵循ACID模型,支持事务;
- 行级锁,提高并发访问性能;
- 支持外键FOREIGN KEY约束,保证数据的完整性和正确性;
- xxx.ibd:xxx代表的是表名,innoDB引擎的每张表都会对应这样一个表空间文件,存储该表的表结 构(frm-早期的 、sdi-新版的)、数据和索引。
- show variables like ‘innodb_file_per_table’; 查看对于InnoDB引擎的表,每一张表都对应一个ibd文件
- 表空间 Innodb存储引擎逻辑结构的最高层,ibd文件其实就是表空间文件,在表空间中可以包含多个segment段。
- 段:表空间是由各个段组成的,常见的段有数据段,索引段,回滚段等。 等。innodb中对于段的管理,都是引擎自身完成,不需要人为对其控制,一个段中包含多个区。
- 区:区是表空间的单元结构,每个区的大小为1m.默认情况下,innodb存储引擎页大小为16k, 即一个区中一共有64个连续的页。
- 页:页是组成区的最小单元,页也是innodb存储引擎磁盘管理的最小单元,每个页的大小默认为16kb.为了保证页的连续性,innodb存储引擎每次从磁盘申请 4-5个区。
- 行:Innodb存储引擎是面向行的,也就是说数据是按行进行存放的,在每一行中除了定义表时
所指定的字段以外,还包含两个隐藏字段(后面会详细介绍)
- MyISAM ( MySQL早期的默认存储引擎 )
- 特点
- 不支持事务,不支持外键
- 支持表锁,不支持行锁
- 访问速度快
- 文件
1. xxx.sdi:存储表结构信息
2. xxx.MYD: 存储数据
3. xxx.MYI: 存储索引 - Memory ( Memory引擎的表数据时存储在内存中的,由于受到硬件问题、或断电问题的影响,只能将这些表作为 临时表或缓存使用。 )
- 特点
- 内存存放
- hash索引(默认)
- 文件:
- xxx.sdi:存储表结构信息
- 引擎特性对比
- 在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据 实际情况选择多种存储引擎进行组合。
- InnoDB: 是Mysql的默认存储引擎,支持事务、外键。如果应用对事务的完整性有比较高的要 求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操 作,那么InnoDB存储引擎是比较合适的选择。
- MyISAM : 如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。
- MEMORY:将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。MEMORY的缺陷就是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性。
索引
索引概述 索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足 特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 这样就可以在这些数据结构 上实现高级查找算法,这种数据结构就是索引。
索引数据结构
MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的索引结构,主要包含以下几种,默认为B+ Tree
索引引擎对索引结构的支持情况
B Tree(多路平衡查找树)和B+树如果选择二叉树作为索引结构,会存在以下缺点:
- 顺序插入时,会形成一个链表,查询性能大大降低。
- 大数据量情况下,层级较深,检索速度慢。
红黑树是一颗自平衡二叉树,那这样即使是顺序插入数 据,最终形成的数据结构也是一颗平衡的二叉树。但是,即使如此,由于红黑树也是一颗二叉树,所以也会存在一个缺点
- 大数据量情况下,层级较深,检索速度慢。
x阶代表有x个指针,同时一个节点就有x个key
B树的演变过程:当需要插入一个新关键字时,先将其放入叶子节点中。如果叶子节点已满,则需要进行分裂操作。分裂操作中,将插入的元素按照顺序放入结点中,然后从中选择一个数作为当前结点的父结点(选择结点的规则是 (n+1)/2 取整),放入父结点中,若没有父结点则创建一个父结点。
B+Tree 与 B-Tree相比,主要有以下三点区别:
- 所有的数据都会出现在叶子节点。
- 叶子节点形成一个单向链表。
- 非叶子节点仅仅起到索引数据作用,具体的数据都是在叶子节点存放的。
mysql索引数据结构对经典的b+tree进行了优化。在原b+tree的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的b+tree,提高区间访问的性能。
哈希索引哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在 hash表中。
特点
- Hash索引只能用于对等比较(=,in),不支持范围查询(between,>,< ,…)
- 无法利用索引完成排序操作
- 查询效率高,通常(不存在hash冲突的情况)只需要一次检索就可以了,效率通常要高于B+tree索引
在MySQL中,支持hash索引的是Memory存储引擎。 而InnoDB中具有自适应hash功能,hash索引是 InnoDB存储引擎根据B+Tree索引在指定条件下自动构建的。
为什么InnoDB存储引擎选择使用B+tree索引结构?
- 相对于二叉树,层级更少,搜索效率高;
- 对于B-tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储 的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低;
- 相对Hash索引,B+tree支持范围匹配及排序操作;
索引分类
而在在InnoDB存储引擎中,根据索引的存储形式,又可以分为以下两种:
聚集索引选取规则:
- 如果存在主键,主键索引就是聚集索引
- 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。
- 如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引。
执行sql语句的示意图
具体过程如下:
- 由于是根据name字段进行查询,所以先根据name='Arm’到name字段的二级索引中进行匹配查找。但是在二级索引中只能查找到 Arm 对应的主键值 10。
- 由于查询返回的数据是*,所以此时,还需要根据主键值10,到聚集索引中查找10对应的记录,最 终找到10对应的行row。
- 最终拿到这一行的数据,直接返回即可。
回表查询: 这种先到二级索引中查找数据,找到主键值,然后再到聚集索引中根据主键值,获取 数据的方式,就称之为回表查询。
索引语法
- 创建索引
- CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name ( index_col_name,… ) ;
- #unique是唯一索引,fulltext是全文索引,如果都没有就是常规索引
- 一个索引是可以关联多个字段的,只关联一个字段叫单列索引,关联了多个字段就叫联合索引,组合索引
- 联合索引中,将区分度最高的字段(即每行数据唯一性最强的字段)置于索引的最左侧。
- 查看索引
- SHOW INDEX FROM table_name ;
- 删除索引
- DROP INDEX index_name ON table_name ;
sql性能分析MySQL 客户端连接成功后,通过 show [session|global] status 命令可以提供服务器状态信息。通过如下指令,可以查看当前数据库的INSERT、UPDATE、DELETE、SELECT的访问频次:
-- session 是查看当前会话 ;
-- global 是查询全局数据 ;
SHOW GLOBAL STATUS LIKE 'Com_______';
# 直接复制粘贴,___不能替换
通过上述指令,我们可以查看到当前数据库到底是以查询为主,还是以增删改为主,从而为数据库优化提供参考依据。 如果是以增删改为主,我们可以考虑不对其进行索引的优化。 如果是以查询为主,那么就要考虑对数据库的索引进行优化了。
因为:
这是因为索引虽然可以提高查询速度,但同时也会在数据插入、删除和更新操作时带来额外的开销。每当数据发生变化时,索引也需要进行相应的更新。因此,如果一个数据库主要进行增删改操作,频繁的索引更新可能会导致性能下降。
另外,索引还会占用额外的存储空间。因此,如果一个数据库主要进行增删改操作,而查询操作相对较少,那么可能没有必要进行索引优化。
慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有 SQL语句的日志。
#查看系统变量slow_query_log来看mysql慢查询日志是否开启
show variables like 'slow_query_log'
如果要开启慢查询日志,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:
# 开启MySQL慢日志查询开关
slow_query_log=1
# 设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2
systemctl restart mysqld #重新启动MySQL服务器
/var/lib/mysql/localhost-slow.log。 查看慢日志文件中记录的信息
show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。通过have_profiling 参数,能够看到当前MySQL是否支持profile操作:
SELECT @@have_profiling ;
#通过set语句在session/global级别开启profiling:
SET profiling = 1;
-- 查看每一条SQL的耗时基本情况
show profiles;
-- 查看指定query_id的SQL语句各个阶段的耗时情况
show profile for query query_id;
-- 查看指定query_id的SQL语句CPU的使用情况
show profile cpu for query query_id;
EXPLAIN 或者 DESC命令获取 MySQL 如何执行 SELECT 语句的信息,包括在 SELECT 语句执行 过程中表如何连接和连接的顺序。
-- 直接在select语句之前加上关键字 explain / desc
EXPLAIN SELECT 字段列表 FROM 表名 WHERE 条件 ;
Explain 执行计划中各个字段的含义:
select_type 不重要,只是告诉你查询类型
主要关注type,possible_key,key和key_len
使用规则(包含索引失效) 最左前缀法则
- 如果索引了多列(联合索引),要遵守最左前缀法则。最左前缀法则指的是查询从索引的最左列开始, 并且不跳过索引中的列。如果跳跃某一列,索引将会部分失效(后面的字段索引失效)。
- 最左前缀法则中指的最左边的列,是指在查询时,联合索引的最左边的字段(即是 第一个字段)必须存在,与我们编写SQL时,条件编写的先后顺序无关。 重点是有没有,和顺序没有关系
范围查询
- 联合索引中,出现范围查询(>,<),范围查询右侧的列索引失效(但是>=和<=是不会造成失效的)
- 在业务允许的情况下,尽可能的使用类似于 >= 或 <= 这类的范围查询,而避免使用 > 或 <
索引失效情况
- 索引列运算
- 例如: explain select * from tb_user where substring(phone,10,2) = ‘15’;
- 字符串不加引号 (status的类型是字符串)
- 例如: explain select * from tb_user where profession = ‘软件工程’ and age = 31 and status = 0;
- 模糊查询
- 如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。
- explain select * from tb_user where profession like ‘软件%’; 索引生效
explain select * from tb_user where profession like ‘%工程’; 索引失效
explain select * from tb_user where profession like ‘%工%’; 索引失效 - or连接条件
- 用or分割开的条件, 如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。
- 环境:id有索引,phone有索引,但是因为age没有,所以,索引失效
explain select * from tb_user where id = 10 or age = 23;
explain select * from tb_user where phone = ‘17799990017’ or age = 23; - 数据分布影响
- 如果MySQL评估使用索引比全表更慢,则不使用索引。
- MySQL在查询时,会评估使用索引的效率与走全表扫描的效率,如果走全表扫描更快,则放弃 索引,走全表扫描。 因为索引是用来索引少量数据的,如果通过索引查询返回大批量的数据,则还不 如走全表扫描来的快,此时索引就会失效。
- 个人理解:绝大多数都符合条件的时候,就不走索引;
使用规则(sql提示,覆盖索引,前缀索引)SQL提示
SQL提示,是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优 化操作的目的。
- use index : 建议MySQL使用哪一个索引完成此次查询(仅仅是建议,mysql内部还会再次进 行评估)。
explain select * from tb_user use index(idx_user_pro) where profession = ‘软件工程’; - ignore index : 忽略指定的索引。
explain select * from tb_user ignore index(idx_user_pro) where profession = ‘软件工程’; - force index : 强制使用索引。
explain select * from tb_user force index(idx_user_pro) where profession = ‘软件工程’;
覆盖索引
尽量使用覆盖索引,减少select *。 那么什么是覆盖索引呢? 覆盖索引是指查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到 (就是使用的索引包含的内容,联合索引就是联合了哪几个属性和id,如果找不到,就需要使用聚集索引,也就是回表查询)。
思考题: 一张表, 有四个字段(id, username, password, status), 由于数据量大, 需要对 以下SQL语句进行优化, 该如何进行才是最优方案:
select id,username,password from tb_user where username = ‘itcast’;
答案: 针对于 username, password建立联合索引, sql为: create index idx_user_name_pass on tb_user(username,password); 这样可以避免上述的SQL语句,在查询的过程中,出现回表查询
前缀索引
当字段类型为字符串(varchar,text,longtext等)时,有时候需要索引很长的字符串,这会让 索引变得很大,查询时,浪费大量的磁盘IO, 影响查询效率。此时可以只将字符串的一部分前缀,建 立索引,这样可以大大节约索引空间,从而提高索引效率。
语法:
CREATE INDEX index_name ON table_name (column_name(length));
其中,index_name是索引的名称,table_name是表名,column_name是需要创建索引的列名,length是可选参数,表示索引的前缀长度。如果不指定length参数,则默认使用整个字段值作为索引。
e.g. create index idx_email_5 on tb_user(email(5));
索引前缀的长度可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值, 索引选择性越高则查询效率越高, 唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
select count(distinct email) / count(*) from tb_user ;
select count(distinct substring(email,1,5)) / count(*) from tb_user ;
注意,前缀索引的流程在找到符合前缀的,然后根据id去聚集索引找到row数据,然后对比是否和要找的内容是否一样,一样的话就返回;然后!接着在前缀索引的链表(B+tree叶子结点找下一个,看是不是和还是和我们的前缀一样,也就是叶子结点的key可能是一样的,但是他们挂载的id可能不同)
- 查找前缀:在B+树中,MySQL会根据前缀索引的值查找到第一个匹配的叶子节点。
- 获取行指针:每个叶子节点都包含一个行指针(id),这个行指针指向聚集索引中的相应行。
- 读取行数据:MySQL会根据行指针读取聚集索引中的行数据。
- 比较行数据:MySQL会比较行数据和查询条件,如果完全匹配,则返回这行数据。
- 查找下一个节点:如果叶子节点的key和查询的前缀相同,MySQL会继续查找下一个叶子节点。因为B+树的叶子节点是通过链表连接的,所以可以直接找到下一个叶子节点。
- 重复步骤2-5:MySQL会重复步骤2-5,直到找到所有匹配的行或者遇到一个不匹配的前缀为止。
**单列索引与联合索引 **
单列索引:即一个索引只包含单个列。 联合索引:即一个索引包含了多个列。
在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引, 而非单列索引。 因为使用联合索引可以使用覆盖索引,避免造成回表
索引设计原则 索引设计原则
- 针对于数据量较大,且查询比较频繁的表建立索引。
- 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索 引。
- 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
- 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
- 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间, 避免回表,提高查询效率。
- 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。
- 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含 NULL值时,它可以更好地确定哪个索引最有效地用于查询。
SQL优化
- 插入数据
- insert优化
- 大批量插入数据
第三个指定后面代表每一个字段用,分隔;每一行之间使用\n分隔
- insert优化
- 主键优化
- 在InnoDB存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(IOT)
- 主键顺序插入的性能是要高于乱序插入的,乱序插入可能出现页分裂现象
- 主键设计原则
- 满足业务需求的情况下,尽量降低主键的长度。
- 插入数据时,尽量选择顺序插入,选择使用AUTO_INCREMENT自增主键。
- 尽量不要使用UUID做主键或者是其他自然主键,如身份证号。
- 业务操作时,避免对主键的修改(删除主键可能造成页合并现象)
- order by 优化
MySQL的排序,有两种方式:- Using filesort : 通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sort buffer中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。
- Using index : 通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高。
对于以上的两种排序方式,Using index的性能高,而Using filesort的性能低,我们在优化排序 操作时,尽量要优化为 Using index。
- 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则 这里和where不同,where后面如果多个条件,条件顺序没有关系;但是group by的要注意条件的顺序
- 尽量使用覆盖索引。
- 多字段排序, 一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)。
- 如果不可避免的出现filesort,大数据量排序时,可以适当增大排序缓冲区大小 sort_buffer_size(默认256k)。
关键字 sort_buffer_size - group by 优化
- 在分组操作中,我们需要通过以下两点进行优化,以提升性能:
- 在分组操作时,可以通过索引来提高效率。
- 分组操作时,索引的使用也是满足最左前缀法则的。
- limit 优化
- 在数据量比较大时,如果进行limit分页查询,在查询时,越往后,分页查询效率越低。
- 因为,当在进行分页查询时,如果执行 limit 2000000,10 ,此时需要MySQL排序前2000010 记 录,仅仅返回 2000000 - 2000010 的记录,其他记录丢弃,查询排序的代价非常大
- 优化思路: 一般分页查询时,通过创建覆盖索引能够比较好地提高性能,可以通过覆盖索引加子查询形式进行优化。
explain select * from tb_sku t , (select id from tb_sku order by id limit 2000000,10) a where t.id = a.id;
- count 优化
- count() 是一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是 NULL,累计值就加 1,否则不加,最后返回累计值。
- 按照效率排序的话,count(字段) < count(主键 id) < count(1) ≈ count(),所以尽 量使用 count()。
- update 优化
- 我们主要需要注意一下update语句执行时的注意事项。
update course set name = ‘javaEE’ where id = 1 ;
当我们在执行删除的SQL语句时,会锁定id为1这一行的数据,然后事务提交之后,行锁释放。 - update course set name = ‘SpringBoot’ where name = ‘PHP’ ;
当我们开启多个事务,在执行上述的SQL时,我们发现行锁升级为了表锁。 导致该update语句的性能 大大降低。 - InnoDB的行锁是针对索引加的锁,不是针对记录加的锁 ,并且该索引不能失效,否则会从行锁 升级为表锁 。
- 我们主要需要注意一下update语句执行时的注意事项。
视图(View)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。
语法,创建,更新,修改,删除视图```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] 视图名称 [,视图名称] …
可以通过视图插入,更新数据
检查选项 当使用WITH CHECK OPTION子句创建视图时,MySQL会通过视图检查正在更改的每个行,例如 插 入,更新,删除,以使其符合视图的定义。 MySQL允许基于另一个视图创建视图,它还会检查依赖视 图中的规则以保持一致性。为了确定检查的范围,mysql提供了两个选项: CASCADED 和 LOCAL ,默认值为 CASCADED 。
1. CASCADED 级联。 比如,v2视图是基于v1视图的,如果在v2视图创建的时候指定了检查选项为 cascaded,但是v1视图 创建时未指定检查选项。 则在执行检查时,不仅会检查v2,还会级联检查v2的关联视图v1。
2. LOCAL 本地。 比如,v2视图是基于v1视图的,如果在v2视图创建的时候指定了检查选项为 local ,但是v1视图创 建时未指定检查选项。 则在执行检查时,只会检查v2,不会检查v2的关联视图v1
这样子,视图就不能插入不满足自己视图条件的数据 <br />cascaed是儿子检查完,爸爸也要检查。local是儿子检查完,问爸爸要不要检查<br />如果自己没有检查选项,自己的条件就不用看了,但是要看依赖的,依赖有检查选项就接着看,如果依赖的还有依赖就接着看;<br />看父亲是一定要看的!有没有检查选项决定看不看自己的条件;但是父亲的条件是不是无脑使用就决定了cascaed和local的区别;
视图更新 要使视图可更新,视图中的行与基础表中的行之间必须存在一对一的关系。如果视图包含以下任何一 项,则该视图不可更新:
1. 聚合函数或窗口函数(SUM()、 MIN()、 MAX()、 COUNT()等
2. DISTINCT
3. GROUP BY
4. HAVING
5. UNION 或者 UNION ALL
视图作用
1. 简单 视图不仅可以简化用户对数据的理解,也可以简化他们的操作。那些被经常使用的查询可以被定义为视 图,从而使得用户不必为以后的操作每次指定全部的条件。
2. 安全 数据库可以授权,但不能授权到数据库特定行和特定的列上。通过视图用户只能查询和修改他们所能见到的数据
3. 数据独立 视图可帮助用户屏蔽真实表结构变化带来的影响。
存储过程(基本语法和变量) 存储过程是事先经过编译并存储在数据库中的一段 SQL 语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。 存储过程思想上很简单,就是数据库 SQL 语言层面的代码封装与重用 <br /> 特点:
1. 封装,复用 -----------------------> 可以把某一业务SQL封装在存储过程中,需要用到 的时候直接调用即可。
2. 可以接收参数,也可以返回数据 --------> 再存储过程中,可以传递参数,也可以接收返回值。
3. 减少网络交互,效率提升 -------------> 如果涉及到多条SQL,每执行一次都是一次网络传 输。 而如果封装在存储过程中,我们只需要网络交互一次可能就可以了。
基本语法:
```sql
#创建
CREATE PROCEDURE 存储过程名称 ([ 参数列表 ])
BEGIN
-- SQL语句
END ;
#调用
CALL 名称 ([ 参数 ]);
#查看
SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'xxx';
#查询information_schema.Routines表: information_schema数据库中的Routines表包含了关于所有存储过程和函数的信息。
SELECT * FROM information_schema.Routines WHERE ROUTINE_NAME = 'get_customer' AND ROUTINE_SCHEMA = 'your_database_name';
#这将只返回在 your_database_name 数据库中名为 get_customer 的存储过程的信息
-- 查询指定数据库的存储过程及状态信息
SHOW CREATE PROCEDURE 存储过程名称 ; -- 查询某个存储过程的定义
#删除
DROP PROCEDURE [ IF EXISTS ] 存储过程名称 ;
#注意:
#在命令行中,执行创建存储过程的SQL时,需要通过关键字 delimiter 指定SQL语句的结束符。
变量
- 系统变量
- 系统变量 是MySQL服务器提供,不是用户定义的,属于服务器层面。分为全局变量(GLOBAL)、会话 变量(SESSION)。
- 全局变量(GLOBAL): 全局变量针对于所有的会话。
- 会话变量(SESSION): 会话变量针对于单个会话,在另外一个会话窗口就不生效了
SHOW [ SESSION | GLOBAL ] VARIABLES ; -- 查看所有系统变量
SHOW [ SESSION | GLOBAL ] VARIABLES LIKE '......';
-- 可以通过LIKE模糊匹配方式查找变量
SELECT @@[SESSION | GLOBAL] 系统变量名; -- 查看指定变量的值
select @@global.autocommit;
#设置系统变量
SET [ SESSION | GLOBAL ] 系统变量名 = 值 ;
SET @@[SESSION | GLOBAL]系统变量名 = 值 ;
SET @@SESSION.autocommit = 0;
#如果没有指定SESSION/GLOBAL,默认是SESSION,会话变量。
mysql服务重新启动之后,所设置的全局参数会失效,要想不失效,可以在 /etc/my.cnf 中配置
- 用户定义变量
- 用户定义变量 是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用 “@变量 名” 使用就可以。其作用域为当前连接。
-
#赋值
#方式一
SET @var_name = expr [, @var_name = expr] ... ;
#set @myname = 'itcast';
SET @var_name := expr [, @var_name := expr] ... ;
#set @mygender := '男',@myhobby := 'java';
#使用
SELECT @var_name ;
select @myname,@myage,@mygender,@myhobby;
用户定义的变量无需对其进行声明或初始化,只不过获取到的值为NULL。
获取一个不存在的用户变量,他的值就会为NULL
- 局部变量
- 局部变量 是根据需要定义的在局部生效的变量,访问之前,需要DECLARE声明。可用作存储过程内的 局部变量和输入参数,局部变量的范围是在其内声明的BEGIN … END块。
-
#声明
DECLARE 变量名 变量类型 [DEFAULT ... ] ;
#变量类型就是数据库字段类型:INT、BIGINT、CHAR、VARCHAR、DATE、TIME等
#赋值
SET 变量名 = 值 ;
SET 变量名 := 值 ;
SELECT 字段名 INTO 变量名 FROM 表名 ... ;
#演示
-- 声明局部变量 - declare
-- 赋值
create procedure p2()
begin
declare stu_count int default 0;
select count(*) into stu_count from student;
select stu_count;
end;
call p2();
参数
- 参数的类型,主要分为以下三种:IN、OUT、INOUT。 具体的含义如下:
- 案例
CREATE PROCEDURE 存储过程名称 ([ IN/OUT/INOUT 参数名 参数类型 ])
BEGIN
-- SQL语句
END ;
#示例
create procedure p4(in score int, out result varchar(10))
begin
if score >= 85 then
set result := '优秀';
elseif score >= 60 then
set result := '及格';
else
set result := '不及格';
end if;
end;
-- 定义用户变量 @result来接收返回的数据, 用户变量可以不用声明
call p4(18, @result);
select @result;
存储过程(if,case,while,repeat,Loop,游标和条件处理程序) if 用于做条件判断,具体的语法结构为
IF 条件1 THEN
.....
ELSEIF 条件2 THEN -- 可选
.....
ELSE -- 可选
.....
END IF;
#在if条件判断的结构中,ELSE IF 结构可以有多个,也可以没有。 ELSE结构可以有,也可以没有。
#案例
create procedure p3()
begin
declare score int default 58;
declare result varchar(10);
if score >= 85 then
set result := '优秀';
elseif score >= 60 then
set result := '及格';
else
set result := '不及格';
end if;
select result;
end;
call p3();
case结构及作用,和我们在基础篇中所讲解的流程控制函数很类似。
#方式一
-- 含义: 当case_value的值为 when_value1时,执行statement_list1,当值为 when_value2时,
执行statement_list2, 否则就执行 statement_list
CASE case_value
WHEN when_value1 THEN statement_list1
[ WHEN when_value2 THEN statement_list2] ...
[ ELSE statement_list ]
END CASE;
#方式二
-- 含义: 当条件search_condition1成立时,执行statement_list1,当条件search_condition2成
立时,执行statement_list2, 否则就执行 statement_list
CASE
WHEN search_condition1 THEN statement_list1
[WHEN search_condition2 THEN statement_list2] ...
[ELSE statement_list]
END CASE;
#案例
create procedure p6(in month int)
begin
declare result varchar(10);
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 := '第四季度';
else
set result := '非法参数';
end case ;
select concat('您输入的月份为: ',month, ', 所属的季度为: ',result);
end;
call p6(16);
#如果判定条件有多个,多个条件之间,可以使用 and 或 or 进行连接。
while 循环是有条件的循环控制语句。满足条件后,再执行循环体中的SQL语句
-- 先判定条件,如果条件为true,则执行逻辑,否则,不执行逻辑
WHILE 条件 DO
SQL逻辑...
END WHILE;
#示例
-- A. 定义局部变量, 记录累加之后的值;
-- B. 每循环一次, 就会对n进行减1 , 如果n减到0, 则退出循环
create procedure p7(in n int)
begin
declare total int default 0;
while n>0 do
set total := total + n;
set n := n - 1;
end while;
select total;
end;
call p7(100);
repeat是有条件的循环控制语句, 当满足until声明的条件的时候,则退出循环 。
-- 先执行一次逻辑,然后判定UNTIL条件是否满足,如果满足,则退出。如果不满足,则继续下一次循环
REPEAT
SQL逻辑...
UNTIL 条件
END REPEAT;
-- A. 定义局部变量, 记录累加之后的值;
-- B. 每循环一次, 就会对n进行-1 , 如果n减到0, 则退出循环
create procedure p8(in 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;
call p8(10);
call p8(100);
LOOP 实现简单的循环,如果不在SQL逻辑中增加退出循环的条件,可以用其来实现简单的死循环。 LOOP可以配合一下两个语句使用: (相当于break,continue)
- LEAVE :配合循环使用,退出循环。
- ITERATE:必须用在循环中,作用是跳过当前循环剩下的语句,直接进入下一次循环。
[begin_label:] LOOP
SQL逻辑...
END LOOP [end_label];
LEAVE label; -- 退出指定标记的循环体
ITERATE label; -- 直接进入下一次循环
#案例一
-- A. 定义局部变量, 记录累加之后的值;
-- B. 每循环一次, 就会对n进行-1 , 如果n减到0, 则退出循环 ----> leave xx
create procedure p9(in n int)
begin
declare total int default 0;
sum:loop
if n<=0 then
leave sum;
end if;
set total := total + n;
set n := n - 1;
end loop sum;
select total;
end;
call p9(100);
#案例二
-- A. 定义局部变量, 记录累加之后的值;
-- B. 每循环一次, 就会对n进行-1 , 如果n减到0, 则退出循环 ----> leave xx
-- C. 如果当次累加的数据是奇数, 则直接进入下一次循环. --------> iterate xx
create procedure p10(in n int)
begin
declare total int default 0;
sum:loop
if n<=0 then
leave sum;#退出sunm循环体
end if;
if n%2 = 1 then
set n := n - 1;
iterate sum;#sum循环体进行下一次循环
end if;
set total := total + n;
set n := n - 1;
end loop sum;
游标(CURSOR)是用来存储查询结果集的数据类型 , 在存储过程和函数中可以使用游标对结果集进行循环的处理。游标的使用包括游标的声明、OPEN、FETCH 和 CLOSE
#声明游标
DECLARE 游标名称 CURSOR FOR 查询语句 ;
#打开游标
OPEN 游标名称 ;
#获取游标记录
FETCH 游标名称 INTO 变量 [, 变量 ] ;
#关闭游标
CLOSE 游标名称 ;
#案例
-- 逻辑:
-- A. 声明游标, 存储查询结果集
-- B. 准备: 创建表结构
-- C. 开启游标
-- D. 获取游标中的记录
-- E. 插入数据到新表中
-- F. 关闭游标
create procedure p11(in uage int)
begin
declare uname varchar(100);
declare upro varchar(100);
declare u_cursor cursor for select name,profession from tb_user where age <=
uage;
drop table if exists tb_user_pro;
create table if not exists tb_user_pro(
id int primary key auto_increment,
name varchar(100),
profession varchar(100)
);
open u_cursor;
while true do
fetch u_cursor into uname,upro;
insert into tb_user_pro values (null, uname, upro);#tb_user_pro是新表,会创建这个新表
end while;
close u_cursor;
end;
call p11(30);
条件处理程序
条件处理程序(Handler)可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤。
DECLARE handler_action HANDLER FOR condition_value [, condition_value]
... statement ;
handler_action 的取值:
CONTINUE: 继续执行当前程序
EXIT: 终止执行当前程序
condition_value 的取值:
SQLSTATE sqlstate_value: 状态码,如 02000
SQLWARNING: 所有以01开头的SQLSTATE代码的简写
NOT FOUND: 所有以02开头的SQLSTATE代码的简写
SQLEXCEPTION: 所有没有被SQLWARNING 或 NOT FOUND捕获的SQLSTATE代码的简写
#示例
-- 逻辑:
-- A. 声明游标, 存储查询结果集
-- B. 准备: 创建表结构
-- C. 开启游标
-- D. 获取游标中的记录
-- E. 插入数据到新表中
-- F. 关闭游标
create procedure p11(in uage int)
begin
declare uname varchar(100);
declare upro varchar(100);
declare u_cursor cursor for select name,profession from tb_user where age <=uage;
-- 声明条件处理程序 : 当SQL语句执行抛出的状态码为02000时,将关闭游标u_cursor,并退出
declare exit handler for SQLSTATE '02000' close u_cursor;
drop table if exists tb_user_pro;
create table if not exists tb_user_pro(
id int primary key auto_increment,
name varchar(100),
profession varchar(100)
);
open u_cursor;
while true do
fetch u_cursor into uname,upro;
insert into tb_user_pro values (null, uname, upro);
end while;
close u_cursor;
end;
call p11(30);
存储函数 存储函数是有返回值的存储过程,存储函数的参数只能是IN类型的。
CREATE FUNCTION 存储函数名称 ([ 参数列表 ])
RETURNS type [characteristic ...]
BEGIN
-- SQL语句
RETURN ...;
END ;
#案例
create function fun1(n int)
returns int deterministic
begin
declare total int default 0;
while n>0 do
set total := total + n;
set n := n - 1;
end while;
return total;
end;
select fun1(50);
characteristic说明:
- DETERMINISTIC:相同的输入参数总是产生相同的结果
- NO SQL :不包含 SQL 语句
- READS SQL DATA:包含读取数据的语句,但不包含写入数据的语句
触发器 触发器是与表有关的数据库对象,指在insert/update/delete之前(BEFORE)或之后(AFTER),触 发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性 , 日志记录 , 数据校验等操作
使用别名OLD和NEW来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。MYSQL现在触发器还只支持行级触发,不支持语句级触发
#创建
CREATE TRIGGER 触发器名称
BEFORE/AFTER INSERT/UPDATE/DELETE
ON 表名 FOR EACH ROW -- 行级触发器
BEGIN
trigger_stmt ;
END;
#查看
SHOW TRIGGERS ;
#删除
DROP TRIGGER [schema_name.]trigger_name ;
-- 如果没有指定 schema_name,默认为当前数据库 。
#案例
-- 准备工作 : 日志表 user_logs
create table user_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;
#插入数据触发器
#concat字符串拼接函数
create trigger tb_user_insert_trigger
after insert on tb_user for each row
begin
insert into user_logs(id, operation, operate_time, operate_id, operate_params)
VALUES
(null, 'insert', now(), new.id, concat('插入的数据内容为:
id=',new.id,',name=',new.name, ', phone=', NEW.phone, ', email=', NEW.email, ',
profession=', NEW.profession));
end;
#修改数据触发器
create trigger tb_user_update_trigger
after update on tb_user for each row
begin
insert into user_logs(id, operation, operate_time, operate_id, operate_params)
VALUES
(null, 'update', now(), new.id,
concat('更新之前的数据: id=',old.id,',name=',old.name, ', phone=',
old.phone, ', email=', old.email, ', profession=', old.profession,
' | 更新之后的数据: id=',new.id,',name=',new.name, ', phone=',
NEW.phone, ', email=', NEW.email, ', profession=', NEW.profession));
end;
#删除数据触发器
create trigger tb_user_delete_trigger
after delete on tb_user for each row
begin
insert into user_logs(id, operation, operate_time, operate_id, operate_params)
VALUES
(null, 'delete', now(), old.id,
concat('删除之前的数据: id=',old.id,',name=',old.name, ', phone=',
old.phone, ', email=', old.email, ', profession=', old.profession));
end;
-- 查看触发器
show triggers ;
锁锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、 RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。
如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个 角度来说,锁对数据库而言显得尤其重要,也更加复杂。
MySQL中的锁,按照锁的粒度分,分为以下三类:
- 全局锁:锁定数据库中的所有表。
- 表级锁:每次操作锁住整张表。
- 行级锁:每次操作锁住对应的行数据。
全局锁(所有数据库所有表)全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语 句,已经更新操作的事务提交语句都将被阻塞。
典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。
为什么全库逻辑备份,就需要加全就锁呢?
假设在数据库中存在这样三张表: tb_stock 库存表,tb_order 订单表,tb_orderlog 订单日 志表。
在进行数据备份时,先备份了tb_stock库存表。
然后接下来,在业务系统中,执行了下单操作,扣减库存,生成订单(更新tb_stock表,插入 tb_order表)。
然后再执行备份 tb_order表的逻辑。 业务中执行插入订单日志操作。
最后,又备份了tb_orderlog表。
此时备份出来的数据,是存在问题的。因为备份出来的数据,tb_stock表与tb_order表的数据不一 致(有最新操作的订单信息,但是库存数没减)。对数据库进行进行逻辑备份之前,先对整个数据库加上全局锁,一旦加了全局锁之后,其他的DDL、 DML全部都处于阻塞状态,但是可以执行DQL语句,也就是处于只读状态,而数据备份就是查询操作。 那么数据在进行逻辑备份的过程中,数据库中的数据就是不会发生变化的,这样就保证了数据的一致性 和完整性
#加全局锁
flush tables with read lock ;
#数据备份
mysqldump -uroot –p1234 itcast > itcast.sql
#数据备份的相关指令, 在后面MySQL管理章节, 还会详细讲解
#释放锁
unlock tables ;
不会锁定数据库的结构(例如,创建或删除表)
数据库中加全局锁,是一个比较重的操作,存在以下问题:
- 如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。
- 如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟。
在InnoDB引擎中,我们可以在备份时加上参数 --single-transaction 参数来完成不加锁的一致性数据备份。(就是不用加锁,也可以数据一致性的备份方法)
mysqldump --single-transaction -uroot –p123456 itcast > itcast.sql
#使用 --single-transaction 参数的 mysqldump 备份过程就像是
给数据库拍了一张“照片”,
这张“照片”反映了开始备份时刻数据库的一致性状态。
在备份过程中,尽管数据库可以正常进行读写操作,
但这些读写操作不会影响到备份的内容,
因为备份所依据的是开始事务时的数据快照。
表级锁(表锁,元数据锁,意向锁)表级锁,每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在MyISAM、 InnoDB、BDB等存储引擎中。
对于表级锁,主要分为以下三类:
- 表锁
- 元数据锁(meta data lock,MDL)
- 意向锁
对于表锁,分为两类:
- 表共享读锁(read lock) [大家都可以读,大家都不能写]
- 表独占写锁(write lock)[只有加锁人可以读写,其他人不能读写]
语法:
- 加锁:lock tables 表名 read/write。
- 释放锁:unlock tables / 客户端断开连接 。
元数据锁
元数据锁(Metadata Lock,简称 MDL)是 MySQL 用于解决 DDL 操作与 DML 操作之间一致性的一种机制。以下是关于元数据锁的一些关键信息:
- 定义:元数据锁是一种表级锁,用于保护表的元数据信息,以确保 DDL 操作(如创建、删除、修改表结构等)与 DML 操作(如插入、删除、更新和查询等)之间的一致性。
- 工作原理:当对一个表进行增删改查操作时,MySQL 会在该表上加一个 MDL 读锁;当要对表进行结构变更操作时,会加一个 MDL 写锁。读锁之间不互斥,可以有多个线程同时对一张表进行增删改查。读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性。
当我们在执行增删改查操作时,mysql会为表加上共享锁(SHARED),无论添加的SHARED_READ还是SHARED_WRITE,它们之间都是兼容的,即一个事务在对某张表进行读写操作时,其他事务也可以对这张表进行写读写操作。但是当我们执行表结构修改语句时,mysql会为表加上排他锁(EXCLUSIVE)。而排他锁与共享锁之间是不兼容的,也就是说当一个事务在对某张表进行读写操作时,其他事务是不可以对这张表的表结构进行修改的。需要额外注意的是,EXCLUSIVE与表锁(SHARED_READ_ONLY /SHARED_NO_READ_WRITE)之间也是互斥的,也就是当一个事务为一张表添加上表锁后,其他事务也不能修改该表的表结构。
意向锁
为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。
有了意向锁之后 :
- 客户端一,在执行DML操作时,会对涉及的行加行锁,同时也会对该表加上意向锁。
- 其他客户端,在对这张表加表锁的时候,会根据该表上所加的意向锁来判定是否可以成功加表锁,不用逐行判断行锁情况了
分类
- 意向共享锁: 由语句select … lock in share mode添加 。 与表锁共享锁 (read)兼容,与表锁排他锁(write)互斥。
- 意向排他锁(IX): 由insert、update、delete、select…for update添加。与表锁共享锁(read)及排他锁(write)都互斥,意向锁之间不会互斥
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from
performance_schema.data_locks;
#查看意向锁及行锁的加锁情况
行级锁(行锁,间隙锁,临键锁)行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在 InnoDB存储引擎中。
InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。
分为以下三类:
- 行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此行进行update和delete。在 RC、RR隔离级别下都支持。
- 间隙锁(Gap Lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持。
- 临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。 在RR隔离级别下支持。
InnoDB实现了以下两种类型的行锁:
- 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。
- 排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。
常见的sql语句加的锁select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks; # 查看意向锁及行锁的加锁情况
默认情况下,InnoDB在 REPEATABLE READ事务隔离级别运行,InnoDB使用 next-key 锁(临键锁)进行搜索和索引扫描,以防止幻读。
- 针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
- InnoDB的行锁是针对于索引加的锁,不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,此时就会升级为表锁。
间隙锁&临键锁
例子环境:
默认情况下,InnoDB在 REPEATABLE READ事务隔离级别运行,InnoDB使用 next-key 锁进行搜索和索引扫描,以防止幻读。
- 索引上的等值查询(唯一索引),给不存在的记录加锁时, 优化为间隙锁。
比如说(A事务)更新id为5的数据,但是我只有id为3和8,那么就是3,8之间的间隙,此时如果B事务要插入一个id为7的数据,就不会成功(A事务没提交)[唯一索引的意思是建立索引的这个字段是unique的,不是这个表只有一个索引] - 索引上的等值查询(非唯一普通索引),向右遍历时最后一个值不满足查询需求时,next-key lock 退化为间隙锁。
比如:在例子条件中建立age索引;此时我要查询age=3的数据;
因为不是唯一索引,可能有多个age=3的,对于这些每一个age=3的,都建立临键锁(自己和前面的间隙),然后到第一个不符合的,例子里的8,建立的就是间隙锁,只锁8之前的间隙,不锁8 - 索引上的范围查询(唯一索引)–会访问到不满足条件的第一个值为止。
例如:我们要查询age>=19的,会建立四把锁
第二把锁是行锁,由于这里我们的查询条件是age>=19,而id=19的数据正好满足age=19,又由于id是唯一索引,因此仅在这行数据上,临键锁优化成了行锁。
第三把锁是临键锁,由于我们这次使用的是主键索引,因此只需要一把锁就足够了,这时可能有小伙伴会问,只锁住主键索引的话,如果有其他事务通过age索引来修改该行数据了怎么办呢?这个大可放心,因为innodb引擎下,数据都是组织在主键索引中的,只要是更新操作,最终都一定会走主键索引的,只要我们锁住了主键索引,更新操作就无法完成。第三把锁中lock_date的值为supremun pseudo-record,这个代表该临键锁是正无穷的临键锁,锁住了正无穷以及其之前的间隙。
第四把锁是25的临键锁
最终的查询结果有两条数据,一条是id为19的数据,一条是id为25的数据,经过之前的分析我们知道,innodb引擎在执行查询时会为符合结果的每个数据都加上一把临键锁,这里符合数据的结果有两个,19由于符合等值优化成了行锁(第二把锁),因此只有25被加上了临键锁(第四把锁),当然为25加上临键锁之后innodb还会继续往后查找有没有符合条件的数据,但是显然是没有了,因为25已经是最大的id值了,那么innodb引擎会就此罢休吗?当然不会,要知道临键锁本身就是用来防止幻读的,如果只锁住[19,25],那么如果有事务再插入了一条id为26的数据,那么仍然会出现幻读现象,因此innodb一不做二不休,直接将(25,+∞]之间的间隙也加上临键锁(第三把锁),临键锁的对象是无穷大,意思是哪怕你id值为无穷大,此刻也插入不进来
锁的样子
行锁:REC_NOT_GAP
间隙锁:GAP
临键锁:
前面的s代表的是shared