目录
基本知识
主流数据库
数据库基本概念
mysql启动
数据库基本命令
数据库
启动数据库
显示数据库
创建数据库
删除数据库
使用数据库
查询当前数据库信息
显示数据库中的表
导入数据库脚本
表
查看表的结构
查看创建某个表的SQL语句
数据库的查询命令
查询字段
运算符操作
表
数据类型
DDL(数据定义语言)
创建表
修改表
重命名表
删除表
DML(数据操作语言)
插入数据
更新数据(修改数据)
删除数据
约束
1. 非空约束(not null)
2. 默认约束(default)
3. 唯一约束(unique)
4. 主键约束(primary key)
5. 自动递增(通常与主键一起使用)
6. 外键约束(foreign key)
高级查询
查询处理
排序(order by)
限制数量(limit)
去重(distinct)
组合查询(union)
函数
MySQL 字符串函数
MySQL 数字函数
MySQL 日期函数
聚合函数(分组函数)
流程函数(if/ifnull)
分组查询
创建分组(group by)
过滤分组(分组后过滤条件)
select顺序
正则表达式
匹配单个实例
匹配多个实例
多表查询
连接查询
内连接
外连接
子查询
使用子查询过滤
计算字段作为子查询
select完整书写格式
存储过程和自定义函数
存储过程
使用存储过程
使用参数
使用变量
逻辑语句
条件语句(if、case)
循环语句(while、repeat)
计算前n项的和
特点
自定义函数
判断账号密码
游标
检索单行数据
循环检索数据
练习(存储过程、游标、自定义函数)
索引
常见索引类型
B+树
二分查找法
二叉树和平衡树
B树和B+树
【面试题】B树和B+树的区别?
使用索引
前缀索引
聚簇索引
使用聚簇索引的优缺点
覆盖索引
索引设计原则
索引使用策略
触发器和试图
触发器
视图
三范式
第一范式(1NF)
第二范式(2NF)
第三范式(3NF)
范式总结
事务和锁
事务
使用事务
四个特性
并行事务带来的问题
隔离级别
InnoDB 中的锁
共享锁和排他锁
意向锁
致性非锁定读
一致性锁定读
基本知识
主流数据库
- 关系型数据库:
- MySQL: 开源免费的关系型数据库,易于使用和学习,支持大型企业级应用。其特点包括高性能、可靠性和可扩展性,支持多种编程语言和操作系统,拥有大量的社区支持和插件
- SQLite:轻量级的嵌入式关系型数据库,支持跨平台开发和部署,易于使用和集成适用于小型应用和移动应用。
- SQLServer: 微软开发的关系型数据库管理系统,适用于中小型企业应用,拥有可靠的性能、安全性和易用性
- Oracle:全球领先的商业关系型数据库,拥有极强的稳定性、安全性和可扩展性,支持高可用、分布式架构,提供强大的数据分析和管理功能
- 非关系型数据库:
- MongoDB:面向文档的非关系型数据库,可扩展性强,可在多台机器上进行分布式部
- Redis: 内存数据库,以键值对形式存储数据,支持多种数据结构,适用于高速读写和缓存等场景。
关系型与非关系型的区别:
- 关系型数据库分字段 查数据
- 非关系型记录一条数据 存和取数据
数据库基本概念
- 数据库管理系统(Database Management System,DBMS): 数据库系统中对数据进行管理的软件系统。
- 数据库(Database,DB): 按照特定的数据结构来组织、存储和管理数据的仓库
- 表(Table):某种特定类型数据的结构化清单。
- 列(Column)或字段:表由一个或多个列组成,每个列有对应的数据类型
- 行(Row)或记录:表中的数据是按行存储的,每行存储一条数据 (记录)
- 主键(Primary Key):一列 (或一组列),它的值能够唯一区分表中每一行
- SQL(Structured Query Language): 结构化查询语言,专门用于与数据库通信的语言.
MySQL启动
- 软件的安装需要注意的问题
- 安装路径使用默认路径
- 用户名: root 密码:123456
- 软件启动
- 开始->MySQL->MySQL5.7 -> 123456
- *** start mysql57 (关闭命令为 *** stop mysql57,需要管理员权限打开 cmd,以后如果无法连接数据库,先执行该命令)
- Windows+r打开cmd 输入: mysql-uroot-p123456
数据库基本命令
数据库
启动数据库
*** start mysql57
显示数据库
(1)显示所有已经存在的数据库
show databases;
(2)显示创建某个特定数据库的SQL语句
show create database 数据库名;
运行结果解读:
编辑
- Query OK,--查询成功
- 1 row affected--一条记录受影响 也就是新建一条数据
- (0.00 sec)--消耗的时间
创建数据库
create database [if not exists] 数据库名;/* [] 中的内容是可加可不加*/
删除数据库
- 将已存在的数据库删除,并同时删除数据库中的数据
drop database [if exists ] 数据库名;
使用数据库
use 数据库名;
查询当前数据库信息
#查询函数时用select
select database();/*查询当前连接的数据库*/
select version();/*查询数据库版本信息*/
select now();/*查询当前时间*/
select user();/*查询当前用户*/
显示数据库中的表
show tables;
导入数据库脚本
【注】
- 路径中不允许出现中文
- 导入前需先创建数据库,然后使用use+数据库 最后执行导入语句
- 没有分号 没有引号
source SQL脚本的文件路径
source C:\Users\Administrator\Desktop\***ment.sql
表
查看表的结构
desc 表名;/*describe的缩写*/
查看创建某个表的SQL语句
show create table 表名;
数据库的查询命令
查询字段
select 字段1,字段2... from 表名;
select sal from emp;
select ename,sal from emp;
select * from 表名;/*查询表中所有信息*/
/*从某个表查询所有满足where条件的信息*/
select * from 表名 where 条件;
运算符操作
1、数值比较/字符比较
- 数值比较:= != > < >= <=
- 字符比较:= !=
2、逻辑操作
- and(两个或多个条件同时成立)
- or(任意一个条件成立即可)
3、范围内比较
- where 字段名 between 值1 and 值2
- where 字段名 in(值1,值2,值3...) #枚举式
- where 字段名 not in(值1,值2,值3,....)
4、匹配空、非空(只能用is 和 is not)
- 空:where 字段名 is null
- 非空: where 字段名 is not null
5、模糊比较
- where 字段名 like 表达式
- _(占位符): 匹配单个字符
- %:匹配0到多个字符
【注】
1、NULL:空值,只能用 is 或者 is not 去匹配不能用'='
2、"":字符串,用 = 或者 != 去匹配
/*emp表*/
--1.查询薪水 sal为5000的员工
select * from emp where sal=5000;
--2.查询员工编号empno大于7800的员工
select *from emp where empno>7800;
--3.查询 sal为1600到3000的员工编号、姓名、薪水
select empno,ename,sal from emp where sal between 1600 and 3000;
--4.查询工作job不等于manager的员工姓名、工作、部门编号
select ename,job,deptno from emp where job !='manager';
--5.查询出jiob为 manager 或者job 为 salesman 的员工
select * from emp where job = 'manager' or job='salesman';
--6.查询入职日期 hiredate 再1982年1月1日之后的员1
select * from emp where hiredate>'1982-01-01';
--7.查询没有佣金 ***m的员工编号、领导编号、薪水、佣金
select empno,mgr,sal,***m from emp where ***m is not null or ***m=0;
--8.查询姓名ename中包含s的所有员工
select * from emp where ename like '%s%';
--9.查询工作job以MAN结尾的员工
select *from emp where job like '%man';
--10.查询ename中第二个字符为a的所有员工
select * from emp where ename like '_a%';
表
数据类型
- 整型
int默认是11 int(11)
应用场景
(1)TINYINT :一般用于枚举数据,比如系统设定取值范围很小且固定的场景。
(2)SMALLINT :可以用于较小范围的统计数据,比如统计工厂的固定资产库存数量等。
(3)MEDIUMINT :用于较大整数的计算,比如车站每日的客流量等。
(4)INT、INTEGER :取值范围足够大,一般情况下不用考虑超限问题,用得最多。比如商品编号。
(5)BIGINT :只有当你处理特别巨大的整数时才会用到。比如双十一的交易量、大型门户网站点击量、证券公司衍生产品持仓等。
- 浮点型和定点型
(1)浮点数相对于定点数的优点是在长度一定的情况下,浮点类型取值范围大,但是不精准,适用于需要取值范围大,又可以容忍微小误差的科学计算场景(比如计算化学、分子建模、流体动力学等);
(2)定点数类型取值范围相对小,但是精准,没有误差,适合于对精度要求极高的场景 (比如涉及金额计算的场景)。
FLOAT和DOUBLE的区别?
FLOAT占用字节数少,取值范围小;DOUBLE占用字节数多,取值范围大。
- 位类型介绍
BIT类型中存储的是二进制值。
BIT类型,如果没有指定(M),默认是1位。这个1位,表示只能存1位的二进制值。这里(M)是表示二进制的位数,位数最小值为1,最大值为64。
- 日期和时间类型
最后一个类型是时间戳 应用场景:格式不确定时使用.
- 字符串类型
注意:char(n) 和 varchar(n) 中括号中 n 代表字符的个数,并不代表字节个数,比如 CHAR(30) 就可以存储 30 个字符。
char和varchar区别?
1.char类型用于存储固定长度字符串 最大长度是255,
按照char给的定长存储存储长度如果不到指定长度时,尾部会填充空格 查询时会自动去掉空格
varchar类型用于存储可变长度字符串 最大65535 varchar保留实际字符,查询时不会去掉空格
Varchar(x) x:最长能存占的空间小 varchar默认255
2.char查找起来更快
=查询号时都会忽略空格,查询还会显示空格,不会去掉
- 枚举与集合类型
- ENUM: 枚举类型,用于存储单一值,可以选择一个预定义的集合。
- SET: 集合类型,用于存储多个值,可以选择多个预定义的集合。
DDL(数据定义语言)
创建表
create table [if not exists] 表名(
字段1 字段类型 [约束条件],
字段1 字段类型 [约束条件],
字段1 字段类型 [约束条件]--注意:最后一个字段后面没有逗号
);--注意:加分号
创建表的示例:
1.创建一个文章表 article,有如下字段
a. id: 文章ID
b. title: 标题
c. author:作者
d. content:文章内容
create table if not exists article(
id int not null,/*不加not null默认可以为空*/
title varchar(30),
author varchar(10),
content text
);
2.创建一个订单表,有如下字段:
a. id: 订单ID
b. customer id: 顾客编号
c. date:订单日期
d. money:订单金额
create table if not exists 订单(
id int,
costomer_id varchar(10),
date datetime,
money double
);
修改表
- 添加字段
alter table 表名
add column 新列名 数据类型 [约束条件] [first after 列名];
- 修改字段的类型
alter table 表名
modify column 列名 数据类型 [约束条件];
- 修改字段的位置
alter table 表名
modify column 列名 数据类型 first after 列名;
- 修改字段名
alter table 表名
change column 旧列名 新列名 数据类型
- 删除字段
alter table 表名
drop column 列名;
重命名表
alter table 旧表名
rename to 新表名;
删除表
drop table [if exists] 表1[,表2,表3...];
练习学生信息表的创建与修改
- 建立学生信息表,字段包括: 学号、姓名、年龄、出生日期
- 在姓名字段后加电话字段,字段名 telno,类型为 varchar 类型
- 将电话字段修改为 char 类型
- 将出生日期字段放在学号字段之前
- 将电话字段名 telno修改为 phone_number
- 删除字段phone_number
- 将表名修改为另外的名字
- 删除表
create table if not exists student(
sno int,
sname varchar(10),
sage int,
birthday date
)
alter table student add tel_no varchar(20) after sname;
alter table student modify tel_no char;
alter table student modify birthday date first ;
alter table student change tel_no phone_number char;
alter table student drop phone_number;
alter table student rename to 学生;
drop table if exists 学生;
DML(数据操作语言)
插入数据
insert into [字段1,2..]表名
values(字段1的值,2);/*字符串要加引号*/
【注意】插入多条数据比多条insert数据快
更新数据(修改数据)
update 表名
set (字段1=字段1的值 ,字段2 =字段2的值)where 限制条件;
【注意】要加where限制条件否则会更新整个表的数据
删除数据
delete from 表名
where 限制条件;
【注意】没有* 加where条件
【注】在使用更新和删除操作时要谨慎,先使用查询语句测试
约束
constraint+名字 约束类型
- 非空约束 NOT NULL - 指示某列不能存储 NULL 值,必须有值。
- 唯一约束 UNIQUE - 保证某列的每行必须有唯一的值,默认允许空值。
- 主键约束 PRIMARY KEY - 默认非空 NOT NULL 和 UNIQUE 的结合。确保某列(或两个列多个列的结合)有唯一标识,有助于更容易更快速地找到表中的一个特定的记录。
- 外键约束 FOREIGN KEY - 保证一个表中的数据匹配另一个表中的值的参照完整性。
- 默认约束 DEFAULT - 规定没有给列赋值时的默认值。
- CHECK - 保证列中的值符合指定的条件。
1. 非空约束(not null)
(1)创建表时设置
create table 表名(字段 类型 not null);
(2)已有字段添加
alter table 表名 modify 字段 类型 not null;
--删除非空约束直接删除 not null 在运行一遍
2. 默认约束(default)
create table 表名(字段 类型 default 默认值);
3. 唯一约束(unique)
- (1)创建表时设置
create table 表名(字段 类型 unique);
(2)已有字段添加(两种方法)
--1
alter table 表名 modify 字段 类型 unique;
--2
alter table 表名 add [constraint 约束名] unique(字段名);
联合唯一约束
create table 表名(
字段 类型,
...
[constraint 约束名] unique(字段1[字段2,...])
);
删除唯一约束
--1
alter table 表名 drop index 约束名;/*index是索引*/
--创建唯一约束一定创建一个索引
--2
alter table 表名 drop key 约束名;
4. 主键约束(primary key)
每个表都应该有一个主键
主键值不能修改
不使用可能会修改的值的列作为主键通过使用id作为主键,因其与业务无关
特点:
- 唯一性:主键必须唯一,不允许重复
- 非空性:主键列不能有空值
- 单一性:每个表只有一个主键 主键可由一个或多个列组成形成复合主键
表级
create table 表名(
字段 类型 primary key,
...
);
联合主键约束
联合主键包含的字段名都不能为空
create table 表名(
字段 类型,
...
[constraint 约束名] primary key(字段1[字段2,...])/*联合主键约束*/
);
删除主键
alter table 表名 drop primary key;
【注】删除主键不需要给名,因为主键只有一个
5. 自动递增(通常与主键一起使用)
auto_increment:设置auto_increment的列增加记录时会实现自动增加(手动插入含有设为自动递增列的字段除外),每个表只允许存在一个递增列。
【注】若手动插入一条记录,自动递增是从最大值开始递增
create table 表名(
字段 类型 auto_increment;
);
6. 外键约束(foreign key)
当创建外键约束后,就出现的父表和子表
-
- 创建表时 先创父表 再创子表
- 插入数据 先插入父表 再子表
- 删除数据 先删子表 再删父表
【注】子表外键类型要与父表类型一
外键约束后该字段列可以为空,外键为空的数据称孤儿数据
--语法
[constraint 外键名] foreign key(列名) references 主表名(主键);
--实例
create table student(
id int primary key auto_increment,
name varchar(20) unique
);
create table score(
id int primary key auto_increment,
degree int,
student_id int,
constraint fk1 foreign key(student_id) references student(id)
);
高级查询
查询处理
排序(order by)
asc--升序排序(不写时默认升序)desc--默认升序
order by 字段1[字段2] [asc|desc]
select * from emp order by sal desc;
【注】查询默认所有不是按主键排序的,无固定排序规则
限制数量(limit)
使用限制语句可限制查询出的行数,两个参数可限制从n到m,其中第一个参数是n-1第二个是m-n+1。limit可解决分页问题,但是页数太多时使用效率很低。
limit 行数(从第一行开始)
limit 开始行(从0开始),行数
select * from emp order by sal limit 1;
select * from emp order by sal limit 3,5
去重(distinct)
放在字段前
用于返回唯一不同的值
查询多个字段时,字段都重复才会去掉,一个重复不会去掉。
#列出所有岗位,先查询再去重
select distinct job from emp;
#同时作用两列,不能查询目标列以外的列
select distinct job,mgr from emp;
组合查询(union)
查询结果求并集
执行多个查询(多条select语句),将结果合并为单个结果集返回;
select 字段1[,字段2,..] from 表1
union
select 字段1[,字段2,..] from 表2;
【注】每个查询列数相同
字段的数据类型类型要相似
union自动去除重复行,允许重复行用union all
查询处理的练习:
emp表
1按照员工的入职日期先后进行降序排序
2查询1981年之后入职的员工,并按照员工薪水 sa1升序排序
3.按照部门升序,薪水降序显示员工信息
4.将公司经理的薪水由高到低进行排序
5.查询员工薪水在2000到3000的,按降序排列
6.取员工中前五人的数据
7查询薪水最高的三名员工信息
8查询出员工中薪水在5-8名的员工信息
9.查询所有领导的编号
10.查询所有的员工编号、员工名以及所有的部门编号和部门名
select * from emp order by HIREDATE desc;
select * from emp where hiredate >='1981-01-01' order by sal ;
select * from emp order by deptno, sal desc;
select * from emp where job='manager' order by sal desc;
select * from emp where sal between 2000 and 3000 order by sal desc;
select * from emp limit 5;
select * from emp order by sal desc limit 4,4; /*5-8名*/
select distinct mgr from emp where mgr is not null;
select empno,ename from emp
union
select deptno,dname from dept;
函数
MySQL 字符串函数
- concat(s1,s2,...): 字符串连接,如果任何一个参数为 null,则返回值为 null
- concat_ws(x,s1,s2,...): 指定分隔符的字符连接函数,x 是连接分隔符,如果分隔符为null,则结果为 null。
- lower(str): 大写转小写
- upper(str):小写转大写
- length(str): 字符串长度
- ltrim(str): 删除字符串左侧空格
- rtrim(str): 删除字符串右侧空格
- trim(str): 删除字符串两侧空格
- substr(str, n,len): 截取子字符串,字符串 str 从n 的位置截取长度为 len 的字符串,如果n 为负数,则子字符串的位置起始于字符串结尾的 n 个字符
- left(str,n): 返回字符串 str 的最左边n个字符
- right(strn): 返回字符串 str 的最右边n个字符
- replace(str,from_str,to_str): 替换函数,字符串 str 中所有的字符串from_str 均被 to_str替换,然后返回这个字符串
- format(x,n):将数字x格式化,并以四舍五入的方式保留小数点后n 位,结果以字符串的形式返回。若n 为0,则返回结果不含小数部分
MySQL 数字函数
- abs(x):返回x的绝对值
- ceil(x):向上取整,返回大于等于x的最小整数值
- floor(x):向下取整,返回小于等于x的最大整数值
- round(x,y=0): 四舍五入,将x四舍五入y位小数,y不传返回整数,y 为负数时,保留x值到小数点左边y位
- truncate(x,y):截函数,返回被舍去至小数点后y位的数字x,y 为负数时截断小数点左边y位
- mod(x,y): 返回x 除以y的余数
- rand():生成0-1的随机数
MySQL 日期函数
- curdate()current date(): 获取当前日期,YYYYMM-DD 格式
- curtime()/current _time(): 获取当前时间,HH:MM:SS 格式
- week(date): 返回 date 为一年中的第几周
- now()/sysdate():获取当前日期和时间,YYYY-MM-DD HH:MM:SS 格式
- date_add(date,interval expr type): 执行日期的加运算,date 是一个 datetime 或 date值,指定起始时间。expr是时间间隔。type 为关键词,如YEAR,MONTH,DAYWEEK,HOUR等.
- datediff(date1,date2): 计算两个日期之间的间隔天数
- unix_timestamp(date): 返回 date的UNIX 时间戳
- form_unixtime(unix): 返回 unix 时间戳的日期值
- date_format(date,format): 日期格式化,按format 格式化 date 值
- str to date(date,format): 将字符串转换成 date 类型
【注】使用mysql内置函数用select
聚合函数(分组函数)
将查询出的结果做聚合运算
- avg(expression):返回某列的平均值
- sum(expression):返回某列值的和
- count(expression): 返回某列的行数
- max(expression):返回某列的最大值
- min(expression):返回某列的最小值
【注】count求个数去除空格不会去0
count(*)/count(1)底层实现差不多,比带条件查询的查询效率高
【注】聚合函数会自动忽略空值,不需要手动增加条件排除空
聚合函数不能作为where子句后的限制条件(因为数据还未取出,无法执行聚合函数)
流程函数(if/ifnull)
- if(value,t,f): 如果 value 为真返回t,否则返回 f
- ifnull(column,value): 如果 column 为空返回 value,否则返回 column
在SQL语句当中若有 NULL 值参与数学运算,计算结果一定是 NULL,为了防止计算结果出现NULL,建议先使用ifnull 空值外理函数预先处理
分组查询
创建分组(group by)
group by 子句:根据一个或多个字段对结果集进行分组,在分组的字段上可以使用count、sum、avg 等函数
select 字段1[字段2,function(字段1),function(字段2)...]
from 表
group by 字段1;
【注】
- 如果分组列中具有 NULL 值,则 NULL 将作为一个分组返回(不需要的话手动过滤掉)。如果列中有多行 NULL值,它们将分为一组
- group by 子句必须出现在 where 子句之后,order by 子句之前
过滤分组(分组后过滤条件)
having 子句: having 非常类似于 where。唯一的差别是 where 过滤行,而 having 过滤分组。having 必须和 group by 一起使用。
having 和 where的区别也可以理解为,where 是分组前过滤,having 是分组后过滤
select顺序
书写顺序
执行顺序
from开始将表中的所有数据加载到内存,在where过滤 再分组,然后执行聚合函数运算,having,select->distinct...排序->限制数量
正则表达式
regexp操作符,regexp 操作符后面跟的就是正则表达式,正则表达式的作用是匹配文本,将一个模式(正则表达式)与一个文本串进行比较。
【问】like 和regexp 的区别
like是全字符串匹配,like 匹配整个列,如果被匹配的文本仅在列值中出现(没有配合其他通配符),like将不会找到它。regexp 在列值内进行匹配,如果被匹配的文本在列值中出现,regexp 将会找到它,相应的行将被返回。
匹配单个实例
匹配多个实例
- 常用元字符
- .:匹配任意单个字符。
- ^:匹配字符串的开始。
- $:匹配字符串的结束。
- 重复元字符
都是修饰前面的字符,放在要修饰字符的右面
- *:匹配0个或多个前面的元素。
- +:匹配1个或多个前面的元素。
- ?:匹配0个或1个前面的元素。
- [abc]:匹配字符集中的任意一个字符。
- [^abc]:匹配除了字符集中的任意一个字符以外的字符。
- [a-z]:匹配范围内的任意一个小写字母。
- {n}:指定数目的匹配
- {n,}:不少于指定数目的匹配
- {n,m}:匹配数目的范围(m不超过255)
【注】^字符--必须以该字符开头
^ 字符¥--字符串从头到尾完全匹配
多表查询
连接查询
多表连接查询就是从多个表中获取数据,若两张表进行连接查询的时候没有任何条件限制,最终的查询结果总数是两张表记录的乘积,该现象称为笛卡儿积现象。
#展示员工信息和所在部门的信息,56 条: 14x4 (笛卡儿积)
select t from emp,dept;
内连接
根据连接条件从多个表中查询选择数据,显示这些表中与连接条件相匹配的行,组合成新的记录。
分类:
-
- 等值连接:连接条件为相等判断的
- 非等值连接:连接条件不为相等判断的
- 自连接:在一个连接查询中,涉及的两个表都是同一张表的查询,自连接是一种特殊的连接查询,它指相互连接的表在物理上为同一张表,在逻辑上分为两张表,自连接时必须取别名
--等值连接
select * from emp join dept
on emp.deptno=dept.empno
select * from emp,dept
where emp.deptno=dept.empno;
--非等值连接:查询员工薪水等级
select e.ename,e.sal,s.grade
from emp e join salgrade s
on e.sal between s.losal and s.hisal;
--自连接:显示所有的员工以及员工对应的领导名
select e1.ename,e2.ename mgr_name
from emp e1 join emp e2
on e1.mgr=e2.empno;
外连接
A表和B 表能够完全匹配的记录查询出来之外,将其中一张表的记录无条件的完全查询出来,对方表没有匹配的记录时,会自动模拟出 null 值与之匹配。
【注】:外连接的查询结果条数>=内连接的查询结果条数
分类:
-
- 左外连接 关键字 left join 查询结果显示左表全部记录,右表显示满足条件的
- 右外连接 关键字 right join 查询结果显示右表全部记录,左表显示满足条件的
右外连接:查询员工所对应的部门名称,要求显示所有员工:
select d.dname,e.ename
from emp e right join dept d
on e.deptno=d.deptno;
左外连接:查询员工所对应的部门名称,要求显示所有部门:
select e.ename,d.dname
from dept d left join emp e
on e.deptno=d.deptno;
子查询
- 子查询,嵌套在其他 SQL 语句内的查询语句,且必须出现在圆括号内 (查询一般指select 语句)
- 子查询的结果可以作为外层查询的过滤条件或计算字段
- 子查询聚合函数要起别名
使用子查询过滤
子查询可与[not]in,>,<,=,!=,all,any,som结合使用
select 字段1[,字段 2...]
from 表1
where 字段1 [not] in (select 字段1 from 表2 where 条件);
all、any、some用于条件比较
all:表示与子查询结果集中的所有值进行比较,需要满足条件的所有值
any、some:这两个关键字含义相同,与子查询结果集中的中值比较,有任意一个满足条件即可
计算字段作为子查询
计算字段是指SELECT语句中的经过特定运算得到的结果,
例如使用算术运算符、字符串连接运算符和聚合函数等。
【注】计算字段本身是一个查询语句,因此在使用计算字段作为子查询时,需要在该计算字段所在的语句中使用别名来引用它。
select 字段1,字段2,...,
(select 聚合函数 from 表2 [where 表2.字段1=表1.字段1])
from 表1 [where 条件];
实例
# 1. 查询每个课程对应的授课教师信息
select c.name ***ame,t.name tname
from course c join teacher t
on c.teacher_id=t.id;
#2. 查询每个学生每科的成绩,显示学生名、课程名、分数
select stu.name sname,c.name ***ame,s.degree
from score s join student stu on s.student_id=stu.id
join course c on c.id=s.course_id
#3. 查询每科平均成绩情况,显示授课老师名、课程名、课程平均分数
select t.name,c.name,avg(degree)
from course c join teacher t on c.teacher_id=t.id
right join score s on s.course_id=c.id
group by c.name;
#4. 查询高于每科平均成绩的成绩信息
select s.*
from score s
join course c on c.id=s.course_id
where s.degree>(select avg(degree) from score where s.course_id=c.id)
#5. 查询所有姓李的学生的成绩
select stu.name,c.name,s.degree
from score s join student stu on s.student_id=stu.id
join course c on c.id=s.course_id
where stu.name like'李%';
select完整书写格式
- 7.select 字段名 对临时表进行整列读取
- 8.distinct 字段名 去除重复数据
- 1.from 表名 将硬盘上的表文件加载到内存
- 3. join 表名 连接外表
- 2.on 条件 对主表进行过滤
- 4.where ... 将符合条件的数据行摘取生成一张新的临时表
- 5.group by ... 根据列中的数据种类,将当前临时表划分成若干个新的临时表
- 6.having ... 可以过滤掉 grou by生成的不符合条件的临时表
- 9.order by ... 对 select生成的临时表重新排序,生成新的临时表
- 10.limit ... 对最终生成的临时表数据行进行截取
以上关键字的顺序不能改变,严格遵守
存储过程和自定义函数
存储过程
定义:存储过程,一组预编译的SQL语句和流程控制语句,被命名并存储在数据库中。
作用:是用来封装复杂数据库操作逻辑,并在有需要时调用
【注】存储过程外定义需要@开头 存储过程中定义个名字即可
创建过程声明变量用declare 如果想给初值用default
使用存储过程
#创建存储过程
create proceture 存储过程名()
begin
--存储过程的逻辑代码
--可以包含 SQL 语句、控制结构和变量操作等
end;
#执行存储过程
call 存储过程名();
#删除存储过程
drop procedure [if exists] 存储过程名;
使用参数
create procedure 存储过程名(
[in|out|inout] 参数名 1 参数的数据类型,
[in|out|inout] 参数名 2 参数的数据类型,
...
)
begin
--存储过程的逻辑代码
--可以包含 SOL 语句、控制结构和变量操作等
end;
create procedure mypro(out i int)
begin
select i;
set i=2;
select i;
end;
set @var=l;
call mypro(@var);
select ovar;
--不同参数类型的输出结果:
# in: 只读,不能修改 1 2 1
# out: 不可读,可以修改 null 2 2
# inout:可以读,可以修改 1 2 2
参数类型
- in (默认): 输入参数,存储过程的输入值,从外部传递给存储过程,存储过程内部是只读的,不能修改它的值
- out: 输出参数,存储过程的返回值,存储过程可以修改它的值并将其返回
- inout:输入和输出参数既可以作为输入值传递给存储过程,也可以由存储过程修改并返回
使用变量
#定义变量
declare 变量名 变量的数据类型 [default 默认值];
#变量赋值
set 变量名=要赋的值
逻辑语句
条件语句(if、case)
if condition then
逻辑代码;
[elseif condition then
逻辑代码;]
[else
逻辑代码;]
end if;
case
when condition1 then
逻辑代码
when condition2 then
逻辑代码
else
逻辑代码
end case;a
循环语句(while、repeat)
while 循环条件 do
逻辑代码
end while;
repeat
逻辑代码
until condition end repeat;
计算前n项的和
--方法一
create procedure sumn(in n int)
begin
declare res int default 0;
while n do
set res=res+n;
set n=n-1;
end while;
select res;
end;
call sumn(100);
--方法二
create procedure sum1(in n int)
begin
declare res int default 0;
repeat
set res=res+n;
set n=n-1;
until n=0
end repeat;
select res;
end;
call sum1(100);
特点
优点:
-
- 代码复用:存储过程可以被多个应用程序或脚本调用,实现了代码的复用
- 提高性能: MySQL将编译后的存储过程放入缓存中。如果应用程序在单个连接中多次使用存储过程,直接使用编译版本。
- 减少网络流量:存储过程可以一次执行多条 SQL 语,减少了与数据库的交互次数
- 安全控制:存储过程可以对数据库中的数据进行严格的访问控制和权限管理
- 数据一致性:存储过程可以实现复杂的数据操作和事务处理,确保数据的一致性和完整性。
缺点:
-
- 创建和维护成本高: SQL是一种结构化查询语言,难以处理复杂的业务逻辑
- 开发调试复杂:需要通过特定的工具和技术进行,不方便调式
- 可移植性差:存储过程通常依赖于特定的数据库平台和版本,不同的数据库系统之间存储过程的语法和特性可能有差异,导致存储过程的可移植性较差
自定义函数
function,可以使用自定义函数来扩展数据库的功能
#创建函数
create function 函数名([参数1 数据类型,[参数2 数据类型,...]])
returns 返回值类型
begin
函数逻辑代码
end;
#调用函数
select 函数名([参数 1,参数2...]);
#删除函数
drop function [if exists] 函数名;
判断账号密码
创建函数:实现检验登录的账号密码是否正常
1.创建账号信息表: (id,name,password) 并存入数据
2.创建一个函数:传入两个参数,用户名和密码
3.函数返回结果:(1)用户不存在 (2)密码错误 (3)登录成功
/*建表*/
create table if not exists user_info(
id int primary key auto_increment,
name varchar(50) not null,
password varchar(50) not null
);
insert into user_info(name,password) values
('ly','123'),
('zhangsan','456'),
('lisi','0000');
/*函数*/
create function login (uname varchar(50), upass varchar(50))
returns varchar(20)
begin
declare res int default 0;
-- 判断用户是否存在
select count(*) into res from user_info where name = uname;
if res = 0 then
return '用户不存在';
end if;
-- 判断密码是否正确
select count(*) into res from user_info where name = uname and password = upass;
if res = 0 then
return '密码错误';
end if;
-- 登录成功
return '登录成功';
end;
select login('ly','1234');
游标
cursor,使用游标可以对存储过程或函数中的查询结果进行逐行处理。
创建游标后,可以使用open 语句打开游标,开始执行游标指定的查询语句并生成结果集。在游标打开得到结果集后,可以使用 fetch 语句访问它的每一行。
游标处理完成后,应关闭游标,释放游标使用的内存和资源。
#创建游标
declare 游标名 cursor for 查询语句;
#打开游标
open 游标名;
#读取游标数据到变量中
fetch 游标名 into 变量名 1,[变量名2...];
#关闭游标
close 游标名;
检索单行数据
导致代码冗余
#创建存储过程
drop procedure if exists testCursor;
create procedure testCursor()
begin
#声明变量
declare emp_name varchar(20);
#声明游标,查询 emp 表中的 ename
declare mycursor cursor
for
select ename from emp;
#打开游标
open mycursor;
#多次读取游标数据
fetch mycursor into emp_name ;
select emp_name;
fetch mycursor into emp_name;
select emp_name;
#关闭游标
close mycursor;
end;
call testCursor;
循环检索数据
容易导致not fund异常 空查询(查询结果没有循环次数多)
#创建存储过程
drop procedure if exists testCursor;
create procedure testCursor()
begin
#声明变量
declare emp_name varchar(20);
declare i int default 0;
#声明游标,查询 emp 表中的 ename
declare mycursor cursor
for
select ename from emp;
#打开游标
open mycursor;
#多次读取游标数据
while i<20 do
fetch mycursor into emp_name ;
set i=i+1;
select emp_name;
#关闭游标
close mycursor;
end;
call testCursor;
上面循环的结果会多一条记录,即最后一条内容是重上一次的结果
循环检索数据需要设置结束条件: 声明一个结束标志位的变量,声明一个句柄(作用:抛出异常),当not found (sglstate02000)出现时,修改结束标志位
句柄内容:当没有数据时循环结束
句柄作用:-捕获报错
#创建存储过程
drop procedure if exists testCursor;
create procedure testCursor()
begin
#声明变量
declare emp_name varchar(20);
declare done int default 0;
#声明游标,查询 emp 表中的 ename
declare mycursor cursor
for
select ename from emp;
#声明句柄:not found 结束
declare continue hardler for not found set done=1;
#打开游标
open mycursor;
#多次读取游标数据
while done=0
do
fetch mycursor into emp_name ;
if done=0 then
select emp_name;
end if;
end while;
#关闭游标
close mycursor;
end;
call testCursor;
【注】:声明必须放在最上面 且声明顺序 变量 游标 句柄
练习(存储过程、游标、自定义函数)
- 创建一个存储过程,接受一个部门编号作为参数,使用游标遍历该部门的所有员工,并将员工的薪水增加100:
create procedure isid(in dept_id int)
begin
#声明变量
declare empno_val int;
declare sal_val int;
declare done int default 0;
#声明游标
declare mycursor cursor
for
select empno,sal from emp where deptno = dept_id;
#声明句柄
declare continue handler for not found set done = 1;
#打开游标
open mycursor;
#循环读取游标数据
while done=0
do
fetch mycursor into empno_val,sal_val;
if done=0 then
update emp set sal = sal_val + 100 where empno= empno_val;
end if;
end while;
#关闭游标
close mycursor;
end;
call isid(20);
2.创建一个自定义函数,接受一个生日日期为参数,计算年龄并返回
create function calculate_age(birthdate date)
returns int
begin
declare age int;
set age = year(curdate()) - year(birthdate);
if date_format(curdate(), '%m%d') < date_format(birthdate, '%m%d') then
set age = age - 1;
end if;
return age;
end;
索引
索引的作用是加快查找速度,引擎是B+树是基于二分查找实现的,可以得到一个二分搜索树。
index(索引),是存储引擎用于快速找到数据的一种数据结构
【面试题】索引的数据结构是什么?(一般默认是B+树)
MySQL默认的存储引擎是InnoDB(MySQL5..6版本以后)-->使用的数据结构也是B+树,该存储引警是最重要、使用最广泛的,除非有非常特别的原因需要使用其他存储引警,否则优先考虑InnoDB。
优点:
- 减少服务器需要扫描的数据量
- 帮助服务器避免排序和临时表
- 索引可以将随机I/O变为顺序I/O,提高查询性能
缺点:
- 从空间角度考虑,建立索引需要占用物理空间
- 从时间角度考虑,创建和维护索引都需要花费时间,例如对数据进行增删改的时候都需要维护索引。
常见索引类型
- 哈希索引: 基于哈希表实现,查找非常快,但不支持范围查找和排序操作(并非是不能,只是不能加快找),也不支持部分索引列的查找,只支持等值比较的查询。如果哈希冲突很多的话,索引的维护代价会很高。因此,哈希索引只适用某些特定场合(一般适用于查找要求很高,不需要范围和排序的要求)。在InnoDB 中,支持的哈希索引是自适应的(当认为你需要创建时会自动创建),不能人为创建。
- 全文索引: 用于全文搜索的索引类型,可以执行关键字搜索。全文索引有很多限制例如当数据量很大,内存无法装载全部索引时,搜索速度可能会非常慢。全文索引的维护成本也很大。MylSAM (InnoDB之前的版本)支持全文索引,InnoDB 从1.2 版本(MySQL5.6)开始支持全文索引。
- B+树索引: B+树索引就是传统意义上的索引,是目前关系型数据库中查找最为常用和最为有效的索引。B+树索引能够加快访问数据的速度,因为存储引擎不再需要进行全表扫描来获取需要的数据。B+树索引是顺序组织存储的,所以很适合查找范围数据,B+树索引分为聚簇索引(主键索引) 和非聚索(二级索引)。
B+树
二分查找法
从5、10、19、21、31、37、42、48、50、55 中查找48,使用二分查找法只需3次.如果顺序查找需要8次(每次查找首位数字的中间数)。
5 10 19 21 31 37 42 48 50 55
5 10 19 21 31 37 42 48 50 55
5 10 19 21 31 37 42 48 50 55
二叉树和平衡树
二又搜索树,左子节点小于父节点的值,右子节点大于父节点的值。如果我们需要查找8,需要 3次,而顺序查找需要 6 次。
同样是二叉搜索树,下图的情况查找效率会很低,从而引出平衡二叉树(AVL树),平衡二又树要求任何节点的子树高度最大差为 1。平衡性确保查找的速度可以很快,避免了二又搜索树的极端情况。
B树和B+树
平衡二叉树随着节点的增加,树的高度会越来越高,会增加磁盘的I/O次数,影响查询效率,
从而引出了B树,B树不限制一个节点只能由2个子节点,从而降低树的高度B树可以将节点的大小优化为磁盘块的大小,每次读取可以有效加载多个节点,B 树常用于数据库库等需要高效访问磁盘的场景。
B+树是对B树的升级,B+树只有叶子节点存数据,非叶子节点只存索引。叶子节点包含
所有索引,叶子节点构成一个有序链表,范围查找更快。由于非叶子节点只存索引,B+树比B
树的非叶子节点可以存更多索引,高度更低,磁盘I/O次数更少。
【面试题】B树和B+树的区别?
-
- 层级低,一半只有2/3层B+树只有叶子节点存数据,B+树相对于B树有冗余数据,因为冗余数据的维护成本低
- 数据是有序的可以变成双向链表,对于数据在排序和范围查找时效率会更快
使用索引
#创建索引
create index 索引名 on 表名(列名);
create index 索引名 on 表名(列名(前缀长度));
#查看索引
show index from 表名;
#删除索引
drop index 索引名 on 表名
#查看某个SQL语句是否使用索引
explain 查询语句
desc 查询语句
explain 各属性含义
- id:查询的序列号
- select_type: 查询的类型,主要是区别普通查询和联合查询、子查询之类的复杂查询
-
- SIMPLE:查询中不包含子查询或者 UNION
- PRIMARY:查询中包含任何复杂的子部分
- SUBQUERY:作为 SELECT或WHERE列表中的子查询
- table:输出的行所引用的表
- type:访问类型
-
- ALL: 扫描全表
- oindex:扫描全部索引树
- range:扫描部分索引,索引范围扫描,对索的扫描开始于某一点,返回匹配值域的行,常见于 between、<、>等的查询
- ref:使用非唯一索引或非唯一索引前缀进行的查找
- eq_ref: 唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描
- const,system:单表中最多有一个匹配行,查询起来非常迅速,例如根据主键或唯一索引查询。system 是const 类型的特例,当查询-的表只有一行的情况下,使用 system。
- NULL:不用访问表或者索引,直接就能得到结果,如 select 1 from test where 1
- key:显示MySQL实际决定使用的索引。如果没有索引被选择,是NULL
key_len:使用到索引字段的长度,key_len 显示的值为索引字段的最大可能长度,并非实际使用长度,即 key_len 是根据表定义计算而得,不是通过表内检索出的。
- ref:显示哪个字段或常数与key一起被使用
- rows:这个数表示MySQL 要遍历多少数据才能找到,表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数,在innodb 上可能是不准确的
- Extra: 执行情况的说明和描述。包含不适合在其他列中显示但十分重要的额外信息
-
- Using index:表示使用索引,如果只有 Using index,说明他没有查询到数据表,只用索引表就完成了这个查询,这个叫覆盖索引。
- Using where: 表示条件查询,如果不读取表的所有数据,或不是仅仅通过索引就可以获取所有需要的数据,则会出现 Using where。
- Using index condition: 索引条件下推 (Index Condition Pushdown,ICP)是MySQL使用索引的情况的优化。 简单来说,在服务器需要扫描表的情况下当没有ICP 时,存储引擎扫描可以明确地使用索引的条件,将符合条件的记录返回给服务器。当使用ICP 时,只要条件可以在索引上判断出来,就由存储引警在索引树上完成判断,再将符合条件的记录返回给服务器。 ICP 可以减少存储引擎必须访问基本表的次数以及服务器必须访问存储引警的次数,这是是否使用ICP的最可靠的判断条件
前缀索引
【问】为什么要选择前缀索引?
节省存储空间:前缀索引允许我们只对列值的一部分进行索引,而不是对整个列值进行索引。对于长字符串列或文本列,完整列值可能会很长,如果对整个列值进行索引,将会占用更多的存储空间。使用前缀索引可以减少索引的长度,从而节省存储空间。
提高索引效率:较短的索引长度可以减少索引的高度和宽度,从而提高索引的效率。更短的索引长度意味着在索引中可以容纳更多的索引项,减少了磁盘I/O和内存消耗。这对于大型表和频繁查询的表格特别有益。
索引开头的部分字符(取文章内容的前几个字符建索引),可以大大节约索引空间,提高索引效率。如 TEXT 数据类型必须使用前缀索引,因为 MySQL不允许索引这些列的完整长度。InnoDB 索引最大长度为767字节。
但这样会降低索引的选择性 (不重复的索引值与总行数的比值),唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。所以要选择足够长的前缀保证较高的选择性同时又不能太长(节约空间)。
可以统计最常见值的数量,再统计前缀的数量,不断调整前缀长度,当两个结果集的数量接近时,就得到比较合适的前缀长度了。
#前5个最常见值的数量
select count(*) as ***t,name
from abc
group by name
order by ***t desc imit 5;
#前5个最常见值前3个字符的数量
select count(*) as ***t, left(name,3) as pre_name
from abc
group by pre_name
order by ***t desc limit 5;
聚簇索引
聚簇索引,一种数据存储方式,将数据放在索引的叶子页,索引和数据在同一个B+树上。因为无法同时把数据放在两个地方,所以一个表一定有且只有一个聚簇索引。
在InnoDB中,这个索引是主键,如果没有定义主键,InnoDB 会选择一个唯一的非空索引代替,也没有的话nnoDB会隐式定义一个主键作为聚簇索引。
面试突击56:聚簇索引和非聚簇索引有什么区别?
使用聚簇索引的优缺点
优点:
-
- 可以把相关数据保存在一起
- 数据访问更快,索引和数据在同一个结构中
缺点
-
- 插入速度严重依赖插入顺序(采用自增主键)
- 更新聚簇索引列的代价很高
- 插入数据或更新主键时可能面临“页分裂”问题(存储的每一个正好是在一个磁盘块)。当主键值要求必须将这一行插入到某个已满的页中时,存储引警会将该页分裂成两个页面来放该行,也分裂会导致表占用更多的磁盘空间
- 非聚簇索引需要两次索引查找
覆盖索引
查询所需要的数据都可以从索引中获取,而不用再去查询数据表中的实际数据,那么这个索引就是一个覆盖索引。
覆盖索引可以减少树的搜索次数,避免了回表,显著提升了查询性能
【问】什么是覆盖索引?
覆盖索引是非聚簇索引不需要回表的一种现象,即从非聚簇索引中就能查到的记录,而不需要查询主键索引中的记录,避免了回表的产生减少了树的搜索次数,可以显著提升性能。
【问】为什么主键不能修改
一旦修改,B+树要重新调整,更修代价太高
【注】联合索引也可以发生索引覆盖
【注】使用索引查询效率会变快时才会使用如果没变快不会使用
#primary
create index ename_index on emp(ename)
create index sal_index on emp(sal);
#没用索引--索引失效
explain select * from emp;
#使用索引ename_index
explain select ename from emp;
#使用索引sal_index(当三个索引里都有empno时会使用数据结构最小的也就是最快的)
explain select empno from emp;
#使用主键索引(虽然sal_index中也存了但是不是按顺序存的通过where条件找不到)
explain select empno from emp where empno=7396;
#使用索引ename_index
explain select empno from emp where ename=' smith';
#使用主键索引
explain select ename from emp where empno=7396;
#使用索引ename_index
explain select * from emp where job='clerk' and ename='smith';
#不使用索引
explain select empno from emp where empno=7396 or ename=' smith';
#使用索引 ename_index sal_index
explain select empno from emp where sal=900 or ename='smith';
#使用索引sal_index 主键 (因为sal>5000是范围查找)
explain select empno from emo where empno=7396 or sal > 5000;
#不使用索引 不符合条件的占绝大多数,相当于全表查询
explain select * from emo where sal != 5000;
【问】索引失效情况?
数据量非常小
where条件or中有一个不是索引
where条件不等于
where条件模糊查询开头是模糊时
where条件涉及数学运算/函数/隐式转换
最左前缀原则
创建多列的联合索引时,满足最左前缀原则。例如创建 (a,b,c) 三列的索引,实际上相当于创建 a、(a,b)、(a,b,c) 三个索引。
当不需要考虑排序和分组时,将选择性最高的列放在前面。这时索引的作用只用于优化where 条件的查找,这样设计可以最快过滤需要的行。
create index ename_job_sal on emp(ename,ob,sal)
explain select * from emp where ename='smith';
explain select * from emp where job='clerk';
explain select * from emp where ename='smith' and job='clerk';
explain select * from emp where job='clerk' and sal=5000:
explain select * from emp where ename='smith' and sal=5000;//索下
索引设计原则
- 为常作为查询条件的字段建立索引: 如果某个字段经常用来做查询条件,那么该字段的查询速度会影响整个表的查询速度。因此,为这样的字段建立索引,可以提高整个表的查询速度。
- 为经常需要排序、分组和联合操作的字段建立索引: 经常需要 ORDER BY、GROUPBY、DISTINCT和UNION 等操作的字段,排序操作会浪费很多时间。如果为其建立索引,可以有效地避免排序操作。
- 创建唯一性索引:唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录
- 限制索引的数目:每个索引都需要占用用磁盘空间,索引越多,需要的磁盘空间就越大,修改表时,对索引的重构和更新很麻烦
- 小表不建议索引(如数量级在百万以内): 由于数据较小,查询花费的时间可能比遍历索引的时间还要短,索引可能不会产生优化效果
- 尽量使用前缀索引: 如果索引的值很长,那么查询的速度会受到影响。
- 删除不再使用或者很少使用的索引。
索引使用策略
- 独立的列:索引使用不当会导致索引失效(查询中实际没有使用索引)。如果查询中的列不是独立的,MySQL不会使用索引。独立的列指查询时索引列不能是表达式的一部分,也不能是函数的参数,这两种情况都会导致索引失效
- 使用前缀索引:使用前缀索引可以节约索引空间,从而提高索引效率,但是需要平衡索引的选择性
- 使用联合索引:使用联合索引可以避免回表,实现覆盖索引,可以减少大量I/O操作
- 合适的索引列顺序:创建联合索引时,不同的列顺序会影响索引的性能,通常将选择性高的列放在最前面
- 合适的主键:最好选择不会修改的列作为主键,不考虑分库分表的情况最好使用自增主键
触发器和试图
触发器
trigger,在表的插入 (insert)、更新 (update)、删除 (delete) 操作发生时自动执行MySQL 语句
创建触发器时需要给出的信息:
- 触发器名
- 在操作前还是操作后触发(before/after)
- 被什么操作触发(insert/update/delete)
- 关联的表
使用触发操作的数据:
- insert: 可通过 new 访问被插入的行,before insert 可以更新 new 中的值 (允许更改被插入的值)。
- delete:可通过 old 访问被删除的行,old 中的值是只读的。
- update: old 访问更新前的值,new 访问更新后的值
#创触发器
create trigger 触发器名 before|after insert|delete|update on 表名
for each row#每一行的操作都会触发
begin
#触发器逻辑
end;
#更新一个部门信息,对应这个部门的员工涨工资
create trigger mytrigger after update on dept
for each row
begin
update emp
set sal=sal+100
where deptno=new.deptno;#new指的是触发器的表
end;
update dept
set loc='BEIJING'
where loc='DALLAS';
练习
#给部门表添加员工数量字段,员工表添加员工时对应部门员工数量自动加1
alter table dept
add emp_num int default 0;
update dept
set emp_num=(select count(*) from emp where emp.deptno=dept.deptno);
create trigger mytrigger after insert on emp
for each row
begin
update dept
set emp_num=emp_num+1
where dept.deptno=new.deptno;
end;
insert into emp(empno,deptno) values(123,1);
视图
view,由查询结果形成的一个虚拟的表。视图不能索引,也不能有关联的触发器
create view 视图名 as 查询语句;
#修改视图
alter view 视图名 as 查询语句;
#删除视图
drop view [if exists] 视图名;
#查看创建视图语句
show create view 视图名;
#6.查询出员工的部门名称,员工的领导名称和薪水等级
create view emp all as
select el.ename,e2.ename mgr name,d.dname,s.grade
from emp e1
left join emp e2 on e1.mgr=e2.empno
join dept d on el.deptno=d.deptno
join salgrade s on el.sal between losal and hisal;
select * from emp all;
作用:
- 重用SQL 语句
- 简化查询语句,隐藏复杂的 SQL
- 安全:使用视图的用户只能访问他们被允许查询的结果集
视图的实现原理:
- 临时表算法:将视图的查询结果存放到临时表里,需要访问视图时,直接访问这个临时表,优点是可以处理复杂擦好像,缺点是引入了创建表的性能开销
- 合并算法:重写含有视图的查询,将视图的定义sl直接合并到查询 sql里,性能更高。
MySQL优化器根据查询的具体情况来选择使用哪种算法。如果视图中包含 GROUY BY、DISTINCT、聚合函数等,只要无法在原表记录和视图记录中建立一一映射的场景中MySQL都将使用临时表算法来实现视图。
可更新视图:可以通过更新这个视图来更新视图涉及的相关表。只要指定了合适的条件,就可以更新、删除甚至向视图中写入数据。
#合并算法
create view abc as select * from emp;
explain select * from abc;
#临时表算法
create view dept emp as select * from emp group by deptno;
explain select * from dept emp;
#可更新视图
insert into abc(empno) values (111);
update abc set empno=222 where empno=111;
insert into dept_emp(empno) values (111);
#不可更新视图
create view abc2 as select deptno from emp;
insert into abc2(deptno) values (111);
create view abc2 as select deptno *2 from emp;
update abc2 set deptno=222 where deptno is NULL;
两种算法
1.临时表--开销大,可以处理复杂sql语句
2.合并--性能好开销小,不能作用复杂sql
三范式
第一范式(1NF)
确保每列的原子性,表中的每一个字段都是不可分割的,同一列中不能有多个值。第一范式是对关系模式的基本要求,不满足第一范式的数据库不是关系型数据库。
- 不满足第一范式的示例:
注意:第一范式要根据实际需求来定
考虑地址字段(黑龙江省大庆市高兴区行知街135号)是否符合第一范式,如果经常访问地址中的城市部分就不符合第一范式需要对地址进行拆分。如果不会访问拆分的部分,就符合第一范式,拆分反而不利于查询完整地址。
第二范式(2NF)
确保唯一性和依赖性,每个表都有主键,且其他字段完全依赖主键
第二范式是在第一范式的基础上,要求表中的每一条数据可以被唯一区分,通常使用主键
实现,其他所有字段都完全依赖主键。
其他字段依赖主键是指,其他每个字段都与主键完全相关,当确定主键的值时就能确定其他所有字段的值。也就是说一个表只能存一种数据,不可以把多种数据存到一个表中
完全依赖是指,联合主键时,其他字段不可以只依赖主键中的某个字段,必须依赖联合主键中的每一个字段。
- 不满足第二范式的示例:
第三范式(3NF)
在第二范式的基础上,非主键字段必须直接依赖于主键,不能存在传递依赖
- 不满足第三范式的示例:
范式总结
- 1NF: 每列原子性,字段不可分割
- 2NF:唯一性和依赖性,要有主键,且其他字段完全依赖主键
- 3NF:没有传递依赖
范式的优缺点
- 优点:
-
- 重复数据很少或者没有
- 表更小,可以更好的放在内存里,执行操作更快
- 更新操作更快
- 可以更少使用group by和distinct
- 缺点:复杂一点的查询需要关联,可能使索引无效
事务和锁
事务
transaction,一组原子性的 SQL 查询,或者说是一个独立的工作单元。如果能够成功执行这组查询的全部语句,就会执行这组查询;如果其中任何一条语句无法成功执行,那么这组查询的所有语句都不会执行。
也就是说,事务内部的语句,要么全部执行成功,要么全部执行失败。
使用事务
- 开始标志:任何一条DML 语句的执行
- 结束标志:
-
- 提交:成功的结束,将所有的 DML 语句操作记录和底层硬盘文件中数据进行同步
- 回滚:失败的结束:将所有 DML 语操作记录全部清除
MySQL默认是自动提交
#开启事务
start transaction;
#结束事务
end transaction
#提交事务
***mit;
#保存回滚点
savepoint 回滚点名
#回滚事务
rollback[ to 回滚点名];
#查看事务的提交方式
show variables like 'auto***mit';
#启用和关闭自动提交模式
set auto***mited=1;
set auto***mited=0
举例:转账业务
- 创建表并插入数据
drop table if exists a***ount;
create table a***ount
(
id int primary key auto increment,
name varchar(20),
money int
);
insert into a***ount (name,money)
values ('lz',2000)('xz',500);
- 自动提交和手动提交,再开一个MySQL-Front窗口查看数据变化
#执行语句,再打开一个 MysQL-Eront 窗口,查询是否有变化
update a***ount set money=money-1000 where name="lz";
update a***ount set money=money+1000 where name="xz";
select * from a***ount;
#查看提交方式,改为手动提交
show variables like 'auto***mit';
set auto***mit=off;
#手动提交
***mit;
四个特性
ACID:
- 原子性(atomicity): 事务是最小工作单元,不可再分,事务的所有操作要么全部提交成功,要么全部失败回滚,不可能只执行其中的一部分操作。
- 一致性 (consistency): 数据库总是从一个一致的状态转换成另一个一致状态,不会因为某条语句失败而出现其他状态。
- 隔离性(isolation): 常来说,一个事务所做的修改在最终提交前,对其他事务是不可见的。
- 持久性(durability): 事务一旦提交,其做的修改会持久的保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。
一个兼容ACID的数据库系统很多复杂但可能用户并没有觉察到的工作。相比没有实现ACID的数据库,通常会需要更强的CPU处理能力、更大的内存和更多的磁盘空间。
并行事务带来的问题
数据一致性问题:
- 脏读(dirty read):一个事务读到另一个事务未提交的数据就叫做脏读。一个事务对条记录做修改,在这个事务提交前是有可能随时回滚的,这些数据就是脏数据
- 不可重复读(non-repeatable read): 在一个事务内多次读取同一条记录,前后结果不一样就叫做不可重复读。事务 A多次查询同一行数据,在多次查询中间,事务B对该行数据进行了修改,事务 A多次查询同一行的数据就会不一致
- 幻读(phantom read): 在一个事务内按相同条件多次查询,前后结果集的数量不同就叫做幻读。事务A按id>5的条件进行多次查询,在多次查询中间,事务 B 插入了一条id 为6的数据,事务A多次查询的结果集数量就会不同
影响:
- 脏读:读到其他事务未提交的数据
- 不可重复读:前后读取数据不一致
- 幻读:前后读取的结果集数量不同
隔离级别
在SQL标准中定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发系统的开销也更低。
- READ UN***MITTED (未提交读): 事务中的修改,没有提交也能被其他事务看到。这个级别会导致很多问题,性能也没有好太多,一般很少使用。
- READ ***MITTED (提交读): 事务中的修改,提交后才能被其他事务看到。有时也叫不可重复读,因为两次执行同样的查询,可能得到不同的结果。
- REPEATABLEREAD(可重复读):同一事务中多次读取同样记录的结果是一致的。该级别无法解决幻读问题。该级别是 MySQL 的默认隔离级别,并且 MySQL 在该级别就可以很大程度解决幻读问题。
- SERIALIZABLE(可串行化): 强制事务串行执行,避免幻读问题。该级别会在读取的每一行数据上都加锁,可能导致大量的超时和锁争用的问题。实际很少用这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑采用该级别。
查看隔离级别:
#查看会话级的当前隔离级别: (会话级只对当前窗口有效)
select @@tx_isolation;
select @@session.tx_isolation;
#查看全局级的当前隔离级别:
select @@global.tx_isolation;
设置隔离级别:
- 方法1: my.ini 文件中的[mysqld] 下面添加transaction-isolation=隔离级别
- 方法2:通过命令
#设置全局或当前会话范围
set global transaction isolation level 隔离级别;
setsession transaction isolation level 隔离级别;
不同隔离级别下a1、a2、a3的值为多少?
- 未提交读:2、2、2
- 提交读: 1、2、2
- 可重复读: 1、1、2
- 串行化: 1、1、2
实现隔离级别的两种方法:
- 加锁:读数据前对其加锁,阻止其他事务对数据修改
- 快照:读取数据的快照(之前的版本)实现,例如,可重复读级别在整个事务期间都读取事务开始时的快照去解决不可重复读问题
InnoDB 中的锁
共享锁和排他锁
- 共享锁 (S Lock): 读锁,允许事务读取数据
- 排他锁 (X Lock):也叫独占锁,写锁,允许事务删除或更新数据
如果事务获取了某个数据的共享锁,其他事务可以立即获得该数据的共享锁,这种情况叫锁兼容。如果事务获取了某个数据的共享锁或排他锁,其他事务想要获取该数据的排他锁,必须要等到该行的锁释放掉。
排他锁和共享锁的兼容性
意向锁
InnoDB允许事务在行锁和表锁同时存在。为支持在不同粒度上进行加锁,InnoDB 支持意向锁。
意向锁,将锁定的对象分为多个层级,意向锁意味着事务有意向在更细粒度上加锁。如果需要对行加锁,需要先对表加意向锁再对行加锁
意向锁在InnoDB中就是表级别的锁,支持两种意向锁:
- 意向共享锁(IS Lock),事务有意向对表中某些行加共享锁
- 意向排他锁(IX Lock),事务有意向对表中某些行加排他锁
表级锁的兼容性
【注】意向锁不会和行级锁冲突,意向锁之间也不会冲突,意向锁只会和共享表锁和排他表锁
冲突
意向锁的作用: 如果没有意向锁,想要给一个表加表锁必须要检查该表是否有表锁和每行是否有锁。而如果在加行锁前给这个表加上了意向锁,这时只需要检查表锁和意向锁就可以了,不需要检查每一行的锁。
致性非锁定读
一致性非锁定读,读取正在执行 delete 和 update 操作的行时,不会等待该行上锁的释放,而是读取该行的一个快照数据(该行之前的版本)。非锁定读极大的提高了数据库的并发性,InnoDB 默认时这种读取方式,也就是说默认普通的 selet 语句是不会加锁的,而是通过读取快照实现数据一致性
上面提到的快照数据就是该行数据的历史版本,由此带来的并发控制称为多版本并发控制(MV***)。
READ ***MITTED (提交读)和 REPEATABLE READ (可重复读)在 nnoDB 中使用的是一致性非锁定读,但是读取的快照不同。提交读级别读取的是最新版本的快照,可重复读级别读取的是事务开始时数据的快照。
可重复读级别通过读快照,可以解决前面提到的幻读问题,但是有些情况需要锁定读.
一致性锁定读
InnoDB 默认使用一致性非锁定读。某些情况用户需要显式加锁保证数据的一致性,支持两种一致性锁定读的操作
- select ... for update: 对读取的行加一个排他锁
- select ...lock in share mode: 对读取的行加一个共享锁
如果只对读取的行加锁会有幻读问题:
锁定读时使用键值间隙锁 (Next-Key Lock),就是行锁加间歇锁,可以解决幻读问题。
- Record Lock: 单行记录的锁。
- Gap Lock:间歇锁,锁定一个范围,但不包含记录本身。
- Next-Key Lock: Gap Lock + Record Lock,锁定一个范围,并锁定记录本身。
非锁定读使用快照和锁定读使用间歇锁可以基本解决幻读问题,但是极特殊情况还是有可能发生幻读。