mysql -uroot -p654321
数据模型--->关系型数据库(概念:建立在关系型模型基础上,由多张相互连接的二维表组成的数据库.而所谓二维表,指的是由行和列组成的表,就类似于Excel表格数据,有表头、有列、有行,
还可以通过一列关联另外一个表格中的某一列数据.
总结:基于二维表存储数据的数据库就称为关系型数据库,不是基于二维表存储数据的数据库,就是非关系型数据库。
SQL--->结构化查询语言。是能操作关系型数据库的编程语言,定义了一套操作关系型数据库统一的标准
mysql里面的注释:单行注释:-- 注释内容 或 # 注释内容
多行注释:/* 注释内容 */
SQL分类
SQL语句:根据其功能主要分为四类:DDL、DML、DQL、DCL。
DDL--->数据定义语言,用来定义数据库对象(数据库,表,字段)
DML--->数据操作语言,用来对数据库表中的数据进行增删改
DQL--->数据查询语言,用来查询数据库中表的记录
DCL--->数据控制语言,用来创建数据库用户、控制数据库的访问权限
DDL
数据定义语言,用来定义数据库对象(数据库,表,字段) 。
数据库操作
查询所有数据库
1 show databases ;
查询当前所处数据库
2 select database() ;
创建数据库
3 create database [ if not exists ] 数据库名 [ default charset 字符集 ] [ collate 排序
规则 ] ;
//在同一个数据库服务器中,不能创建两个名称相同的数据库,否则将会报错。
//可以通过 if not exists 参数来解决这个问题,数据库不存在, 则创建该数据库,如果存在,则不
//创建。 create database if not extists 数据库名;
//创建一个数据库,并且指定字符集 create database 数据库名 default charset utf8mb4;
删除数据库
4 drop database [ if exists ] 数据库名 ;
//如果删除一个不存在的数据库,将会报错。此时,可以加上参数 if exists ,如果数据库存在,再
//执行删除,否则不执行删除。
切换数据库
5 use 数据库名 ;
//我们要操作某一个数据库下的表时,就需要通过该指令,切换到对应的数据库下,否则是不能操作的。
表操作
查询当前数据库所有表
1 show tables;
查看指定表结构
2 desc 表名 ;
//通过这条指令,我们可以查看到指定表的字段,字段的类型、是否可以为NULL,是否存在默认值等信
//息。
查询指定表的建表语句
3 show create table 表名 ;
//主要是用来查看建表语句的,而有部分参数我们在创建表的时候,并未指定也会查询
//到,因为这部分是数据库的默认值,如:存储引擎、字符集等。
创建表结构
CREATE TABLE 表名(
字段1 字段1类型 [COMMENT 字段1注释 ],
字段2 字段2类型 [COMMENT 字段2注释 ],
字段3 字段3类型 [COMMENT 字段3注释 ],
......
字段n 字段n类型 [COMMENT 字段n注释 ]
) [ COMMENT 表注释 ] ;
//注意: [...] 内为可选参数,最后一个字段后面没有逗号
MySQL中的数据类型有很多,主要分为三类:数值类型、字符串类型、日期时间类型。
!!!char 与 varchar 都可以描述字符串,char是定长字符串,指定长度多长,就占用多少个字符,和
字段值的长度无关 。而varchar是变长字符串,指定的长度为最大占用长度 。相对来说,char的性
能会更高些。
如:
1). 用户名 username ------> 长度不定, 最长不会超过50
username varchar(50)
2). 性别 gender ---------> 存储值, 不是男,就是女
gender char(1)
3). 手机号 phone --------> 固定长度为11
phone char(11)
表操作-修改
添加字段
1 ALTER TABLE 表名 ADD 字段名 类型 (长度) [ COMMENT 注释 ] [ 约束 ];
修改数据类型
2 ALTER TABLE 表名 MODIFY 字段名 新数据类型 (长度);
修改字段名和字段类型
3 ALTER TABLE 表名 CHANGE 旧字段名 新字段名 类型 (长度) [ COMMENT 注释 ] [ 约束 ];
删除字段
4 ALTER TABLE 表名 DROP 字段名;
修改表名
5 ALTER TABLE 表名 RENAME TO 新表名;
总结: ALTER TABLE 表名 ADD|MODIFY|CHANGE|DROP|RENAME TO
alter 改变,修改,变更 modify 限定,修改,该进 change 改变,变化,替换,更换
drop 丢,剔除,除名,放弃
表操作-删除
删除表
1 DROP TABLE [ IF EXISTS ] 表名;
//可选项 IF EXISTS 代表,只有表名存在时才会删除该表,表名不存在,则不执行删除操作(如果不
//加该参数项,删除一张不存在的表,执行将会报错)。
删除指定表, 并重新创建表
2 TRUNCATE TABLE 表名;
//注意: 在删除表的时候,表中的全部数据也都会被删除。
truncate 删节,截断,截断文件
DML
DML(数据操作语言),用来对数据库中表的数据记录进行增、删、改的操作。
添加数据(INSERT)
修改数据(UPDATE)
删除数据(DELETE)
添加数据
1 给指定字段添加数据
insert into 表名 (字段名1, 字段名2, ...) values (值1, 值2, ...);
2 给全部字段添加数据
insert into 表名 values (值1, 值2, ...);
3 批量添加数据
insert into 表名 (字段名1, 字段名2, ...) values (值1, 值2, ...), (值1, 值2, ...), (值
1, 值2, ...) ;
insert into 表名 values (值1, 值2, ...), (值1, 值2, ...), (值1, 值2, ...) ;
//注意事项:
// 插入数据时,指定的字段顺序需要与值的顺序是一一对应的。
// 字符串和日期型数据应该包含在引号中。
// 插入的数据大小,应该在字段的规定范围内。
查询全表数据的SQL语句 ---> select * from 表名;
修改数据
1 修改数据的具体语法为:
update 表名 set 字段名1 = 值1 , 字段名2 = 值2 , .... [ where 条件 ] ;
//注意事项:
//修改语句的条件可以有,也可以没有,如果没有条件,则会修改整张表的所有数据。
删除数据
1 删除数据的具体语法为:
delete from 表名 [ where 条件 ] ;
//注意事项:
// delete 语句的条件可以有,也可以没有,如果没有条件,则会删除整张表的所有数
据。
// delete 语句不能删除某一个字段的值(可以使用update,将该字段值置为null即
可)。
DQL
DQL(数据查询语言),用来查询数据库中表的记录。
查询关键字: SELECT
DQL 查询语句,语法结构如下:
SELECT
字段列表
FROM
表名列表
WHERE
条件列表
GROUP BY
分组字段列表
HAVING
分组后条件列表
ORDER BY
排序字段列表
LIMIT
分页参数
基本查询(不带任何条件)
条件查询(where)
聚合函数(count、max、min、avg、sum)
分组查询(group by)
排序查询(order by)
分页查询(limit)
基础查询
1 在基本查询的DQL语句中,不带任何的查询条件,查询的语法如下:
select 字段1, 字段2, 字段3 ... from 表名 ;
select * from 表名 ;
// 注意 : * 号代表查询所有字段,在实际开发中尽量少用(不直观、影响效率)。
字段设置别名
select 字段1 [ as 别名1 ] , 字段2 [ as 别名2 ] ... from 表名;
//字段设置别名可以简写
select 字段1 [ 别名1 ] , 字段2 [ 别名2 ] ... from 表名;
去除重复记录
select distinct 字段列表 from 表名;
条件查询
select 字段列表 from 表名 where 条件列表 ;
比较运算符 功能
> 大于
>= 大于等于
< 小于
<= 小于等于
= 等于
<> 或 != 不等于
BETWEEN ... AND ... 在某个范围之内(含最小、最大值)
IN(...) 在in之后的列表中的值,多选一
LIKE 占位符 模糊匹配(_匹配单个字符, %匹配任意个字符)
IS NULL 是NULL
IS NOT NULL 不是NULL
常用的逻辑运算符如下:
逻辑运算符 功能
AND 或 && 并且 (多个条件同时成立)
OR 或 || 或者 (多个条件任意一个成立)
NOT 或 ! 非 , 不是
_ 占位符有几个短横线占几位
例子: 查询姓名为两个字的员工信息 _ %
select * from emp where name like '__';
% 任意占位符,表示多个任意个数的占位
例子: 查询身份证号最后一位是X的员工信息
select * from emp where idcard like '%X';
select * from emp where idcard like '_________________X';
聚合函数
将一列数据作为一个整体,进行纵向计算 。
函数 功能
count 统计数量
max 最大值
min 最小值
avg 平均值
sum 求和
select 聚合函数(字段列表) from 表名 ;
// 注意 : NULL值是不参与所有聚合函数运算的。
分组查询
select 字段列表 from 表名 [ where 条件 ] group by 分组字段名 [ having 分组
后过滤条件 ];
//where与having区别
//执行时机不同:where是分组之前进行过滤,不满足where条件,不参与分组;而having是分组
//之后对结果进行过滤。
//判断条件不同:where不能对聚合函数进行判断,而having可以。
//注意事项:
// 执行顺序: where > 聚合函数 > having 。
// 支持多字段分组, 具体语法为 : group by columnA,columnB
排序查询
select 字段列表 from 表名 order by 字段1 排序方式1 , 字段2 排序方式2 ;
排序方式
asc : 升序(默认值)
desc: 降序
//注意事项:
// 如果是升序, 可以不指定排序方式ASC ;
// 如果是多字段排序,当第一个字段值相同时,才会根据第二个字段进行排序 ;
分页查询
select 字段列表 from 表名 limit 起始索引, 查询记录数 ;
//注意事项:
//起始索引从0开始,起始索引 = (查询页码 - 1)* 每页显示记录数。
//分页查询是数据库的方言,不同的数据库有不同的实现,MySQL中是LIMIT。
//如果查询的是第一页数据,起始索引可以省略,直接简写为 limit 查询记录数。
执行顺序
DCL
DCL(数据控制语言),用来管理数据库用户、控制数据库的访问权限。
创建用户
CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码';
修改用户密码
ALTER USER '用户名'@'主机名' IDENTIFIED WITH mysql_native_password BY '新密码' ;
删除用户
DROP USER '用户名'@'主机名' ;
//注意事项:
//在MySQL中需要通过用户名@主机名的方式,来唯一标识一个用户。 主机名可以使用 % 通配。
mysql怎样查看所有用户 SELECT User, Host FROM mysql.user;
权限控制
查询权限
SHOW GRANTS FOR '用户名'@'主机名' ;
授予权限
GRANT 权限列表 ON 数据库名.表名 TO '用户名'@'主机名';
撤销权限
REVOKE 权限列表 ON 数据库名.表名 FROM '用户名'@'主机名';
注意事项:
//多个权限之间,使用逗号分隔
//授权时, 数据库名和表名可以使用 * 进行通配,代表所有。
函数
字符串函数
函数 功能
concat(S1,S2,...Sn) 字符串拼接,将S1,S2,... Sn拼接成一个字符串
lower(str) 将字符串str全部转为小写
upper(str) 将字符串str全部转为大写
lpad(str,n,pad) 左填充,用字符串pad对str的左边进行填充,达到n个字符串长度
rpad(str,n,pad) 右填充,用字符串pad对str的右边进行填充,达到n个字符串长度
trim(str) 去掉字符串头部和尾部的空格
substring(str,start,len) 返回从字符串str从start位置起的len个长度的字符串
数值函数
函数 功能
ceil(x) 向上取整
floor(x) 向下取整
mod(x,y) 返回x/y的模
rand() 返回0~1内的随机数
round(x,y) 求参数x的四舍五入的值,保留y位小数
//案例:
//通过数据库的函数,生成一个六位数的随机验证码。
//思路: 获取随机数可以通过rand()函数,但是获取出来的随机数是在0-1之间的,所以可以在其基础
//上乘以1000000,然后舍弃小数部分,如果长度不足6位,补0
//select lpad(round(rand()*1000000 , 0), 6, '0');
日期函数
函数 功能
curdate() 返回当前日期
curtime() 返回当前时间
now() 返回当前日期和时间
year(date) 获取指定date的年份
month(date) 获取指定date的月份
day(date) 获取指定date的日期
date_add(date, INTERVAL exprtype) 返回一个日期/时间值加上一个时间间隔expr后的时间值
datediff(date1,date2) 返回起始时间date1 和 结束时间date2之间的天数
//YEAR , MONTH , DAY:当前年、月、日
select YEAR(now());
select MONTH(now());
select DAY(now());
//date_add:增加指定的时间间隔
select date_add(now(), INTERVAL 70 YEAR );
//datediff:获取两个日期相差的天数
select datediff('2021-10-01', '2021-12-01');
//案例:
//查询所有员工的入职天数,并根据入职天数倒序排序。
//思路: 入职天数,就是通过当前日期 - 入职日期,所以需要使用datediff函数来完成。
select name, datediff(curdate(), 入职日期) as '入职天数' from emp order by
入职天数 desc;
流程函数
函数 功能
IF(value , t , f) 如果value为true,则返回t,否则返回f
IFNULL(value1 , value2) 如果value1不为空,返回value1,否则返回value2
CASE WHEN [ val1 ] THEN [res1] ...ELSE [ default ] END
如果val1为true,返回res1,... 否则返回default默认值
CASE [ expr ] WHEN [ val1 ] THEN [res1] ... ELSE [ default ] END
如果expr的值等于val1,返回res1,... 否则返回default默认值
约束
概念:约束是作用于表中字段上的规则,用于限制存储在表中的数据。
目的:保证数据库中数据的正确、有效性和完整性。
约束 描述 关键字
非空约束 限制该字段的数据不能为null not null 唯一约束 保证该字段的所有数据都是唯一不重复的 unique
主键约束 主键是一行数据的唯一标识要求非空且唯一 Primary Key
默认约束 保存数据时,如果未指定该字段的值则采用默认值 default
检查约束 (8.0.16版本之后)保证字段值满足某一个条件 check
外键约束 用来让两张表的数据之间建立连接,保证数据的一致性和完整性 foreign key
//注意:约束是作用于表中字段上的,可以在创建表/修改表的时候添加约束。
外键
用来让两张表或者是多张表的数据之间建立连接,从而保证数据的一致性和完整性。
添加外键
CREATE TABLE 表名(
字段名 数据类型,
...
[CONSTRAINT] [外键名称] FOREIGN KEY (外键字段名) REFERENCES 主表 (主表列名)
);
alter table 表名 add constraint 外键名称 foreign key (外键字段名) references 主表 (主表列名);
//外键名称是我们自己取名的
//案例:
//为emp表的dept_id字段添加外键约束,关联dept表的主键id。
//alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references
//dept(id);
删除外键
alter table 表名 drop foreign key 外键名称;
//alter table emp drop foreign key fk_emp_dept_id;
删除/更新行为
添加了外键之后,再删除父表数据时产生的约束行为,我们就称为删除/更新行为。具体的删除/更新行为有以下几种:
行为 说明
NO ACTION 当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有则不
允许删除/更新。 (与 RESTRICT 一致) 默认行为
RESTRICT 当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有则不
允许删除/更新。 (与 NO ACTION 一致) 默认行为
CASCADE 当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有,则
也删除/更新外键在子表中的记录。
SET NULL 当在父表中删除对应记录时,首先检查该记录是否有对应外键,如果有则设置子表
中该外键值为null(这就要求该外键允许取null)。
SET DEFAULT 父表有变更时,子表将外键列设置成一个默认的值 (Innodb不支持)
具体语法为:
ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段) REFERENCES
主表名 (主表字段名) ON UPDATE 行为 ON DELETE 行为;
alter table 表名 add constraint 外键名称 foreign key (外键字段) references
主表名 (主表字段名) on update 行为 on delete 行为
多表查询(之前的查询都是单表查询,而现在要学习的则是多表查询操作)
多表关系 :在进行数据库表结构设计时,根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务之间相互关联,所以各个表结构之间也存在着各种联系就是多表关系。
基本上分为三种:
一对多(多对一)
多对多
一对一
一对多(例如: 部门 与 员工的关系)
关系: 一个部门对应多个员工,一个员工对应一个部门
实现: (在多的一方建立外键,指向一的一方的主键)
多对多(例如: 学生 与 课程的关系)
关系: 一个学生可以选修多门课程,一门课程也可以供多个学生选择
实现: 建立第三张中间表,中间表至少包含两个外键,分别关联两方主键
一对一(例子: 用户 与 用户详情的关系)
关系: 一对一关系,多用于单表拆分,将一张表的基础字段放在一张表中,其他详情字段放在另
一张表中,以提升操作效率
实现: 把一张表拆分为两张表并在任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的(unique)
多表查询概述 (多表查询就是指从多张表中查询数据)
原来查询单表数据,执行的SQL形式为:
select * from 表名1;
那么要执行多表查询,就只需要使用逗号分隔多张表即可,
如: select * from 表名1 , 表名1; //但是会造成笛卡尔积
// 表名1所有的记录情况 与 表名2所有记录的情况的 所有组合情况,这种现象称之为笛卡尔积。
// 笛卡尔积---> 笛卡尔乘积是指在数学中,两个集合A集合 和 B集合的所有组合情况。而在多表查询中,我们
//是需要消除无效的笛卡尔积的,只保留两张表关联部分的数据。
//在SQL语句中,如何来去除无效的笛卡尔积呢? 我们可以给多表查询加上连接查询的条件即可。
//select * from 表名1 , 表名2 where 表名1_某字段值 = 表名2.某字段值
多表查询分类
1.连接查询
内连接:相当于查询A、B交集部分数据
内连接查询的是两张表交集部分的数据。
内连接的语法分为两种: 隐式内连接、显式内连接。
隐式内连接
select 字段列表 from 表1 , 表2 where 条件 ... ;
显式内连接
select 字段列表 from 表1 [ inner ] join 表2 on 连接条件 ... ;
// inner 内部的 join 连接处,接合点
表的别名
表的别名:
① 表名 as 别名1 , 表名 as 别名2 ; //as 可以省略
② 表名 别名1 , 表名 别名2 ;
//注意事项:一旦为表起了别名,就不能再使用表名来指定对应的字段了,此时只能够使用别名来指定字
段。
外连接:
左外连接:查询左表所有数据,以及两张表交集部分数据
左外连接 //左外连接相当于查询表1(左表)的所有数据,当然也包含表1和表2交集部分的数据。
select 字段列表 from 表1 left [ outer ] join 表2 on 条件 ... ;
//SELECT 字段列表 FROM 表1 LEFT [ OUTER ] JOIN 表2 ON 条件 ... ;
右外连接:查询右表所有数据,以及两张表交集部分数据
右外连接 //右外连接相当于查询表2(右表)的所有数据,当然也包含表1和表2交集部分的数据。
select 字段列表 from 表1 right [ outer ] join 表2 on 条件 ... ;
//SELECT 字段列表 FROM 表1 RIGHT [ OUTER ] JOIN 表2 ON 条件 ... ;
/***
注意事项:
左外连接和右外连接是可以相互替换的,只需要调整在连接查询时SQL中,表结构的先后顺
序就可以了。而我们在日常开发使用时,更偏向于左外连接。
*/
自连接:当前表与自身的连接查询,自连接必须使用表别名
自连接查询 //就是自己连接自己,也就是把一张表连接查询多次,而对于自连接查询,可以是内连接查询,也可以
// 是外连接查询。
select 字段列表 from 表A 别名A join 表A 别名B on 条件 ...;
//SELECT 字段列表 FROM 表A 别名A JOIN 表A 别名B ON 条件 ... ;
/***
注意事项:
在自连接查询中,必须要为表起别名,要不然我们不清楚所指定的条件、返回的字段,到底
是哪一张表的字段。
*/
联合查询(对于union查询,就是把多次查询的结果合并起来,形成一个新的查询结果集)
union( 联合,结合)
联合查询 //对于union查询,就是把多次查询的结果合并起来,形成一个新的查询结果集。
select 字段列表 from 表A ...
union [ all ]
select 字段列表 from 表B ....;
SELECT 字段列表 FROM 表A ...
UNION [ ALL ]
SELECT 字段列表 FROM 表B ....;
/***
对于联合查询的多张表的列数必须保持一致,字段类型也需要保持一致。
union all 会将全部的数据直接合并在一起,union 会对合并之后的数据去重。
union all查询出来的结果,仅仅进行简单的合并,并未去重。
union 联合查询,会对查询出来的结果进行去重处理。
*/
2.子查询概述 (SQL语句中嵌套SELECT语句,称为嵌套查询,又称子查询)
SQL语句中嵌套select语句,称为嵌套查询,又称子查询。
select * from 表1 where 字段名 条件 (select 字段名 from 表2);
//SELECT * FROM t1 WHERE column1 =||>= || <= 或.....等 ( SELECT column1 FROM t2 );
//子查询外部的语句可以是insert / update / delete / select 的任何一个。
根据子查询结果不同,分为:
标量子查询(子查询结果为单个值)
子查询返回的结果是单个值(数字、字符串、日期等),最简单的形式,这种子查询称为标量
子查询。常用的操作符:= <> > >= < <=
列子查询(子查询结果为一列)
子查询返回的结果是一列(可以是多行),这种子查询称为列子查询。
常用的操作符:in 、not in 、 any 、some 、 all
操作符 描述
in 在指定的集合范围之内,多选一
not in 不在指定的集合范围之内
any 子查询返回列表中,有任意一个满足即可
some 与ANY等同,使用SOME的地方都可以使用ANY
all 子查询返回列表的所有值都必须满足
行子查询(子查询结果为一行)
子查询返回的结果是一行(可以是多列),这种子查询称为行子查询。
常用的操作符:= 、<> 、in 、not in
表子查询(子查询结果为多行多列)
子查询返回的结果是多行多列,这种子查询称为表子查询。
常用的操作符:in
根据子查询位置,分为: where之后 from之后 select之后
事务
事务简介 事务 是一组操作的集合,是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
//事务简介
//事务 是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系
//统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
//注意: 默认MySQL的事务是自动提交的,也就是说,当执行完一条DML语句时,MySQL会立即隐
//式的提交事务。
控制事务一
查看事务提交方式
select @@autocommit ; // SELECT @@autocommit ;
设置事务提交方式
set @@autocommit = 0 || 1 ; //(0--->手动提交需要用commit提交;1 --->默认的自动提交)
/***
1.autocommit=0,不会自动提交,需要手动commit; 2.autocommit=1,每次执行修改语句会自动执行commit
*/
提交事务
commit // COMMIT;
回滚事务
rollback // ROLLBACK;
//set @@autocommit = 0
//注意:上述的这种方式,我们是修改了事务的自动提交行为, 把默认的自动提交(1)修改为了手动提
//交(0), 此时我们执行的DML语句都不会提交, 需要手动的执行commit进行提交。
控制事务二
开启事务
start transaction 或 begin ; //START TRANSACTION 或 BEGIN ;
提交事务
commit //COMMIT;
回滚事务
rollback //ROLLBACK;
事务的四大特性
原子性(Atomicity) :事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
隔离性(Isolation) :数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立
环境下运行。
持久性(Durability) :事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
上述就是事务的四大特性,简称ACID。
// Atomicity 原子性 原子数 原字价 Consistency 一致性 ; 连贯性 ; 黏稠度 ; 平滑度
// Isolation 隔离性 孤立 孤独 绝缘 Durability 持久性 ; 耐久性 ; 耐用性
并发事务问题
赃读:一个事务读到另外一个事务还没有提交的数据。 //比如--->B读取到了A未提交的数据。
不可重复读:一个事务先后读取同一条记录,但两次读取的数据不同,称之为不可重复读。
//比如--->事务A两次读取同一条记录,但是读取到的数据却是不一样的。
幻读:一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据
已经存在,好像出现了 "幻影"。
事务的隔离级别
隔离级别 脏读 不可重复读 幻读
Read uncommitted(读未提交) √ √ √
Read committed(读已提交) × √ √
Repeatable Read(可重复读默认) × × √
Serializable(可串性化) × × ×
查看事务隔离级别 select @@transaction_isolation
//SELECT @@TRANSACTION_ISOLATION;
设置事务隔离级别
set session transaction isolation level 隔离级别
//SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL { READ UNCOMMITTED |
//READ COMMITTED | REPEATABLE READ | SERIALIZABLE }
注意:事务隔离级别越高,数据越安全,但是性能越低。
存储引擎
存储引擎(需要在合适的场景选择合适的存储引擎。)
存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。存储引擎是基于表的,而不是基于库的,所以存储引擎也可被称为表类型。可以在创建表的时候,来指定选择的存储引擎,如果没有指定将自动选择默认的存储引擎(mysql8版本及以上 默认存储引擎为: InnoDB)
//查询当前数据库支持的存储引 show engines;
建表时指定存储引擎
create table 表名(
字段1 字段1类型 [ COMMENT 字段1注释 ] ,
......
字段n 字段n类型 [COMMENT 字段n注释 ]
) engine = INNODB || MyISAM || Memory [ COMMENT 表注释 ] ;
查询当前数据库支持的存储引擎
show engines;
存储引擎特点
INNODB
1). InnoDB 介绍
InnoDB是一种兼顾高可靠性和高性能的通用存储引擎,
在 MySQL 5.5 之后,InnoDB是默认的MySQL存储引擎。
2). 特点
DML(增删改操作)操作遵循ACID模型,支持事务;
行级锁,提高并发访问性能;
支持外键FOREIGN KEY约束,保证数据的完整性和正确性;
3). 文件
xxx.ibd:xxx代表的是表名,
innoDB引擎的每张表都会对应这样一个表空间文件,存储该表的表结构(frm-早期的 、sdi-新版的)、数据和索引。
参数:innodb_file_per_table
show variables like 'innodb_file_per_table';
如果该参数开启,代表对于InnoDB引擎的表,每一张表都对应一个ibd文件。 直接打开MySQL的
数据存放目录 某盘:\xxx\xxx\Data,这个目录下有很多文件夹,不同的文件夹代表不同的数据库
打开其中一个文件夹可以看到里面有很多的ibd文件,每一个ibd文件就对应一张表
/***
比如:我们有一张表 account,就有这样的一个account.ibd文件,
而在这个ibd文件中不仅存放表结构、数据,还会存放该表对应的索引信息。
而该文件是基于二进制存储的,不能直接基于记事本打开,
在当前路径下使用CMD打开dos窗口
我们再使用mysql提供的一个指令 ibd2sdi (ibd2sdi 表名.ibd),
通过该指令就可以从ibd文件中提取sdi信息,而sdi数据字典信息中就包含该表的表结构。
*/
4). 逻辑存储结构
表空间 : InnoDB存储引擎逻辑结构的最高层,ibd文件其实就是表空间文件,在表空间中可以
包含多个Segment段。
段 : 表空间是由各个段组成的,常见的段有数据段、索引段、回滚段等。
InnoDB中对于段的管理,都是引擎自身完成,不需要人为对其控制,一个段中包含多个区。
区 : 区是表空间的单元结构,每个区的大小为1M。
默认情况下,InnoDB存储引擎页大小为16K, 即一个区中一共有64个连续的页。
页 : 页是组成区的最小单元,页也是InnoDB 存储引擎磁盘管理的最小单元,
每个页的大小默认为 16KB.为了保证页的连续性,InnoDB 存储引擎每次从磁盘申请 4-5 个区。
行 : InnoDB 存储引擎是面向行的,也就是说数据是按行进行存放的,在每一行中除了定义表时
所指定的字段以外,还包含两个隐藏字段(后面会详细介绍)。
MyISAM
1). MyISAM介绍
MyISAM是MySQL早期的默认存储引擎。
2). 特点
不支持事务,不支持外键
支持表锁,不支持行锁
访问速度快
3). 文件
表名.sdi:存储表结构信息
表名.MYD: 存储数据
表名.MYI: 存储索引
Memory
1). Memory介绍
Memory引擎的表数据是存储在内存中的,由于受到硬件问题、或断电问题的影响,只能将这些表作为
临时表或缓存使用。
2). 特点
内存存放
hash索引(默认)
3).文件
xxx.sdi:存储表结构信息
INNODB MyISAM Memory区别及特点
面试题:
InnoDB引擎与MyISAM引擎的区别 ?
①. InnoDB引擎, 支持事务, 而MyISAM不支持。
②. InnoDB引擎, 支持行锁和表锁, 而MyISAM仅支持表锁, 不支持行锁。
③. InnoDB引擎, 支持外键, 而MyISAM是不支持的。
存储引擎选择?
在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合。
InnoDB:是Mysql的默认存储引擎,支持事务、外键。如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操作,那么InnoDB存储引擎是比较合适的选择。
MyISAM :如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完
整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。
MEMORY:将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。MEMORY的缺陷就是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性。
索引
索引概述: 索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。
在无索引的情况下--->就需要从第一行开始扫描,一直扫描到最后一行,我们称之为 全表扫描,性能很低。
在有索引的情况下--->对于某张表建立了索引,假设索引结构就是二叉树,那么也就意味着,会对建立索引的这个字段建立一个二叉树的索引结构。// 备注:这里只是假设索引的结构是二叉树
优势 | 劣势
提高数据检索的效率,降低数据库的IO成本 | 索引列也是要占用空间的。
|
|
通过索引列对数据进行排序,降低 | 索引大大提高了查询效率,同时却也降低更新表的速度,
数据排序的成本,降低CPU的消耗。 | 如对表进行INSERT、UPDATE、DELETE时,效率降低。
索引结构
MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的索引结构,主要包含以下几种:
索引结构 描述
B+Tree索引 最常见的索引类型,大部分引擎都支持 B+树索引
Hash索引 底层数据结构是用哈希表实现的,只有精确匹配索引列的查询才有效,
不支持范围查询
R-tree(空间索引) 空间索引是MyISAM引擎的一个特殊索引类型,
主要用于地理空间数据类型,通常使用较少
Full-text(全文索引) 是一种通过建立倒排索引,快速匹配文档的方式。
类似于Lucene,Solr,ES
//full 满是……的;饱的;最大量的;完整的,详尽的
二叉树
假如说MySQL的索引结构采用二叉树的数据结构,比较理想的结构如下:
但如果主键是顺序插入的,则会形成一个单向链表,结构如下:
所以,如果选择二叉树作为索引结构,会存在以下缺点:
顺序插入时,会形成一个链表,查询性能大大降低。 大数据量情况下,层级较深,检索速度慢。
红黑树
红黑树是一颗自平衡二叉树,那这样即使是顺序插入数据,最终形成的数据结构也是一颗平衡的二叉树,结构如下:
但是,即使如此,由于红黑树也是一颗二叉树,所以也会存在一个缺点:大数据量情况下,层级较深,检索速度慢。
所以,在MySQL的索引结构中,并没有选择二叉树或者红黑树,而选择的是B+Tree,
B-Tree
B-Tree,B树是一种多叉路衡查找树,相对于二叉树,B树每个节点可以有多个分支,即多叉。
以一颗最大度数(max-degree)为n(n阶)的b-tree为例,那这个B树每个节点最多存储(n-1)个key,n个指针 //树的度数指的是一个节点的子节点个数。
例子:以一颗最大度数(max-degree)为5(5阶)的b-tree为例,那这个B树每个节点最多存储4个key,5个指针:
该例子特点:
5阶的B树,每一个节点最多存储4个key,对应5个指针。一旦节点存储的key数量到达5,就会裂变,中间元素向上分裂。在B树中,非叶子节点和叶子节点都会存放数据。
B+Tree
B+Tree是B-Tree的变种,
例子:我们以一颗最大度数(max-degree)为4(4阶)的b+tree为例,来看一
下其结构示意图:
可以看到,两部分:
绿色框框起来的部分,是索引部分,仅仅起到索引数据的作用,不存储数据。
红色框框起来的部分,是数据存储部分,在其叶子节点中要存储具体的数据。
B+Tree 与 B-Tree相比,主要有以下三点区别:
1.所有的数据都会出现在叶子节点。
2.叶子节点形成一个单向链表。
3.非叶子节点仅仅起到索引数据作用,具体的数据都是在叶子节点存放的。
上述我们所看到的结构是标准的B+Tree的数据结构,再来看看MySQL中优化之后的B+Tree。
MySQL索引数据结构对经典的B+Tree进行了优化。在原B+Tree的基础上,增加了一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的B+Tree,提高区间访问的性能,利于排序。
Hash
MySQL中除了支持B+Tree索引,还支持一种索引类型---Hash索引。
哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中。
特点
1 Hash索引只能用于对等比较(=,in),不支持范围查询(between,>,< ,...)
2 无法利用索引完成排序操作
3 查询效率高,通常(不存在hash冲突的情况)只需要一次检索就可以了,效率通常要高于B+tree索
引
存储引擎支持
在MySQL中,支持hash索引的是Memory存储引擎。 而InnoDB中具有自适应hash功能,hash索引是InnoDB存储引擎根据B+Tree索引在指定条件下自动构建的。
思考题: 为什么InnoDB存储引擎选择使用B+tree索引结构?
A. 相对于二叉树,层级更少,搜索效率高;
B. 对于B-tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储
的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低;
C. 相对Hash索引,B+tree支持范围匹配及排序操作;
索引分类
在MySQL数据库,将索引的具体类型主要分为以下几类:主键索引、唯一索引、常规索引、全文索引。
分类 含义 特点 关键字
主键索引 针对于表中主键创建的索引 默认自动创建, 只能有一个 PRIMARY
唯一索引 避免同一个表中某数据列中的值重复 可以有多个但是值只能是一个 UNIQUE
常规索引 快速定位特定数据 可以有多个
全文索引 全文索引查找的是文本中的关键词,而不是比较索引中的值 可以有多个 FULLTEXT
聚集索引&二级索引而在InnoDB存储引擎中,根据索引的存储形式,又可以分为以下两种:
聚集索引选取规则:
如果存在主键,主键索引就是聚集索引。
如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。
如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索
引。
聚集索引的叶子节点下挂的是这一行的数据 。
二级索引的叶子节点下挂的是该字段值对应的主键值。
接下来,我们来分析一下,当我们执行如下的SQL语句时,具体的查找过程是什么样子的。
具体过程如下: //这里我们根据姓名去拿行记录
①. 由于是根据name字段进行查询,所以先根据name='Arm'到name字段的二级索引中进行匹配查
找。但是在二级索引中只能查找到 Arm 对应的主键值 10。
②. 由于查询返回的数据是*,所以此时,还需要根据主键值10,到聚集索引中查找10对应的记录,最终找到10对应的行row。
③. 最终拿到这一行的数据,直接返回即可。
综上是回表查询: 这种先到二级索引中查找数据,找到主键值,然后再到聚集索引中根据主键值,获取数据的方式,就称之为回表查询。
思考题?
以下两条SQL语句,那个执行效率高? 为什么?
A. select * from user where id = 10 ;
B. select * from user where name = 'Arm' ;
备注: id为主键,name字段创建的有索引;
解答:
A 语句的执行性能要高于B 语句。
因为A语句直接走聚集索引,直接返回数据。而B语句需要先查询name字段的二级索引,然
后再查询聚集索引,也就是需要进行回表查询。
索引语法
创建索引
CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name ( 表字段,... ) ;
查看索引
SHOW INDEX FROM table_name ;
创建联合索引
CREATE INDEX index_name ON table_name(表字段1,表字段2,表字段3);
删除索引
DROP INDEX index_name ON table_name ;
SQL性能分析
MySQL 客户端连接成功后,通过 show [session|global] status 命令可以提供服务器状态信
息,可以查看当前数据库的INSERT、UPDATE、DELETE、SELECT的访问频次:
-- session 是查看当前会话 ;
-- global 是查询全局数据 ;
SHOW GLOBAL STATUS LIKE 'Com_______';
Com_delete: 删除次数
Com_insert: 插入次数
Com_select: 查询次数
Com_update: 更新次数
/***
如果是以增删改为主,我们可以考虑不对其进行索引的优化。 如果是以
查询为主,那么就要考虑对数据库的索引进行优化了。
*/
如过说是以查询为主,又该如何定位针对于那些查询语句进行优化呢? 次数可以借助于慢查询
日志
慢查询日志
慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有
SQL语句的日志。
MySQL的慢查询日志默认没有开启,可以查看一下系统变量
show variables like slow_query_log。 //在mysql目录下
如果要开启慢查询日志,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:
// 开启MySQL慢日志查询开关
slow_query_log=1
// 设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2
配置完毕之后,通过以下指令重新启动MySQL服务器进行测试,查看慢日志文件中记录的信息
/var/lib/mysql/localhost-slow.log。
systemctl restart mysqld
然后,再次查看开关情况,慢查询日志就已经打开了。
MySQL的慢查询查看开关情况,可以查看一下系统变量 show variables like slow_query_log。
检查慢日志内容情况:tail -f localhost-slow.log, //最好新建终端,一个终端测试,一个重点查看日志。那这样,通过慢查询日志,就可以定位出执行效率比较低的SQL,从而有针对性的进行优化。检查慢查询日志。在慢查询日志中,只会记录执行时间超多我们预设时间(2s)的SQL,执行较快的SQL是不会记录的。这样,通过慢查询日志,就可以定位出执行效率比较低的SQL,从而有针对性的进行优化。
profile详情
show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。通过have_profiling
参数,能够看到当前MySQL是否支持profile操作:
通过have_profiling参数。
能够看到当前MySQL是否支持profile操作:
SELECT @@have_profiling ;
//通过SELECT @@have_profiling ;可以看到,当前MySQL是否支持profile操作的,
//如果返回列的值是YES。可以看到,当前MySQL是支持 profile操作的,但是开关是关闭的。
//所以我们可以通过set语句在session/global级别开启profiling:
SET profiling = 1;
/**
开关已经打开了,接下来,所执行的SQL语句,都会被MySQL记录,并记录执行时间消耗到哪儿去
了。
**/
//SELECT @@have_profiling ; 查看到当前MySQL是否支持profile操作:
//SET profiling = 1; 开启profile操作的
// profiling 资料收集,剖析研究
.......
执行一系列的业务SQL的操作,然后通过如下指令查看指令的执行耗时:
查看每一条SQL的耗时情况: show profiles; 这样会得到一张表,可以根据表提供的信息查询当前SQL语句各个阶段的耗时情况,下面是例子,//这里就不写查询的SQL语句了
-- 查看每一条SQL的耗时基本情况
show profiles;
-- 查看指定query_id的SQL语句各个阶段的耗时情况
show profile for query query_id;
-- 查看指定query_id的SQL语句CPU的使用情况
show profile cpu for query query_id;
例子:
explain
explain 或者 desc命令获取 MySQL 如何执行 select 语句的信息,包括在select语句执行过程中表如何连接和连接的顺序。
语法:
-- 直接在select语句之前加上关键字 explain / desc
explain || desc select 字段列表 from 表名 where 条件 ;
//EXPLAIN || DESC SELECT 字段列表 FROM 表名 WHERE 条件 ;
例子:执行下图表中该语句之后 ,会返回一张表。
Explain 执行计划中各个字段的含义:
// 通过索引来提升数据查询性能。
在演示的时候,使用准备的一张表 , 在这张表中准备了1000w的记录。这张表中id为主键,有主键索引,而其他字段是没有建立索引的。---> 根据id进行数据查询,性能依然很快,因为主键id是有索引的。再来根据没有索引的字段进行查询,可以看到根据没有索引的字段进行查询,查询返回了一条数据,结果耗时很长的,就是因为该字段没有索引,造成了全表扫描,而造成查询效率很低。那么我们可以针对于该没有索引的字段,建立一个索引,建立了索引之后,再次根据这个字段(此时这个字段已经有了索引)进行查询,执行相同的SQL语句,再次查看SQL的耗时。明显会看到,该字段建立了索引之后,查询性能大大提升。建立索引前后,查询耗时都不是一个数量级的。
创建普通索引:create index idx_表名_表字段 on 表名(表字段 ) ; // 给索引取名标准化
最左前缀法则
如果索引了多列(联合索引),要遵守最左前缀法则。最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。如果跳跃某一列,索引将会部分失效(后面的字段索引失效)。
根据show index from 表名 // 查看创建了哪些索引
例子:
思考题?
范围查询
联合索引中,出现范围查询(>,<),范围查询右侧的列索引失效。
所以,在业务允许的情况下,尽可能的使用类似于 >= 或 <= 这类的范围查询,而避免使用 > 或 < .
索引失效情况
1).索引列运算 . 不要在索引列上进行运算操作,否则索引将失效。
2).字符串不加引号 . 字符串类型字段使用时,不加引号,索引将失效。
经过上面两组示例,会明显的发现,如果字符串不加单引号,对于查询结果,没什么影响,但是数
据库存在隐式类型转换,索引将失效。
3).模糊查询 . 如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。
由于下面查询语句中,都是根据profession字段查询,符合最左前缀法则,联合索引是可以生效的,这里主要看一下,模糊查询时,%加在关键字之前,和加在关键字之后的影响。
经过上述的测试,可以发现,在like模糊查询中,在关键字后面加%,索引可以生效。而如果在关键字前面加了%,索引将会失效。如果是在关键字前后都加%,索引将会失效。
4) . or连接条件 用or分割开的条件,如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。
这里的age是没有索引的
由于age没有索引,所以即使id、phone有索引,索引也会失效。
所以需要也可以对age字段建立索引。
最终,我们发现,当or连接的条件,左右两侧字段都有索引时,索引才会生效。
5) . 数据分布影响 如果MySQL评估使用索引比全表更慢,则不使用索引。
因为MySQL在查询时,会自动评估使用索引的效率与走全表扫描的效率,如果走全表扫描更快,则放弃索引,走全表扫描。 因为索引是用来索引少量数据的,如果通过索引查询返回大批量的数据,则还不如走全表扫描来的快,此时索引就会失效。
接下来,我们再来看看 is null 与 is not null 操作是否走索引。
执行如下两条语句 :
最终可以看到,一模一样的SQL语句,先后执行了两次,结果查询计划是不一样的,为什么会出现这种现象,这是和数据库的数据分布有关系。查询时MySQL会评估,走索引快,还是全表扫描快,如果全表扫描更快,则放弃索引走全表扫描。 因此,is null 、is not null是否走索引,得具体情况具体分析,并不是固定的。
SQL提示
目前tb_user表的索引情况如下:
把上述的 idx_user_age, idx_email 这两个之前测试使用过的索引直接删除。
drop index idx_user_age on tb_user;
drop index idx_email on tb_user;
查看有哪些索引
show index from 表名
删除索引。
drop index 索引名 on 表名;
测试结果,我们可以看到,possible_keys中 idx_user_pro_age_sta,idx_user_pro 这两个
索引都可能用到,最终MySQL选择了idx_user_pro_age_sta索引。这是MySQL自动选择的结果。
那么,能不能在查询的时候,自己来指定使用哪个索引呢?
答案是肯定的,此时就可以借助于,MySQL的SQL提示来完成。
SQL提示,是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。
1). use index : 建议MySQL使用哪一个索引完成此次查询(仅仅是建议,mysql内部还会再次进
行评估)。
explain select * from 表名 use index(指定要使用的所用名) where 某字段 = '某值';
2). ignore index : 忽略指定的索引。
explain select * from 表名 ignore index(指定要使用的所用名) where 某字段 = '某值';
3). force index : 强制使用索引。
explain select * from 表名 force index(指定要使用的所用名) where 某字段 = '某值';
例子:
覆盖索引!!!
尽量使用覆盖索引,减少select *。 那么什么是覆盖索引呢? 覆盖索引是指 查询使用了索引,并
且需要返回的列,在该索引中已经全部能够找到 。
来看一组SQL的执行计划,看看执行计划的差别,然后再来具体做一个解析。
从上述的执行计划可以看到,这四条SQL语句的执行计划前面所有的指标都是一样的,看不出来差
异。但是此时,主要关注的是后面的Extra,前面两条SQL的结果为 Using where; Using
Index ; 而后面两条SQL的结果为: Using index condition 。--->使用索引条件
// extra额外;特别,格外 // condition 状况,状态;条件,
因为,在tb_user表中有一个联合索引 idx_user_pro_age_sta,该索引关联了三个字段
profession、age、status,而这个索引也是一个二级索引,所以叶子节点下面挂的是这一行的主
键id。 所以当我们查询返回的数据在 id、profession、age、status 之中,则直接走二级索引
直接返回数据了。 如果超出这个范围,就需要拿到主键id,再去扫描聚集索引,再获取额外的数据了,这个过程就是回表。 而我们如果一直使用select * 查询返回所有字段值,很容易就会造成回表查询(除非是根据主键查询,此时只会扫描聚集索引)。
例子:id是主键,是一个聚集索引。 name字段建立了普通索引,是一个二级索引(辅助索引)。
根据id查询,直接走聚集索引查询,一次索引扫描,直接返回数据,性能高。根据name字段查询,查询二级索引,但是由于查询返回在字段为 id,name,在name的二级索引中,这两个值都是可以直接获取到的,因为覆盖索引,所以不需要回表查询,性能高。但是在name的二级索引中,不包含其他字段,只含有主键的ID,所以,需要两次索引扫描,也就是需要回表查询,在去聚集索引查询对应的ID,性能相对较差一点。
思考题?
一张表, 有四个字段(id, username, password, status),
由于数据量大, 需要对以下SQL语句进行优化, 该如何进行才是最优方案:
select id,username,password from 表名 where username = 'ppc';
答案: 针对于 username, password建立联合索引, sql为:
create index idx_表名_name_pass on 表名(username,password);
这样可以避免上述的SQL语句,在查询的过程中,出现回表查询。
前缀索引
当字段类型为字符串(varchar,text,longtext等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO, 影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。
前缀索引语法
create index idx_表名_表字段名_num值 on 表名(表字段(num值)) ;
//标准化书写前缀索引
示例:
//语法 create index idx_xxxx on table_name(column(n)) ;
//create index idx_email_5 on tb_user(email(5));
为tb_user表的email字段,建立长度为5的前缀索引。
create index idx_tb_user_email_5 on tb_user(email(5));
前缀长度可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高, 唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。根据代码段里面的语句执行之后可以根据数据来确定截取几个字符串是最佳的长度,那么我们的前缀索引就截取几个
select count(distinct 表字段) / count(*) from 表名 ;
select count(distinct substring(表字段,从哪儿开始,截取几个)) / count(*) from 表名 ;
前缀索引的查询流程
单列索引与联合索引
单列索引:即一个索引只包含单个列。
联合索引:即一个索引包含了多个列。
结论:当表字段都是有单列索引的时候,最终mysql只会选择一个索引,也就是说,只能走一个字段的索引.此时是会回表查询的。
紧接着,我们再来创建一个phone和name字段的联合索引来查询一下执行计划。
创建联合索引的指令
create [unique] index idx_表名_表字段1_表名字段2 on 表名(表字段1, 表字段2 ...)
// 创建联合索引的标准化写法
结论:在使用有联合索引的字段进行查询时,就走联合索引,而在联合索引中包含某字段1,和某字段2等...的信息,所以查询是无需回表查询的。并且在叶子节点下挂的是对应的主键id,所以查询没有联合索引的字段值时。再根据ID去聚集索引里面去查找对应的列
!!!在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引,而非单列索引。
索引设计原则!!!
1). 针对于数据量较大,且查询比较频繁的表建立索引。
2). 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索
引。
3). 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
4). 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
5). 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,
避免回表,提高查询效率。
6). 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增
删改的效率。
7). 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含
NULL值时,它可以更好地确定哪个索引最有效地用于查询。
SQL优化
插入数据 insert
如果需要一次性往数据库表中插入多条记录,可以从以下三个方面进行优化。
3). 优化方案三
大批量插入数据
如果一次性需要插入大批量数据(比如: 几百万的记录),使用insert语句插入性能较低,此时可以使
用MySQL数据库提供的load指令进行插入。操作如下:
可以执行如下指令,将数据脚本文件中的数据加载到表结构中:
-- 客户端连接服务端时,加上参数 -–local-infile
mysql –-local-infile -u 用户名 -p密码
-- 设置全局参数local_infile为1,开启从本地加载文件导入数据的开关
set global local_infile = 1;
-- 执行load指令将准备好的数据,加载到表结构中
load data local infile '/路径/数据.log' into table 表名 fields
terminated by ',' lines terminated by '\n'
// 主键顺序插入性能高于乱序插入 在load时,主键顺序插入性能高于乱序插入
// fields 域,字段; terminated 终止;结束;终结(terminate 的过去分词)
主键优化
前面大批量插入数据提到,主键顺序插入的性能是要高于乱序插入的。这里介绍具体的原因,
1). 数据组织方式
在InnoDB存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表
行数据,都是存储在聚集索引的叶子节点上的。
在InnoDB引擎中,数据行是记录在逻辑结构 page 页中的,而每一个页的大小是固定的,默认16K。那也就意味着,一个页中所存储的行也是有限的,如果插入的数据行row在该页存储不下,将会存储到下一个页中,页与页之间会通过指针连接。
2). 页分裂
页可以为空,也可以填充一半,也可以填充100%。(如果一行数据过大,会行溢出),根据主键排列。
A. 主键顺序插入效果
①. 从磁盘中申请页, 主键顺序插入
B. 主键乱序插入效果
①. 假如1#,2#页都已经写满了,存放了如图所示的数据
②. 此时再插入id为50的记录,我们来看看会发生什么现象
会再次开启一个页,写入新的页中吗?
上述的这种现象,称之为 "页分裂",是比较耗费性能的操作。
3). 页合并
目前表中已有数据的索引结构(叶子节点)如下:
这个里面所发生的合并页的这个现象,就称之为 "页合并"。
MERGE_THRESHOLD:合并页的阈值,可以自己设置,在创建表或者创建索引时指定。
索引设计原则 !!!
满足业务需求的情况下,尽量降低主键的长度。
插入数据时,尽量选择顺序插入,选择使用AUTO_INCREMENT自增主键。
尽量不要使用UUID做主键或者是其他自然主键,如身份证号。因为是乱序插入
业务操作时,避免对主键的修改。
order by优化
MySQL的排序,有两种方式:
Using filesort : 通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sort
buffer中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。
Using index : 通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要
额外排序,操作效率高。
对于以上的两种排序方式,Using index的性能高,而Using filesort的性能低,在优化排序
操作时,尽量要优化为 Using index。 // using 使用利用 filesort 文件排序
删除索引的SQL
drop index 索引名 on 表名
// drop index idx_表名_表字段 on 表名;
查看表中有哪些索引
show index from 表名
例子: 此时的age和phone都没有索引
结论: 由于排序的字段都没有索引,所以排序的性能比较低
创建索引
create index 索引名 on 表名(表字段)
//标准化创建索引
create index idx_表名_表字段1_表字段2 on 表名(表字段1,表字段2);
创建索引后,根据age, phone进行升序排序
建立索引之后,再次进行排序查询,就由原来的Using filesort, 变为了 Using index,性能
就是比较高的了。
创建索引后,根据age, phone进行降序排序
也出现 Using index, 但是此时Extra中出现了 Backward index scan,这个代表反向扫描索引,在MySQL中创建的索引,默认索引的叶子节点是从小到大排序的,而此时查询排序时,是从大到小,所以,在扫描时,就是反向扫描,就会出现 Backward index scan。 在MySQL8版本中,支持降序索引,我们也可以创建降序索引。 // Backward 反向地
根据phone,age进行升序排序,phone在前,age在后。
排序时,也需要满足最左前缀法则,否则也会出现 filesort。因为在创建索引的时候, age是第一个
字段,phone是第二个字段,所以排序时,也就该按照这个顺序来,否则就会出现 Using
filesort。
根据age, phone进行降序一个升序,一个降序
因为创建索引时,如果未指定顺序,默认都是按照升序排序的,而查询时,一个升序,一个降序,此时就会出现Using filesort。
为了解决上述的问题,我们可以创建一个索引,这个联合索引中 age 升序排序,phone 倒序排序。
创建联合索引(age 升序排序,phone 倒序排序)
create index idx_user_age_phone_ad on tb_user(age asc ,phone desc);
创建联合索引排序
create index 索引名 on 表名(字段1 asc || desc,字段2 desc||asc);
在执行之前的语句
由上述的测试,我们得出order by优化原则:
A. 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则。
B. 尽量使用覆盖索引。
C. 多字段排序, 一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)。
D. 如果不可避免的出现filesort,大数据量排序时,可以适当增大排序缓冲区大小
sort_buffer_size(默认256k)。 // sort_buffer_size 缓冲区大小
group by优化
首先我们先将 tb_user 表的索引全部删除掉 。
删除索引的SQL
drop index 索引名 on 表名
// 索引名 ---> idx_表名_表字段_...
查看表中索引 ---> show index from 表名
在没有索引的情况下,执行如下SQL,查询执行计划(explain):
然后,我们在针对于 profession , age, status 创建一个联合索引。
create index idx_user_pro_age_sta on tb_user(profession , age , status);
创建联合索引的语句
create index idx_表名_表字段1_表字段2_.. on 表名(表字段1, 表字段2 , ...)
再执行前面相同的SQL查看执行计划。
再执行如下的分组查询SQL,查看执行计划:
结论,如果仅仅根据age分组,就会出现 Using temporary ;而如果是根据 profession,age两个字段同时分组,则不会出现 Using temporary。原因是因为对于分组操作,在联合索引中,也是符合最左前缀法则的。
所以,在分组操作中,需要通过以下两点进行优化,以提升性能:
A. 在分组操作时,可以通过索引来提高效率。
B. 分组操作时,索引的使用也是满足最左前缀法则的。
limit优化
在数据量比较大时,如果进行limit分页查询,在查询时,越往后,分页查询效率越低。
limit要优化的原因是,随着记录越来越多。越往后,分页查询效率越低,这就是分页查询的问题所在。
例子:当在进行分页查询时,如果执行 limit 2000000,10 ,此时需要MySQL排序前2000010 记
录,仅仅返回 2000000 - 2000010 的记录,其他记录丢弃,查询排序的代价非常大 。
优化思路: 一般分页查询时,通过创建 覆盖索引 能够比较好地提高性能,可以通过覆盖索引加子查
询形式进行优化。
(覆盖索引是指查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到)
//limit优化例子
//explain select * 表名A a ,
//(select id from 表名A order by id limit 2000000,10) b
//where a.id = b.id;
count优化
select count(*) from 表名 ; // 发现,如果数据量很大,在执行count操作时,是非常耗时的。
MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(*) 的时候会直接返回这个
数,效率很高; 但是如果是带条件的count,MyISAM也慢。
InnoDB 引擎就麻烦了,它执行 count(*) 的时候,需要把数据一行一行地从引擎里面读出
来,然后累积计数。
如果说要大幅度提升InnoDB表的count效率,
主要的优化思路:自己计数(可以借助于redis这样的数据库进行,但是如果是带条件的count又比较麻烦了)。
count用法 --->count() 是一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是 NULL,累计值就加 1,否则不加,最后返回累计值。
用法:count(*)、count(主键)、count(字段)、count(数字)
按照效率排序的话,count(字段) < count(主键 id) < count(1) ≈ count(*),所以尽
量使用 count(*)。
update优化
注意update语句执行时的注意事项。
当在执行更改的SQL语句时,如果是根据索引来锁定某一条记录的值时会锁定该id值这一行的数据,另一个B事务只要不是修改A事务要修改的这一条记录,就不会堵塞,如果B事务要修改当前A事务要修改的语句,那么就会发生堵塞(因为A事务把这条记录行所锁了,但是其他的行却可以操作),等待A事务提交完毕之后(A事务提交之后,行锁释放。),再去修改,某事务的行锁不释放,其他的事务操作该条的记录就一直堵塞,但是不影响其他事务操作其他没有行锁的事务
当开启多个事务,不是根据具有索引字段来确定的某一行记录的记录行,那么行锁就会升级为表锁。就是不是根据索引字段确定出来的行记录,不提交,就会把整张表给锁住,其他记录行就不能被其他事务给操作了 导致该update语句的性能大大降低。
例子:
当在执设置新值的SQL语句时,会锁定id为1这一行的数据,然后事务提交之后,行锁释放。之后其他的事务才能来操作这条记录,在该事务还未提交之前,其他事务只能来操作其他没有行锁的记录行,
但是当我们在执行如下SQL时。
当开启多个事务时,在执行上述的SQL时,就发现行锁升级为了表锁。升级为了表锁之后,就是事务没提交之前,其他记录行的数据也不允许其他的事务来修改 导致该update语句的性能大大降低。// 原因就是因为 name 字段不具有索引
结论:InnoDB的行锁是针对索引加的锁,不是针对记录加的锁 ,并且该索引不能失效,否则会行锁升级为表锁 。
视图/存储过程/触发器
视图
视图(View)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。
通俗的讲,视图只保存了查询的SQL逻辑,不保存查询结果。所以我们在创建视图的时候,主要的工作就落在创建这条SQL查询语句上。
创建试图
CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句 [ WITH [
CASCADED | LOCAL ] CHECK OPTION ]
create or replace view
视图名称
[(列名列表)] as select语句 [with [ cascaded | local ] check option ]
查询视图
查看创建视图语句:show create view 视图名称;
//查看创建视图语句:SHOW CREATE VIEW 视图名称;
查看视图数据:select * from 视图名称 ...... ;
//查看视图数据:SELECT * FROM 视图名称 ...... ;
修改视图
create [or replace] view 视图名称[(列名列表)] as select语句 [with |cascaded |local] check option
//方式一:CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句 [ WITH
//[ CASCADED | LOCAL ] CHECK OPTION ]
alter view 视图名称[(列名列表)] as select语句 [with [cascadea| local] check option ]
//方式二:ALTER VIEW 视图名称[(列名列表)] AS SELECT语句 [ WITH [ CASCADED |
//LOCAL ] CHECK OPTION ]
删除视图
drop view [if exists] 视图名称 [,视图名称] ...
//DROP VIEW [IF EXISTS] 视图名称 [,视图名称] ...
普通创建视图
//创建视图
create or replace view 视图名称 as select语句;
//查询视图
show create 视图名称; //查看创建视图的sql
select * from 视图名称; // 查看视图里面的数据
select * from 视图名称 SQL条件; // 创建出的视图尽管是虚拟表但是却可以当做一张真实表一样使用
//修改视图
create or replace view 视图名称 as select语句; //重复一次创建视图的语句,就是修改视图的语句
alter view 视图名称 as select语句; //重复一次创建视图的语句,就是修改视图的语句
//删除视图
drop view if exists 视图名称;
上述我们演示了,视图应该如何创建、查询、修改、删除,那么能不能通过视图来插入、更新数据
呢? 接下来,做一个测试。
create or replace view 视图名称 as select id,name from student where id <= 10 ;
select * from 视图名称;
insert into 视图名称 values(6,'ppc');
insert into 视图名称 values(17,'unicorn31');
执行上述的SQL,我们会发现,id为6和17的数据都是可以成功插入的。 但是我们在执行查询的时候,查询出来的数据,却没有id为17的记录。
因为我们在创建视图的时候,指定的条件为 id<=10, id为17的数据,是不符合条件的,所以没有查
询出来,但是这条数据确实是已经成功的插入到了基表中。
如果我们定义视图时,如果指定了条件,然后我们在插入、修改、删除数据时,是否可以做到必须满足条件才能操作,否则不能够操作呢? 答案是可以的,这就需要借助于视图的检查选项了。
检查选项
当使用with check option子句创建视图时,MySQL会通过视图检查正在更改的每个行,例如 插入,更新,删除,以使其符合视图的定义。 MySQL允许基于另一个视图创建视图,它还会检查依赖视图中的规则以保持一致性。为了确定检查的范围,mysql提供了两个选项: CASCADED 和 LOCAL ,默认值为 CASCADED 。 //
CASCADED级联。(cascaded)
比如,v2视图是基于v1视图的,如果在v2视图创建的时候指定了检查选项为 cascaded,但是v1视图创建时未指定检查选项。 则在执行检查时,不仅会检查v2,还会级联检查v2的关联视图v1。
LOCAL本地。(local)
比如,v2视图是基于v1视图的,如果在v2视图创建的时候指定了检查选项为 local ,但是v1视图创建时未指定检查选项。 则在执行检查时,只会检查v2,不会检查v2的关联视图v1。
视图的更新
要使视图可更新,视图中的行与基础表中的行之间必须存在一对一的关系。如果视图包含以下任何一项,则该视图不可更新:
A. 聚合函数或窗口函数(sum()、 min()、 max()、count()等)
B. distinct //DISTINCT
C. group by //GROUP BY
D. having //HAVING
E. union union all //UNION 或者 UNION ALL
视图作用
1).简单(视图不仅可以简化用户对数据的理解,也可以简化他们的操作。那些被经常使用的查询可以被定义为视图,从而使得用户不必为以后的操作每次指定全部的条件。)
2). 安全(数据库可以授权,但不能授权到数据库特定行和特定的列上。通过视图用户只能查询和修改他们所能见到的数据。)
3). 数据独立(视图可帮助用户屏蔽真实表结构变化带来的影响。)
案例:
存储过程
存储过程是事先经过编译并存储在数据库中的一段 SQL 语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。存储过程思想上很简单,就是数据库 SQL 语言层面的代码封装与重用。
1).封装,复用 -----------------------> 可以把某一业务SQL封装在存储过程中,需要用到的时候直接调用即可。
2).可以接收参数,也可以返回数据 --------> 再存储过程中,可以传递参数,也可以接收返回值。
3).减少网络交互,效率提升 -------------> 如果涉及到多条SQL,每执行一次都是一次网络传
输。 而如果封装在存储过程中,只需要网络交互一次可能就可以了。
创建||定义 存储过程
create procedure 存储过程名称 ([ 参数列表 ])
begin
-- SQL语句
end ;
调用
call 创建存储过程时使用的名称([ 参数 ]);
查看
select * from information_schema.ROUTINES where ROUTINE_SCHEMA = '数据库名';
//查询指定数据库的存储过程及状态信息
show create procedure 存储过程名称 ; -- 查询某个存储过程的定义
删除
drop procedure if exists 存储过程名称 ;
//注意:
//在命令行中,执行创建存储过程的SQL时,需要通过关键字 delimiter 指定SQL语句的
//结束符。
delimiter的简介
delimiter是告诉mysql解释器,该段命令是否已经结束了,mysql是否可以执行了。
默认情况下,delimiter是分号;
命令行客户端中,如果有一行命令以分号结束, 那么回车后,mysql将会执行该命令。
但有时候,不希望MySQL这么做。在为可能输入较多的语句,且语句中包含有分号。 这种情况下,就需要事先把delimiter换成其它符号,如//、$$或者;;。
mysql>delimiter //
示例:创建一个存储过程,在创建该存储过程之前,将delimiter分隔符转换成符号“//”,最后在转换回符号“;”
-- 将结束标志符更改为//
delimiter //
-- 创建存储过程
create or procedure 存储过程名称([])
begin
sql语句;
end
//
-- 将结束标志符更改回分号
delimiter ;
上面就是,先将分隔符设置为 //, 直到遇到下一个 //,才整体执行语句。
执行完后,最后一行, delimiter ; 将mysql的分隔符重新设置为分号;
如果不修改的话,本次会话中的所有分隔符都以// 为准。
MySQL存储过程的创建语法
CREATE PROCEDURE 过程名([[IN|OUT|INOUT] 参数名 数据类型[,[IN|OUT|INOUT] 参数名 数据类型…]]) [特性 ...] 过程体
MySQL默认以";"为分隔符,
如果没有声明分割符,则编译器会把存储过程当成SQL语句进行处理,因此编译过程会报错,
所以要事先用“delimiter //”声明当前段分隔符,让编译器把两个"//"之间的内容当做存储过程的代码,不会执行这些代码;
“delimiter ;”的意为把分隔符还原。
参数
存储过程根据需要可能会有输入、输出、输入输出参数,如果有多个参数用","分割开。MySQL存储过程的参数用在存储过程的定义,共有三种参数类型,in,out,inout,//笔记在后面
IN参数的值必须在调用存储过程时指定,在存储过程中修改该参数的值不能被返回,为默认值 OUT:该值可在存储过程内部被改变,并可返回 INOUT:调用时指定,并且可被改变和返回
过程体--->过程体的开始与结束使用begin与end进行标识。
declare: 存储过程及函数内部定义变量
在MySQL中变量分为三种类型: 系统变量、用户定义变量、局部变量。
系统变量 是MySQL服务器提供,不是用户定义的,属于服务器层面。分为全局变量(global)、会话变量(session)。
查看系统变量
show [ session | global ] variables ; -- 查看所有系统变量
show [ session | global ] variables like '......'; -- 可以通过LIKE模糊匹配方式查找变量
select @@[session | global] 系统变量名; -- 查看指定变量的值
select @@global.系统变量名; -- 查看指定变量的值
设置系统变量
set [ session| global ] 系统变量名 = 值 ;
set @@[session | global]系统变量名 = 值 ;
注意:
如果没有指定 session/global,默认是session,会话变量。
mysql服务重新启动之后,所设置的全局参数会失效,要想不失效,可以在 /etc/my.cnf 中配置。
// 全局变量(global): 全局变量针对于所有的会话。
// 会话变量(session): 会话变量针对于单个会话,在另外一个会话窗口就不生效了。
例子:
用户定义变量 是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用 "@变量名" 使用就可以。其作用域为当前连接。
1). 赋值 //这里的var_name 就是用户定义变量的变量名
方式一:
SET @var_name = expr [, @var_name = expr] ... ;
SET @var_name := expr [, @var_name := expr] ... ;
//赋值时,可以使用 = ,也可以使用 := 。
方式二:
SELECT @var_name := expr [, @var_name := expr] ... ;
SELECT 字段名 INTO @var_name FROM 表名;
//把查询出来的值交给var_name这个变量,前提是查询出来的必须是单行单列一个值才能使用这种,
//多行多列在笔记后面
2). 使用
SELECT @var_name ;
//注意: 用户定义的变量无需对其进行声明或初始化,只不过获取到的值为NULL。
例子:
局部变量 是根据需要定义的在局部生效的变量,访问之前,需要declare声明。可用作存储过程内的局部变量和输入参数,局部变量的范围是在其内声明的begin ... end块。
//declare: 存储过程及函数内部定义变量 (declare: 宣布,声明;断言)
1). 声明
declare 变量名 变量类型 [default ... ] ;
//DECLARE 变量名 变量类型 [DEFAULT ... ] ;
//变量类型就是数据库字段类型:INT、BIGINT、CHAR、VARCHAR、DATE、TIME等。
//declare 宣布,声明;断言
2). 赋值
set 变量名 = 值 ;
set 变量名 := 值 ;
select 字段名 into 变量名 from 表名 ... ;
例子:
if 用于做条件判断,具体的语法结构为:
if 条件1 then
.....
elseif 条件2 then -- 可选
.....
else -- 可选
.....
end if;
//在if条件判断的结构中,elseif 结构可以有多个,也可以没有。 ELSE结构可以有,也可以没有。
// then 接下来,然后;
例子:
上述的需求我们虽然已经实现了,但是也存在一些问题,比如:score 分数我们是在存储过程中定义死的,而且最终计算出来的分数等级,也仅仅是最终查询展示出来而已。
那么能不能,把score分数动态的传递进来,计算出来的分数等级是否可以作为返回值返回呢?
答案是肯定的,可以通过接下来所讲解的 参数 来解决上述的问题。
参数
参数的类型,主要分为以下三种:in、out、inout。 具体的含义如下:
例子1:
例子2:
case 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;
// statement sql语句 condition1 状况,状态;条件 search 查找
例子:
注意:如果判定条件有多个,多个条件之间,可以使用 and 或 or 进行连接。
while 循环是有条件的循环控制语句。满足条件后,再执行循环体中的SQL语句。
//先判定条件,如果条件为true,则执行逻辑,否则,不执行逻辑
while 条件 do
SQL逻辑...
end while;
例子:
repeat是有条件的循环控制语句, 当满足until声明的条件的时候,则退出循环
//先执行一次逻辑,然后判定until条件是否满足,如果满足,则退出。如果不满足,则继续下一次循环
repeat
SQL逻辑...
until 条件
end repeat;
//until 到……时,直到……为止;直到(某处)
例子:
LOOP(循环) 实现简单的循环,如果不在SQL逻辑中增加退出循环的条件,可以用其来实现简单的死循环。
LOOP可以配合一下两个语句使用:
leave :配合循环使用,退出循环。
iterate:必须用在循环中,作用是跳过当前循环剩下的语句,直接进入下一次循环。
//iterate 迭代;重复;反复说;重做
[begin_label:] loop
SQL逻辑...
end loop [end_label];
leave label; -- 退出指定标记的循环体
iterate label; -- 直接进入下一次循环
//上述语法中出现的 begin_label,end_label,label 指的都是自己所自定义的标记。
例子:
例子2:
游标
游标(cursor)是用来存储查询结果集的数据类型 , 在存储过程和函数中可以使用游标对结果集进
行循环的处理。游标的使用包括游标的声明、open、fetch和 close,其语法分别如下。
A. 声明游标
declare 游标名称 cursor for 查询语句 ;
//DECLARE 游标名称 CURSOR FOR 查询语句 ; //declare宣布,声明;断言
B. 打开游标
open 游标名称 ;
//OPEN 游标名称 ;
C. 获取游标记录
fetch 游标名称 into 变量 [, 变量 ] ; //fetch 获取,拿来,提取
//FETCH 游标名称 INTO 变量 [, 变量 ] ;
D. 关闭游标
close 游标名称 ;
//CLOSE 游标名称 ;
使用游标步骤
逻辑:
1. 声明游标, 存储查询结果集
2. 准备: 创建表结构
3. 开启游标
4. 获取游标中的记录
5. 插入数据到新表中
6. 关闭游标
例子:根据传入的参数uage,来查询用户表tb_user中,所有的用户年龄小于等于uage的用户姓名
(name)和专业(profession),并将用户的姓名和专业插入到所创建的一张新表
(id,name,profession)中。
上述的功能,虽然我们实现了,但是逻辑并不完善,而且程序执行完毕,获取不到数据,数据库还报错。 接下来,我们就需要来完成这个存储过程,并且解决这个问题。
要想解决这个问题,就需要通过MySQL中提供的 条件处理程序 Handler 来解决。
条件处理程序(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代码的简写
例子:
具体的错误状态码,可以参考官方文档:
具体的错误状态码,可以参考官方文档2
存储函数
存储函数是有返回值的存储过程,存储函数的参数只能是IN类型的。具体语法如下:
create function 存储函数名称 ([ 参数列表 ])
returns type [characteristic ...]
begin
-- SQL语句
return ...;
end ;
// returns回报;收益;[贸易]退货(return 的复数)返回;回复;归还(return 的单三形式)
characteristic说明: //deterministic 确定性的;命运注定论的
DETERMINISTIC:相同的输入参数总是产生相同的结果
NO SQL :不包含 SQL 语句。
READS SQL DATA:包含读取数据的语句,但不包含写入数据的语句。
触发器
触发器是与表有关的数据库对象,指在insert/update/delete之前(BEFORE)或之后(AFTER),触
发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性 , 日志记录 , 数据校验等操作 。
使用别名OLD和NEW来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。现在触发器还
只支持行级触发,不支持语句级触发。
创建
create trigger trigger_name // trigger_name 我们自己创建的触发器名
before/after insert/update/delete
ON 要做触发器的表名 for each row //行级触发器
BEGIN
trigger_stmt ; //这里我们需要手写触发器被触发时要往trigger_name里面加入的SQL语句
END;
查看
show triggers //show triggers;– 查看触发器
删除
DROP TRIGGER [schema_name.]trigger_name ; // 如果没有指定 schema_name,默认为当前数
据库 。
//trigger 触发器,触发
例子:
通过触发器记录 tb_user 表的数据变更日志,将变更日志插入到日志表user_logs中, 包含增加,
修改 , 删除 ;
表结构准备:
插入数据触发器 // create trigger trigger_name trigger_name 我们自己创建的触发器名
最终要的是一定要有一个触发后产生数据存在的表,这里是user_logs,一张表就可以多次记录触发情况,
测试完毕之后,检查日志表中的数据是否可以正常插入,以及插入数据的正确性。那么就需要我们去查看触发器名对应的表了,这里是user_logs ,查看这个表也是用select * from user_logs
例子:修改数据触发器
测试完毕之后,检查日志表中的数据是否可以正常插入,以及插入数据的正确性。
例子:删除数据触发器
测试完毕之后,检查日志表中的数据是否可以正常插入,以及插入数据的正确性。
锁
锁是计算机协调多个进程或线程并发访问某一资源的机制,在数据库中,数据也是一种供许多用户共享的资源。
MySQL中的锁,按照锁的粒度分,分为以下三类:
全局锁:锁定数据库中的所有表。
表级锁:每次操作锁住整张表。
行级锁:每次操作锁住对应的行数据。
如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
全局锁
全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML语句,DDL语
句,已经更新操作的事务提交语句都将被阻塞。
典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。
为什么全库逻辑备份,就需要加全就锁呢?
B. 再来分析一下加了全局锁后的情况
1). 加全局锁
flush tables with read lock ;
2). 数据备份
mysqldump -u用户名 –p密码 数据库 > 自定义的名字.sql // 路径/自定义的名字.sql
//例子: mysqldump -uroot –p123456 database_name > sql名字.sql
3). 释放锁
unlock tables ;
全局锁特点 ---> 数据库中加全局锁,是一个比较重的操作,存在以下问题:
如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。
如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导
致主从延迟。
在InnoDB引擎中,可以在备份时加上参数 --single-transaction 参数来完成不加锁的一致性数据备份。mysqldump --single-transaction -u用户名 –p密码 要备份的数据库名 > 自定义的名字.sql
表级锁
表级锁,每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在MyISAM、InnoDB、BDB等存储引擎中。
对于表级锁,主要分为以下三类:表锁,元数据锁(meta data lock,MDL),意向锁
//对于表锁,分为两类:表共享读锁(read lock)表独占写锁(write lock)
语法:
加锁:lock tables 表名... read/write。
释放锁:unlock tables / 客户端断开连接 。
B. 写锁
结论: 读锁不会阻塞其他客户端的读,但是会阻塞写。写锁既会阻塞其他客户端的读,又会阻塞
其他客户端的写。
元数据锁
meta data lock , 元数据锁,简写MDL。
MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。为了避免DML与DDL冲突,保证读写的正确性。这里的元数据,可以简单理解为就是一张表的表结构。 也就是说,某一张表涉及到未提交的事务时,是不能够修改这张表的表结构的。
在MySQL5.5中引入了MDL(元数据锁)后,当对一张表进行增删改查的时候,它会自动加MDL读锁(共享);当对表结构进行变更操作的时候,它会自动加MDL写锁(排他)。
常见的SQL操作时,所添加的元数据锁:
我们可以通过下面的SQL,来查看数据库中的元数据锁的情况:
select object_type,object_schema,object_name,lock_type,lock_duration from
performance_schema.metadata_locks ;
意向锁
为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。
分类
意向共享锁(IS): 由语句select ... lock in share mode添加 。 与 表锁共享锁(read)兼容,与表锁排他锁(write)互斥。
意向排他锁(IX): 由insert、update、delete、select...for update添加 。与表锁共享锁(read)及排他锁(write)都互斥,意向锁之间不会互斥。
一旦事务提交了,意向共享锁、意向排他锁,都会自动释放。
可以通过以下SQL,查看意向锁及行锁的加锁情况:
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):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。
(默认情况下,InnoDB在 REPEATABLE READ(可重复读)事务隔离级别运行,InnoDB使用 next-key(临键锁)锁进行搜索和索引扫描,以防止幻读。
针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
InnoDB的行锁是针对于索引加的锁,不通过索引条件检索数据,那么InnoDB将对表中的所有记
录加锁,此时 就会升级为表锁。
可以通过以下SQL,查看意向锁及行锁的加锁情况:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from
performance_schema.data_locks;
例子:
B. select...lock in share mode,加共享锁,共享锁与共享锁之间兼容。
共享锁与排他锁之间互斥。
客户端一获取的是id为1这行的共享锁,客户端二是可以获取id为3这行的排它锁的,因为不是同一行数据。 而如果客户端二想获取id为1这行的排他锁,会处于阻塞状态,以为共享锁与排他锁之间互斥。
C. 排它锁与排他锁之间互斥
当客户端一,执行update语句,会为id为1的记录加排他锁; 客户端二,如果也执行update语句更
新id为1的数据,也要为id为1的数据加排他锁,但是客户端二会处于阻塞状态,因为排他锁之间是互斥的。 直到客户端一,把事务提交了,才会把这一行的行锁释放,此时客户端二,解除阻塞。
D. 无索引行锁升级为表锁
当在两个客户端中执行如下操作:
在客户端一中,开启事务,并执行update语句,更新name为Lily的数据,也就是id为19的记录 。
然后在客户端二中更新id为3的记录,却不能直接执行,会处于阻塞状态,为什么呢?
原因就是因为此时,客户端一,根据name字段进行更新时,name字段是没有索引的,如果没有索引,此时行锁会升级为表锁(因为行锁是对索引项加的锁,而name没有索引)。
接下来,我们再针对name字段建立索引,索引建立之后,再次做一个测试:
此时我们可以看到,客户端一,开启事务,然后依然是根据name进行更新。而客户端二,在更新id为3的数据时,更新成功,并未进入阻塞状态。 这样就说明,我们根据索引字段进行更新操作,就可以避免行锁升级为表锁的情况。
间隙锁&临键锁
默认情况下,InnoDB在 REPEATABLE READ(可重复读)事务隔离级别运行,InnoDB使用 next-key(临键锁)锁进行搜索和索引扫描,以防止幻读。
1.索引上的等值查询(唯一索引),给不存在的记录加锁时, 优化为间隙锁 。
2.索引上的等值查询(非唯一普通索引),向右遍历时最后一个值不满足查询需求时,next-key
lock 退化为间隙锁。
3.索引上的范围查询(唯一索引)--会访问到不满足条件的第一个值为止。
注意:间隙锁唯一目的是防止其他事务插入间隙。间隙锁可以共存,一个事务采用的间隙锁不会
阻止另一个事务在同一间隙上采用间隙锁。
A. 索引上的等值查询(唯一索引),给不存在的记录加锁时, 优化为间隙锁 。
B. 索引上的等值查询(非唯一普通索引),向右遍历时最后一个值不满足查询需求时,next-key
lock 退化为间隙锁。
介绍分析一下:
我们知道InnoDB的B+树索引,叶子节点是有序的双向链表。 假如,我们要根据这个二级索引查询值
为18的数据,并加上共享锁,我们是只锁定18这一行就可以了吗? 并不是,因为是非唯一索引,这个
结构中可能有多个18的存在,所以,在加锁时会继续往后找,找到一个不满足条件的值(当前案例中也
就是29)。此时会对18加临键锁,并对29之前的间隙加锁。
C. 索引上的范围查询(唯一索引)--会访问到不满足条件的第一个值为止。
查询的条件为id>=19,并添加共享锁。 此时我们可以根据数据库表中现有的数据,将数据分为三个部分: [19] (19,25] (25,+∞]
所以数据库数据在加锁时,就是将19加了行锁,25的临键锁(包含25及25之前的间隙),正无穷的临键锁(正无穷及之前的间隙)。
InnoDB引擎
逻辑存储结构
InnoDB的逻辑存储结构如下图所示:
1). 表空间
表空间是InnoDB存储引擎逻辑结构的最高层, 如果用户启用了参数 innodb_file_per_table(在
8.0版本中默认开启) ,则每张表都会有一个表空间(xxx.ibd),一个mysql实例可以对应多个表空
间,用于存储记录、索引等数据。
2). 段
段,分为数据段、索引段、回滚段,InnoDB是索引组织表,数据段就是B+树的叶子节点, 索引段即为B+树的非叶子节点。段用来管理多个Extent(区)。
3). 区
区,表空间的单元结构,每个区的大小为1M。 默认情况下, InnoDB存储引擎页大小为16K(存储引擎页就是一个区里面的每个page), 即一个区中一共有64个连续的页。
4). 页
页,是InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默认为 16KB。为了保证页的连续性,
InnoDB 存储引擎每次从磁盘申请 4-5 个区。
5). 行
行,InnoDB 存储引擎数据是按行进行存放的。
在行中,默认有两个隐藏字段:
Trx_id:每次对某条记录进行改动时,都会把对应的事务id赋值给trx_id隐藏列。
Roll_pointer:每次对某条引记录进行改动时,都会把旧的版本写入到undo日志中,然后这个
隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。
架构
MySQL5.5 版本开始,默认使用InnoDB存储引擎,它擅长事务处理,具有崩溃恢复特性,在日常开发中使用非常广泛。下面是InnoDB架构图,左侧为内存结构,右侧为磁盘结构。
内存结构
在内存结构中,主要分为四大块儿: Buffer Pool、Change Buffer、Adaptive
Hash Index、Log Buffer。
1). Buffer Pool
InnoDB存储引擎基于磁盘文件存储,访问物理硬盘和在内存中进行访问,速度相差很大,为了尽可能
弥补这两者之间的I/O效率的差值,就需要把经常使用的数据加载到缓冲池中,避免每次访问都进行磁
盘I/O。
在InnoDB的缓冲池中不仅缓存了索引页和数据页,还包含了undo页、插入缓存、自适应哈希索引以及
InnoDB的锁信息等等。
缓冲池 Buffer Pool,是主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增
删改查操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从磁盘加载并缓存),然后再以一定频
率刷新到磁盘,从而减少磁盘IO,加快处理速度。
缓冲池以Page页为单位,底层采用链表数据结构管理Page。根据状态,将Page分为三种类型:
• free page:空闲page,未被使用。
• clean page:被使用page,数据没有被修改过。
• dirty page:脏页,被使用page,数据被修改过,也中数据与磁盘的数据产生了不一致。
在专用服务器上,通常将多达80%的物理内存分配给缓冲池 。参数设置: show variables
like 'innodb_buffer_pool_size';
2). Change Buffer
Change Buffer,更改缓冲区(针对于非唯一二级索引页),在执行DML语句时,如果这些数据Page
没有在Buffer Pool中,不会直接操作磁盘,而会将数据变更存在更改缓冲区 Change Buffer
中,在未来数据被读取时,再将数据合并恢复到Buffer Pool中,再将合并后的数据刷新到磁盘中。
Change Buffer的意义是什么呢?
先来看一幅图,这个是二级索引的结构图:
与聚集索引不同,二级索引通常是非唯一的,并且以相对随机的顺序插入二级索引。同样,删除和更新可能会影响索引树中不相邻的二级索引页,如果每一次都操作磁盘,会造成大量的磁盘IO。有了ChangeBuffer之后,我们可以在缓冲池中进行合并处理,减少磁盘IO。
3). Adaptive Hash Index
自适应hash索引,用于优化对Buffer Pool数据的查询。MySQL的innoDB引擎中虽然没有直接支持
hash索引,但是给我们提供了一个功能就是这个自适应hash索引。因为前面我们讲到过,hash索引在
进行等值匹配时,一般性能是要高于B+树的,因为hash索引一般只需要一次IO即可,而B+树,可能需
要几次匹配,所以hash索引的效率要高,但是hash索引又不适合做范围查询、模糊匹配等。
InnoDB存储引擎会监控对表上各索引页的查询,如果观察到在特定的条件下hash索引可以提升速度,
则建立hash索引,称之为自适应hash索引。
自适应哈希索引,无需人工干预,是系统根据情况自动完成。
参数: adaptive_hash_index
4). Log Buffer
Log Buffer:日志缓冲区,用来保存要写入到磁盘中的log日志数据(redo log 、undo log),
默认大小为 16MB,日志缓冲区的日志会定期刷新到磁盘中。如果需要更新、插入或删除许多行的事
务,增加日志缓冲区的大小可以节省磁盘 I/O。
参数:
innodb_log_buffer_size:缓冲区大小
innodb_flush_log_at_trx_commit:日志刷新到磁盘时机,取值主要包含以下三个:
1: 日志在每次事务提交时写入并刷新到磁盘,默认值。
2: 每秒将日志写入并刷新到磁盘一次。 3: 日志在每次事务提交后写入,并每秒刷新到磁盘一次。
磁盘结构
1). System Tablespace
系统表空间是更改缓冲区的存储区域。如果表是在系统表空间而不是每个表文件或通用表空间中创建的,它也可能包含表和索引数据。(在MySQL5.x版本中还包含InnoDB数据字典、undolog等)
参数:innodb_data_file_path
系统表空间,默认的文件名叫 ibdata1。
2). File-Per-Table Tablespaces
如果开启了innodb_file_per_table开关 ,则每个表的文件表空间包含单个InnoDB表的数据和索
引 ,并存储在文件系统上的单个数据文件中。
开关参数:innodb_file_per_table ,该参数默认开启。
那也就是说,我们没创建一个表,都会产生一个表空间文件,如图:
3). General Tablespaces
通用表空间,需要通过 CREATE TABLESPACE 语法创建通用表空间,在创建表时,可以指定该表空
间。
A. 创建表空间
CREATE TABLESPACE 表空间名字 ADD DATAFILE 'filename.idb' ENGINE = engine_name;
B. 创建表时指定表空间
CREATE TABLE 表名(...建表的SQL) TABLESPACE 表空间名字;
4). Undo Tablespaces
撤销表空间,MySQL实例在初始化时会自动创建两个默认的undo表空间(初始大小16M),用于存储
undo log日志。
5). Temporary Tablespaces
InnoDB 使用会话临时表空间和全局临时表空间。存储用户创建的临时表等数据。
6). Doublewrite Buffer Files
双写缓冲区,innoDB引擎将数据页从Buffer Pool刷新到磁盘前,先将数据页写入双写缓冲区文件
中,便于系统异常时恢复数据。
7). Redo Log
重做日志,是用来实现事务的持久性。该日志文件由两部分组成:重做日志缓冲(redo log
buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中。当事务提交之后会把所
有修改信息都会存到该日志中, 用于在刷新脏页到磁盘时,发生错误时, 进行数据恢复使用。
以循环方式写入重做日志文件,涉及两个文件:
前面我们介绍了InnoDB的内存结构,以及磁盘结构,那么内存中我们所更新的数据,又是如何到磁盘
中的呢? 此时,就涉及到一组后台线程,接下来,就来介绍一些InnoDB中涉及到的后台线程。
后台线程
在InnoDB的后台线程中,分为4类,分别是:Master Thread 、IO Thread、Purge Thread、
Page Cleaner Thread。
1). Master Thread
核心后台线程,负责调度其他线程,还负责将缓冲池中的数据异步刷新到磁盘中, 保持数据的一致性, 还包括脏页的刷新、合并插入缓存、undo页的回收 。
2). IO Thread
在InnoDB存储引擎中大量使用了AIO来处理IO请求, 这样可以极大地提高数据库的性能,而IO
Thread主要负责这些IO请求的回调。
可以通过以下的这条指令,查看到InnoDB的状态信息,其中就包含IO Thread信息。
show engine innodb status \G;
3). Purge Thread
主要用于回收事务已经提交了的undo log,在事务提交之后,undo log可能不用了,就用它来回
收。
4). Page Cleaner Thread
协助 Master Thread 刷新脏页到磁盘的线程,它可以减轻 Master Thread 的工作压力,减少阻
塞。
事务原理
事务基础
1).事务 是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
2). 特性
1).原子性:事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
2).一致性:事务完成时,必须使所有的数据都保持一致状态。
3).隔离性:数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
4).持久性:事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
InnoDB引擎是如何保证事务的这四大特性的。对于这四大特性,实际上分为两个部分。 其中的原子性、一致性、持久化,实际上是由InnoDB中的两份日志来保证的,一份是redo log日志,一份是undo log日志。 而持久性是通过数据库的锁,加上MVCC来保证的。
// redo 重做;重新装饰 undo 撤消;撤销;取消
redo log 重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。
该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log
file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中, 用
于在刷新脏页到磁盘,发生错误时, 进行数据恢复使用。
如果没有redolog,可能会存在什么问题的? 我们一起来分析一下。
在InnoDB引擎中的内存结构中,主要的内存区域就是缓冲池,在缓冲池中缓存了很多的数据页。 当我们在一个事务中,执行多个增删改的操作时,InnoDB引擎会先操作缓冲池中的数据,如果缓冲区没有对应的数据,会通过后台线程将磁盘中的数据加载出来,存放在缓冲区中,然后将缓冲池中的数据修改,修改后的数据页我们称为脏页。 而脏页则会在一定的时机,通过后台线程刷新到磁盘中,从而保证缓冲区与磁盘的数据一致。 而缓冲区的脏页数据并不是实时刷新的,而是一段时间之后将缓冲区的数据刷新到磁盘中,假如刷新到磁盘的过程出错了,而提示给用户事务提交成功,而数据却没有持久化下来,这就出现问题了,没有保证事务的持久性。
那么,如何解决上述的问题呢? 在InnoDB中提供了一份日志 redo log,接下来我们再来分析一
下,通过redolog如何解决这个问题。
有了redolog之后,当对缓冲区的数据进行增删改之后,会首先将操作的数据页的变化,记录在redo log buffer中。在事务提交时,会将redo log buffer中的数据刷新到redo log磁盘文件中。
过一段时间之后,如果刷新缓冲区的脏页到磁盘时,发生错误,此时就可以借助于redo log进行数据恢复,这样就保证了事务的持久性。 而如果脏页成功刷新到磁盘 或 或者涉及到的数据已经落盘,此时redolog就没有作用了,就可以删除了,所以存在的两个redolog文件是循环写的。
那为什么每一次提交事务,要刷新redo log 到磁盘中呢,而不是直接将buffer pool中的脏页刷新
到磁盘呢 ?因为在业务操作中,我们操作数据一般都是随机读写磁盘的,而不是顺序读写磁盘。 而redo log在往磁盘文件中写入数据,由于是日志文件,所以都是顺序写的。顺序写的效率,要远大于随机写。 这种先写日志的方式,称之为 WAL(Write-Ahead Logging)。
undo log 回滚日志,用于记录数据被修改前的信息 , 作用包含两个 : 提供回滚(保证事务的原子性) 和 MVCC(多版本并发控制) 。undo log和redo log记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。Undo log销毁:undo log在事务执行时产生,事务提交时,并不会立即删除undo log,因为这些日志可能还用于MVCC。Undo log存储:undo log采用段的方式进行管理和记录,存放在前面介绍的 rollback segment 回滚段中,内部包含1024个undo log segment。
MVCC
基本概念
1). 当前读
读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。对于我们日常的操作,如:select ... lock in share mode(共享锁),select ... for update、update、insert、delete(排他锁)都是一种当前读。
测试:
在测试中我们可以看到,即使是在默认的RR隔离级别下,事务A中依然可以读取到事务B最新提交的内容,因为在查询语句后面加上了 lock in share mode 共享锁,此时是当前读操作。当然,当我们加排他锁的时候,也是当前读操作。
2). 快照读
简单的select(不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。
• Read Committed:每次select,都生成一个快照读。
• Repeatable Read:开启事务后第一个select语句才是快照读的地方。
• Serializable:快照读会退化为当前读。
在测试中,我们看到即使事务B提交了数据,事务A中也查询不到。 原因就是因为普通的select是快照
读,而在当前默认的RR隔离级别下,开启事务后第一个select语句才是快照读的地方,后面执行相同的select语句都是从快照中获取数据,可能不是当前的最新数据,这样也就保证了可重复读。
3). MVCC
MVCC全称多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,快照读为MySQL实现MVCC提供了一个非阻塞读功能。MVCC的具体实现,还需要依赖于数据库记录中的三个隐式字段、undo log日志、readView。
隐藏字段
例子:
当创建了上面的这张表,那么当在查看表结构的时候,就可以显式的看到这三个字段。 实际上除了这三个字段以外,InnoDB会自动的添加三个隐藏字段及其含义分别是:
而上述的前两个字段是肯定会添加的, 是否添加最后一个字段DB_ROW_ID,得看当前表有没有主键,如果有主键,则不会添加该隐藏字段。
测试
1). 查看有主键的表 --->某表
进入服务器中的 /var/lib/mysql/数据库名/ , 查看某表名的表结构信息, 通过如下指令:
ibd2sdi 某表名.ibd // 打开二进制文件 ibd2sdi 表名.ibd
查看到的表结构信息中,有一栏 columns,在其中我们会看到处理我们建表时指定的字段以外,还有额外的两个字段 分别是:DB_TRX_ID 、 DB_ROLL_PTR ,因为该表有主键,所以没有DB_ROW_ID隐藏字段。
2). 查看没有主键的表 --->某表
此时,我们再通过以下指令来查看表结构及其其中的字段信息:ibd2sdi 某表名.ibd
查看到的表结构信息中,有一栏 columns,在其中我们会看到处理我们建表时指定的字段以外,还有额外的三个字段 分别是:DB_TRX_ID 、 DB_ROLL_PTR 、DB_ROW_ID,因为该表是没有指定主键的。
undolog回滚日志,在insert、update、delete的时候产生的便于数据回滚的日志。
当insert的时候,产生的undo log日志只在回滚时需要,在事务提交后,可被立即删除。
而update、delete的时候,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会立即
被删除。
版本链
有一张表原始数据为:
DB_TRX_ID : 代表最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID,是
自增的。
DB_ROLL_PTR : 由于这条数据是才插入的,没有被更新过,所以该字段值为null。
然后,有四个并发事务同时在访问这张表。
最终发现,不同事务或相同事务对同一条记录进行修改,会导致该记录的undolog生成一条
记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。
readview
ReadView(读视图)是 快照读 SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id。
而在readview中就规定了版本链数据的访问规则:trx_id 代表当前undolog版本链对应事务ID。
不同的隔离级别,生成ReadView的时机不同:
READ COMMITTED :在事务中每一次执行快照读时生成ReadView。
REPEATABLE READ:仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。
原理分析
RC隔离级别, 在RC隔离级别下,在事务中每一次执行快照读时生成ReadView。
RR隔离级别
在RR隔离级别下,仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。 而RR 是可重复读,在一个事务中,执行两次相同的select语句,查询到的结果是一样的。
那MySQL是如何做到可重复读的呢?
MySQL管理
系统数据库 --->Mysql数据库安装完成后,自带了一下四个数据库,具体作用如下:
常用工具mysql--->该mysql不是指mysql服务,而是指mysql的客户端工具。
语法 :
mysql [options] [database]
选项 :
-u, --user=name //指定用户名
-p, --password[=name] //指定密码
-h, --host=name //指定服务器IP或域名
-P, --port=port //指定连接端口
-e, --execute=name //执行SQL语句并退出
例子: 登录 mysql -h连接的ip地址 -u用户名 -p用户密码
-e选项可以在Mysql客户端执行SQL语句,而不用连接到MySQL数据库再执行,对于一些批处理脚本,这种方式尤其方便。
mysql -uroot –p用户密码 数据库名 -e "SQL语句"; //执行完毕后不会登录到mysql客户端,
示例:mysql -uroot –p用户密码 数据库名 -e "select * from 表名"; 能查看到返回信息,执行完毕,却不会进入到mysql命令行
mysqladmin---> mysqladmin 是一个执行管理操作的客户端程序。可以用它来检查服务器的配置和当前状态、创建并删除数据库等。
通过帮助文档查看选项:mysqladmin --help
语法:
mysqladmin [options] command ...
选项:
-u, --user=name //指定用户名
-p, --password[=name] //指定密码
-h, --host=name //指定服务器IP或域名
-P, --port=port //指定连接端口
示例:
mysqladmin -u用户名 –p用户密码 drop '数据库名' //删除某数据库 注意;分号的有无
mysqladmin -u用户名 –p用户密码 version //查看数据库版本信息 切记这里结束时不能加分号
mysqlbinlog
由于服务器生成的二进制日志文件以二进制格式保存,所以如果想要检查这些文本的文本格式,就会使
用到mysqlbinlog 日志管理工具。
语法 :
mysqlbinlog [options] log-files1 log-files2 ...
选项 :
-d, --database=name 指定数据库名称,只列出指定的数据库相关操作。
-o, --offset=# 忽略掉日志中的前n行命令。
-r,--result-file=name 将输出的文本格式日志输出到指定文件。
-s, --short-form 显示简单格式, 省略掉一些信息。
--start-datatime=date1 --stop-datetime=date2 指定日期间隔内的所有日志。
--start-position=pos1 --stop-position=pos2 指定位置间隔内的所有日志。
mysqlshow --->mysqlshow 客户端对象查找工具,用来很快地查找存在哪些数据库、数据库中的表、表中的列或者索引。
语法 :
mysqlshow [options] [db_name [table_name [col_name]]]
选项 :
--count 显示数据库及表的统计信息(数据库,表 均可以不指定)
-i 显示指定数据库或者指定表的状态信息
示例:
//查询test库中每个表中的字段数,及行数
mysqlshow -u用户名 -p用户密码 test --count
//查询test库中book表的详细情况
mysqlshow -u用户名 -p用户密码 test book --count
示例:
mysqldump---> mysqldump 客户端工具用来备份数据库或在不同数据库之间进行数据迁移。备份内容包含创建表,及插入表的SQL语句。 //dump 转储
语法 :
mysqldump [options] db_name [tables]
mysqldump [options] --database/-B db1 [db2 db3...]
mysqldump [options] --all-databases/-A
连接选项 :
-u, --user=name 指定用户名
-p, --password[=name] 指定密码
-h, --host=name 指定服务器ip或域名
-P, --port=# 指定连接端口
输出选项:
--add-drop-database 在每个数据库创建语句前加上 drop database 语句
--add-drop-table 在每个表创建语句前加上 drop table 语句 , 默认开启 ;
不开启 (--skip-add-drop-table)
-n, --no-create-db 不包含数据库的创建语句
-t, --no-create-info 不包含数据表的创建语句
-d --no-data 不包含数据
-T, --tab=name 自动生成两个文件:一个.sql文件,创建表结构的语句;一
个.txt文件,数据文件
查看sql文件linux下使用cat SQL名.sql
备份出来的数据包含: 删除表的语句 创建表的语句 数据插入语句
如果在数据备份时,不需要创建表,或者不需要备份数据,只需要备份表结构,都可以通过对应的参数来实现。
C. 将db01数据库的表的表结构与数据分开备份(-T)
mysqldump -uroot -p1234 -T /root db01 score
mysqlimport/source ---> mysqlimport 是客户端数据导入工具,用来导入mysqldump 加 -T 参数后导出的文本文件。
语法 :
mysqlimport [options] db_name textfile1 [textfile2...]
//示例 :
mysqlimport -u用户名 -p用户密码 数据库名 /路径/备份名字.txt
source--->如果需要导入sql文件,可以使用mysql中的source 指令 :
语法 :
source /路径/xxxxx.sql