目录
基本知识
主流数据库
数据库基本概念
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 中的锁
共享锁和排他锁
意向锁
致性非锁定读
一致性锁定读
关系型与非关系型的区别:
net start mysql57
(1)显示所有已经存在的数据库
show databases;
(2)显示创建某个特定数据库的SQL语句
show create database 数据库名;
运行结果解读:
编辑
create database [if not exists] 数据库名;/* [] 中的内容是可加可不加*/
drop database [if exists ] 数据库名;
use 数据库名;
#查询函数时用select
select database();/*查询当前连接的数据库*/
select version();/*查询数据库版本信息*/
select now();/*查询当前时间*/
select user();/*查询当前用户*/
show tables;
【注】
source SQL脚本的文件路径
source C:\Users\Administrator\Desktop\comment.sql
desc 表名;/*describe的缩写*/
show create table 表名;
select 字段1,字段2... from 表名;
select sal from emp;
select ename,sal from emp;
select * from 表名;/*查询表中所有信息*/
/*从某个表查询所有满足where条件的信息*/
select * from 表名 where 条件;
1、数值比较/字符比较
2、逻辑操作
3、范围内比较
4、匹配空、非空(只能用is 和 is not)
5、模糊比较
【注】
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.查询没有佣金 comm的员工编号、领导编号、薪水、佣金
select empno,mgr,sal,comm from emp where comm is not null or comm=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查找起来更快
=查询号时都会忽略空格,查询还会显示空格,不会去掉
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...];
练习学生信息表的创建与修改
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 学生;
insert into [字段1,2..]表名
values(字段1的值,2);/*字符串要加引号*/
【注意】插入多条数据比多条insert数据快
update 表名
set (字段1=字段1的值 ,字段2 =字段2的值)where 限制条件;
【注意】要加where限制条件否则会更新整个表的数据
delete from 表名
where 限制条件;
【注意】没有* 加where条件
【注】在使用更新和删除操作时要谨慎,先使用查询语句测试
constraint+名字 约束类型
(1)创建表时设置
create table 表名(字段 类型 not null);
(2)已有字段添加
alter table 表名 modify 字段 类型 not null;
--删除非空约束直接删除 not null 在运行一遍
create table 表名(字段 类型 default 默认值);
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 约束名;
每个表都应该有一个主键
主键值不能修改
不使用可能会修改的值的列作为主键通过使用id作为主键,因其与业务无关
特点:
表级
create table 表名(
字段 类型 primary key,
...
);
联合主键约束
联合主键包含的字段名都不能为空
create table 表名(
字段 类型,
...
[constraint 约束名] primary key(字段1[字段2,...])/*联合主键约束*/
);
删除主键
alter table 表名 drop primary key;
【注】删除主键不需要给名,因为主键只有一个
auto_increment:设置auto_increment的列增加记录时会实现自动增加(手动插入含有设为自动递增列的字段除外),每个表只允许存在一个递增列。
【注】若手动插入一条记录,自动递增是从最大值开始递增
create table 表名(
字段 类型 auto_increment;
);
当创建外键约束后,就出现的父表和子表
【注】子表外键类型要与父表类型一
外键约束后该字段列可以为空,外键为空的数据称孤儿数据
--语法
[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)
);
asc--升序排序(不写时默认升序)desc--默认升序
order by 字段1[字段2] [asc|desc]
select * from emp order by sal desc;
【注】查询默认所有不是按主键排序的,无固定排序规则
使用限制语句可限制查询出的行数,两个参数可限制从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
放在字段前
用于返回唯一不同的值
查询多个字段时,字段都重复才会去掉,一个重复不会去掉。
#列出所有岗位,先查询再去重
select distinct job from emp;
#同时作用两列,不能查询目标列以外的列
select distinct job,mgr from emp;
查询结果求并集
执行多个查询(多条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内置函数用select
将查询出的结果做聚合运算
【注】count求个数去除空格不会去0
count(*)/count(1)底层实现差不多,比带条件查询的查询效率高
【注】聚合函数会自动忽略空值,不需要手动增加条件排除空
聚合函数不能作为where子句后的限制条件(因为数据还未取出,无法执行聚合函数)
在SQL语句当中若有 NULL 值参与数学运算,计算结果一定是 NULL,为了防止计算结果出现NULL,建议先使用ifnull 空值外理函数预先处理
group by 子句:根据一个或多个字段对结果集进行分组,在分组的字段上可以使用count、sum、avg 等函数
select 字段1[字段2,function(字段1),function(字段2)...]
from 表
group by 字段1;
【注】
having 子句: having 非常类似于 where。唯一的差别是 where 过滤行,而 having 过滤分组。having 必须和 group by 一起使用。
having 和 where的区别也可以理解为,where 是分组前过滤,having 是分组后过滤
书写顺序
执行顺序
from开始将表中的所有数据加载到内存,在where过滤 再分组,然后执行聚合函数运算,having,select->distinct...排序->限制数量
regexp操作符,regexp 操作符后面跟的就是正则表达式,正则表达式的作用是匹配文本,将一个模式(正则表达式)与一个文本串进行比较。
【问】like 和regexp 的区别
like是全字符串匹配,like 匹配整个列,如果被匹配的文本仅在列值中出现(没有配合其他通配符),like将不会找到它。regexp 在列值内进行匹配,如果被匹配的文本在列值中出现,regexp 将会找到它,相应的行将被返回。
都是修饰前面的字符,放在要修饰字符的右面
【注】^字符--必须以该字符开头
^ 字符¥--字符串从头到尾完全匹配
多表连接查询就是从多个表中获取数据,若两张表进行连接查询的时候没有任何条件限制,最终的查询结果总数是两张表记录的乘积,该现象称为笛卡儿积现象。
#展示员工信息和所在部门的信息,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 值与之匹配。
【注】:外连接的查询结果条数>=内连接的查询结果条数
分类:
右外连接:查询员工所对应的部门名称,要求显示所有员工:
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;
子查询可与[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 cname,t.name tname
from course c join teacher t
on c.teacher_id=t.id;
#2. 查询每个学生每科的成绩,显示学生名、课程名、分数
select stu.name sname,c.name cname,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'李%';
以上关键字的顺序不能改变,严格遵守
定义:存储过程,一组预编译的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
参数类型
#定义变量
declare 变量名 变量的数据类型 [default 默认值];
#变量赋值
set 变量名=要赋的值
if condition then
逻辑代码;
[elseif condition then
逻辑代码;]
[else
逻辑代码;]
end if;
case
when condition1 then
逻辑代码
when condition2 then
逻辑代码
else
逻辑代码
end case;a
while 循环条件 do
逻辑代码
end while;
repeat
逻辑代码
until condition end repeat;
--方法一
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);
优点:
缺点:
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;
【注】:声明必须放在最上面 且声明顺序 变量 游标 句柄
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。
优点:
缺点:
从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。平衡性确保查找的速度可以很快,避免了二又搜索树的极端情况。
平衡二叉树随着节点的增加,树的高度会越来越高,会增加磁盘的I/O次数,影响查询效率,
从而引出了B树,B树不限制一个节点只能由2个子节点,从而降低树的高度B树可以将节点的大小优化为磁盘块的大小,每次读取可以有效加载多个节点,B 树常用于数据库库等需要高效访问磁盘的场景。
B+树是对B树的升级,B+树只有叶子节点存数据,非叶子节点只存索引。叶子节点包含
所有索引,叶子节点构成一个有序链表,范围查找更快。由于非叶子节点只存索引,B+树比B
树的非叶子节点可以存更多索引,高度更低,磁盘I/O次数更少。
#创建索引
create index 索引名 on 表名(列名);
create index 索引名 on 表名(列名(前缀长度));
#查看索引
show index from 表名;
#删除索引
drop index 索引名 on 表名
#查看某个SQL语句是否使用索引
explain 查询语句
desc 查询语句
explain 各属性含义
key_len:使用到索引字段的长度,key_len 显示的值为索引字段的最大可能长度,并非实际使用长度,即 key_len 是根据表定义计算而得,不是通过表内检索出的。
【问】为什么要选择前缀索引?
节省存储空间:前缀索引允许我们只对列值的一部分进行索引,而不是对整个列值进行索引。对于长字符串列或文本列,完整列值可能会很长,如果对整个列值进行索引,将会占用更多的存储空间。使用前缀索引可以减少索引的长度,从而节省存储空间。
提高索引效率:较短的索引长度可以减少索引的高度和宽度,从而提高索引的效率。更短的索引长度意味着在索引中可以容纳更多的索引项,减少了磁盘I/O和内存消耗。这对于大型表和频繁查询的表格特别有益。
索引开头的部分字符(取文章内容的前几个字符建索引),可以大大节约索引空间,提高索引效率。如 TEXT 数据类型必须使用前缀索引,因为 MySQL不允许索引这些列的完整长度。InnoDB 索引最大长度为767字节。
但这样会降低索引的选择性 (不重复的索引值与总行数的比值),唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。所以要选择足够长的前缀保证较高的选择性同时又不能太长(节约空间)。
可以统计最常见值的数量,再统计前缀的数量,不断调整前缀长度,当两个结果集的数量接近时,就得到比较合适的前缀长度了。
#前5个最常见值的数量
select count(*) as cnt,name
from abc
group by name
order by cnt desc imit 5;
#前5个最常见值前3个字符的数量
select count(*) as cnt, left(name,3) as pre_name
from abc
group by pre_name
order by cnt 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;//索下
trigger,在表的插入 (insert)、更新 (update)、删除 (delete) 操作发生时自动执行MySQL 语句
创建触发器时需要给出的信息:
使用触发操作的数据:
#创触发器
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;
作用:
视图的实现原理:
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
确保每列的原子性,表中的每一个字段都是不可分割的,同一列中不能有多个值。第一范式是对关系模式的基本要求,不满足第一范式的数据库不是关系型数据库。
注意:第一范式要根据实际需求来定
考虑地址字段(黑龙江省大庆市高兴区行知街135号)是否符合第一范式,如果经常访问地址中的城市部分就不符合第一范式需要对地址进行拆分。如果不会访问拆分的部分,就符合第一范式,拆分反而不利于查询完整地址。
确保唯一性和依赖性,每个表都有主键,且其他字段完全依赖主键
第二范式是在第一范式的基础上,要求表中的每一条数据可以被唯一区分,通常使用主键
实现,其他所有字段都完全依赖主键。
其他字段依赖主键是指,其他每个字段都与主键完全相关,当确定主键的值时就能确定其他所有字段的值。也就是说一个表只能存一种数据,不可以把多种数据存到一个表中
完全依赖是指,联合主键时,其他字段不可以只依赖主键中的某个字段,必须依赖联合主键中的每一个字段。
在第二范式的基础上,非主键字段必须直接依赖于主键,不能存在传递依赖
范式的优缺点
transaction,一组原子性的 SQL 查询,或者说是一个独立的工作单元。如果能够成功执行这组查询的全部语句,就会执行这组查询;如果其中任何一条语句无法成功执行,那么这组查询的所有语句都不会执行。
也就是说,事务内部的语句,要么全部执行成功,要么全部执行失败。
MySQL默认是自动提交
#开启事务
start transaction;
#结束事务
end transaction
#提交事务
commit;
#保存回滚点
savepoint 回滚点名
#回滚事务
rollback[ to 回滚点名];
#查看事务的提交方式
show variables like 'autocommit';
#启用和关闭自动提交模式
set autocommited=1;
set autocommited=0
举例:转账业务
drop table if exists account;
create table account
(
id int primary key auto increment,
name varchar(20),
money int
);
insert into account (name,money)
values ('lz',2000)('xz',500);
#执行语句,再打开一个 MysQL-Eront 窗口,查询是否有变化
update account set money=money-1000 where name="lz";
update account set money=money+1000 where name="xz";
select * from account;
#查看提交方式,改为手动提交
show variables like 'autocommit';
set autocommit=off;
#手动提交
commit;
ACID:
一个兼容ACID的数据库系统很多复杂但可能用户并没有觉察到的工作。相比没有实现ACID的数据库,通常会需要更强的CPU处理能力、更大的内存和更多的磁盘空间。
数据一致性问题:
影响:
在SQL标准中定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发系统的开销也更低。
查看隔离级别:
#查看会话级的当前隔离级别: (会话级只对当前窗口有效)
select @@tx_isolation;
select @@session.tx_isolation;
#查看全局级的当前隔离级别:
select @@global.tx_isolation;
设置隔离级别:
#设置全局或当前会话范围
set global transaction isolation level 隔离级别;
setsession transaction isolation level 隔离级别;
不同隔离级别下a1、a2、a3的值为多少?
实现隔离级别的两种方法:
如果事务获取了某个数据的共享锁,其他事务可以立即获得该数据的共享锁,这种情况叫锁兼容。如果事务获取了某个数据的共享锁或排他锁,其他事务想要获取该数据的排他锁,必须要等到该行的锁释放掉。
排他锁和共享锁的兼容性
InnoDB允许事务在行锁和表锁同时存在。为支持在不同粒度上进行加锁,InnoDB 支持意向锁。
意向锁,将锁定的对象分为多个层级,意向锁意味着事务有意向在更细粒度上加锁。如果需要对行加锁,需要先对表加意向锁再对行加锁
意向锁在InnoDB中就是表级别的锁,支持两种意向锁:
表级锁的兼容性
【注】意向锁不会和行级锁冲突,意向锁之间也不会冲突,意向锁只会和共享表锁和排他表锁
冲突
意向锁的作用: 如果没有意向锁,想要给一个表加表锁必须要检查该表是否有表锁和每行是否有锁。而如果在加行锁前给这个表加上了意向锁,这时只需要检查表锁和意向锁就可以了,不需要检查每一行的锁。
一致性非锁定读,读取正在执行 delete 和 update 操作的行时,不会等待该行上锁的释放,而是读取该行的一个快照数据(该行之前的版本)。非锁定读极大的提高了数据库的并发性,InnoDB 默认时这种读取方式,也就是说默认普通的 selet 语句是不会加锁的,而是通过读取快照实现数据一致性
上面提到的快照数据就是该行数据的历史版本,由此带来的并发控制称为多版本并发控制(MVCC)。
READ COMMITTED (提交读)和 REPEATABLE READ (可重复读)在 nnoDB 中使用的是一致性非锁定读,但是读取的快照不同。提交读级别读取的是最新版本的快照,可重复读级别读取的是事务开始时数据的快照。
可重复读级别通过读快照,可以解决前面提到的幻读问题,但是有些情况需要锁定读.
InnoDB 默认使用一致性非锁定读。某些情况用户需要显式加锁保证数据的一致性,支持两种一致性锁定读的操作
如果只对读取的行加锁会有幻读问题:
锁定读时使用键值间隙锁 (Next-Key Lock),就是行锁加间歇锁,可以解决幻读问题。
非锁定读使用快照和锁定读使用间歇锁可以基本解决幻读问题,但是极特殊情况还是有可能发生幻读。