MySQL数据库从入门到入土!

前言

此篇文章是本人在学习MySQL数据库时,对本网站各位大佬有关MySQL数据库的优秀文章做出的整理和总结,包括CentOS7下MySQL5.7的安装、SQL语句使用、储存过程、内核、索引、事务、锁、MVCC、日志和运维方面的介绍和讲解,引用的文章会列在本篇结尾,本篇文章无任何盈利性,如有版权问题,请随时联系!

文章目录

    • 前言
    • 一、安装MySQL
        • 1、在CentOS7上登录MySQL网站
        • 2、下载四个文件
        • 3、将四个文件移动到/usr/local中
        • 4、安装
        • 5、检查是否安装成功
        • 6、查看用户是否存在
        • 7、初始化数据库
        • 8、执行命令启动
        • 9、密码
        • 10、登录
        • 11、修改密码
        • 12、退出重启
    • 二、数据库的操作
        • 1、创建数据库
        • 2、删除数据库
        • 3、用户创建
    • 三、表的操作
        • 1、表的创建
        • 2、查询表中所有数据
        • 3、在表中插入
        • 4、更改表的某一行
        • 5、清空某个表
        • 6、删除表
        • 7、显示所有表
    • 四、查询
        • 1、基础语句
        • 2、模糊查询
        • 3、排序查询
        • 4、聚合函数
        • 5、分组查询
        • 6、内连接查询
        • 7、外连接查询
        • 8、子查询
    • 五、存储过程
        • 1、简介
        • 2、使用
        • 3、实例
    • 六、函数
        • 1、聚合函数
        • 2、数值计算类函数
        • 3、日期时间相关
    • 七、MyISAM和InnoDB的区别
        • 1、MyISAM
        • 2、InnoDB
        • 3、区别
    • 八、索引
        • 1、索引的含义
        • 2、索引的意义
        • 3、索引的作用
        • 4、索引的缺点
        • 5、索引的使用场景
          • 5.1、建议使用索引的场景
          • 5.2、不建议使用索引的场景
        • 6、索引的分类与说明
          • 6.1、主键索引
          • 6.2、单列索引
          • 6.3、唯一索引
          • 6.4、复合索引
          • 6.5、聚集索引与非聚集索引
    • 九、事务
        • 1、事务的定义
        • 2、事务的使用
        • 3、事务的产生
        • 4、事务的特征
    • 十、锁
        • 1、概念
        • 2、MyIsam
          • 2.1、如何加表锁
          • 2.2、并发锁
          • 2.3、MyISAM的锁调度
        • 3、InnoDB
          • 3.1、并发事务带来的问题
          • 3.2、InnoDB都有哪些锁?
          • 3.3、行锁模式及加锁方法
          • 3.4、InnoDB行锁实现方式
          • 3.5、间隙锁
          • 3.6、Next-Key锁
          • 3.7、InnoDB加锁规则
          • 3.8、什么时候使用表锁
        • 4、死锁
        • 5、总结
    • 十一、MVCC—多版本并发控制
        • 1、什么是MVCC?
        • 2、MVCC的作用
        • 3、前提
        • 4、MVCC的优点
        • 5、总结
    • 十二、日志
        • 1、MySQL的逻辑架构
        • 2、redo log
          • 2.1、redo log的起因
          • 2.2、redo log基本概念
          • 2.3、redo log记录形式
        • 3、bin log
          • 3.1、bin log基本概念
          • 3.2、bin log使用场景
          • 3.3、bin log日志格式
        • 4、redo log和bin log的区别
        • 5、 undo log
    • 十三、运维
      • 1、mysqldump
        • 1.1、简介
        • 1.2、备份命令
          • 1.2.1、命令格式
          • 1.2.2、选项说明
          • 1.2.3、实例
        • 1.3、还原命令
          • 1.3.1、系统行命令
          • 1.3.2、source方法
      • 2、show processlist
        • 2.1、简介
        • 2.2、show processlist参数分析
        • 2.3、show processlist-state参数分析
      • 3、show engine innodb status
        • 3.1、简介
        • 3.2、分析
    • 参考博客


一、安装MySQL

1、在CentOS7上登录MySQL网站

MySQL :: Download MySQL Community Server (Archived Versions)

2、下载四个文件

MySQL数据库从入门到入土!_第1张图片

MySQL数据库从入门到入土!_第2张图片

3、将四个文件移动到/usr/local中

mv mysql-community-client-.... /usr/local

4、安装

1.rpm -ivh mysql-community-common-5.7.16-1.el7.x86_64.rpm
2.rpm -ivh mysql-community-libs-5.7.16-1.el7.x86_64.rpm
3.rpm -ivh mysql-community-client-5.7.16-1.el7.x86_64.rpm
4.rpm -ivh mysql-community-server-5.7.16-1.el7.x86_64.rpm

MySQL数据库从入门到入土!_第3张图片

5、检查是否安装成功

rpm -qa | grep mysql

MySQL数据库从入门到入土!_第4张图片

6、查看用户是否存在

id mysql

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tYcdlAbH-1667548411534)(C:\Users\19407\Desktop\工作文件\学习整理\image-20221102115439075.png)]

7、初始化数据库

mysqld --initialize --user=root

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0LLetH5z-1667548411535)(C:\Users\19407\Desktop\工作文件\学习整理\image-20221102115631350.png)]

8、执行命令启动

service mysqld start

9、密码

grep password /var/log/musqld.log

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Woc7lRbg-1667548411542)(C:\Users\19407\Desktop\工作文件\学习整理\image-20221102115817271.png)]

10、登录

mysql -uroot -p

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nDun0Zbw-1667548411544)(C:\Users\19407\Desktop\工作文件\学习整理\image-20221102115929182.png)]

登录失败

解决方法
1)查看日志 cat /var/log/mysqld.log
2)发现ibdata1没有权限
3)设置权限 chmod -R 777 /var/lib/mysql
4)重启服务器 service mysqld start
5)再次登入mysql

MySQL数据库从入门到入土!_第5张图片

成功

11、修改密码

set password = password("1");

12、退出重启

exit #退出
service mysqld restart	#重启服务

二、数据库的操作

1、创建数据库

create database db1;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zfqzaait-1667548411547)(C:\Users\19407\Desktop\工作文件\学习整理\image-20221102150553648.png)]

create database db1;	-- 创建库

create database if not exists db1;	-- 创建库是否存在,不存在则创建

use db1;	-- 使用库

show databases;	-- 查看所有数据库
 
show create database db1; 	-- 查看某个数据库的定义信息

alter database db1 character set utf8; 	-- 修改数据库字符信息

drop database db1; 	-- 删除数据库

2、删除数据库

drop database nt;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3pfL9EbS-1667548411547)(C:\Users\19407\Desktop\工作文件\学习整理\image-20221102161929266.png)]

3、用户创建

grant all on *.* to user1 identified by '1'	
#all表示全部权限,on:作用于,*.*:全部数据库中的所有表,user1:名字,identified by+'':密码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ETrh0Kpo-1667548411548)(C:\Users\19407\Desktop\工作文件\学习整理\image-20221102151905474.png)]

三、表的操作

1、表的创建

create table nt (`id` int(4),`name` char(40));	#字段名需要``括上

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F8R0cnYr-1667548411549)(C:\Users\19407\Desktop\工作文件\学习整理\image-20221102151223649.png)]

create table student(
    id int,
    name varchar(32),
    age int ,
    score double(4,1),
    birthday date,
    insert_time timestamp
);	-- 创建学生表
 

desc 表名;	-- 查看表结构

show create table 表名;	-- 查看创建表的SQL语句

alter table 表名 rename to 新的表名;	-- 修改表名

alter table 表名 add 列名 数据类型;	-- 添加一列

alter table 表名 drop 列名;	-- 删除列

drop table 表名;	-- 删除表
drop table  if exists 表名 ;	-- 删除表如果表存在

2、查询表中所有数据

select * from nt;	#查询nt表中的所有数据

3、在表中插入

insert into nt values (1,'zh');	#数字直接写,逗号分隔,字符串需要''括上	

MySQL数据库从入门到入土!_第6张图片

insert into 表名(列名1,列名2,...列名n) values(1,2,...值n);	-- 写全所有列名

insert into 表名 values(1,2,...值n);	-- 不写列名(所有列全部添加)

insert into 表名(列名1,列名2) values(1,2);	-- 插入部分数据
一、插入一个字段
alter table 表名 add 需要修改/增加的信息 after 字段名 
-- (在哪个字段后增加)

alter table dept Add column name varchar(20) not null default 0 AFTER sex;


alter table 表名 add 需要修改/增加的信息 first;
-- 增加字段(把字段添加在第一个位置)

alter table dept Add column name varchar(20) not null default 0 first;

alter table 表名 Add 需要修改/增加的信息;
-- 增加字段(插在末尾)

二、修改字段名
alter table 表名 change 原字段 要修改的字段名 字段类型;

alter table dept change name newname varchar(30);

4、更改表的某一行

update nt set name='aaa' where id=1;

MySQL数据库从入门到入土!_第7张图片

update 表名 set 列名 =;	-- 不带条件的修改(会修改所有行)

update 表名 set 列名 =where 列名=;	-- 带条件的修改

5、清空某个表

truncate table nt;	-- truncate 截断,删除表中所有数据(高效 先删除表,然后再创建一张一样的表。)

6、删除表

drop table nt;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vRpjClgp-1667548411550)(C:\Users\19407\Desktop\工作文件\学习整理\image-20221102161852954.png)]

delete from 表名 where 列名  =;	-- 删除表中数据

delete from 表名;	-- 删除表中所有数据

7、显示所有表

show tables;

MySQL数据库从入门到入土!_第8张图片

四、查询

1、基础语句

-- 查询年龄大于等于20 小于等于30				
SELECT * FROM student WHERE age >= 20 &&  age <=30;
SELECT * FROM student WHERE age >= 20 AND  age <=30;
SELECT * FROM student WHERE age BETWEEN 20 AND 30;
				
-- 查询年龄22岁,18岁,25岁的信息
SELECT * FROM student WHERE age = 22 OR age = 18 OR age = 25
SELECT * FROM student WHERE age IN (22,18,25);

2、模糊查询

is null(不为空),like(模糊查询)、distinct(去除重复值)

-- 查询英语成绩不为null
SELECT * FROM student WHERE english  IS NOT NULL;
	
 _:单个任意字符
 %:多个任意字符
-- 查询姓马的有哪些? like
SELECT * FROM student WHERE NAME LIKE '马%';
-- 查询姓名第二个字是化的人			
SELECT * FROM student WHERE NAME LIKE "_化%";				
-- 查询姓名是3个字的人
SELECT * FROM student WHERE NAME LIKE '___';					
-- 查询姓名中包含德的人
SELECT * FROM student WHERE NAME LIKE '%德%';
 
-- 关键词 DISTINCT 用于返回唯一不同的值。
-- 语法:SELECT DISTINCT 列名称 FROM 表名称
SELECT DISTINCT NAME FROM  student ;

3、排序查询

语法:order by 子句

​ order by 排序字段1 排序方式1 , 排序字段2 排序方式2…

注意:
如果有多个排序条件,则当前边的条件值一样时,才会判断第二条件。

-- 例子
SELECT * FROM person ORDER BY math; -- 默认升序
SELECT * FROM person ORDER BY math desc; -- 降序

4、聚合函数

1、count:计算个数

2、max:计算最大值

3、min:计算最小值

4、sum:计算和

5、avg:计算平均数

5、分组查询

语法:group by 分组字段;

注意:分组之后查询的字段:分组字段、聚合函数

-- 按照年龄分组。分别查询20、30岁同学的平均分
SELECT age , AVG(math) FROM student GROUP BY age;

MySQL数据库从入门到入土!_第9张图片

-- 按照性别分组。分别查询男、女同学的平均分,人数
SELECT sex , AVG(math),COUNT(id) FROM student GROUP BY sex;

-- 按照性别分组。分别查询男、女同学的平均分,人数 要求:分数低于70分的人,不参与分组
SELECT sex , AVG(math),COUNT(id) FROM student WHERE math > 70 GROUP BY sex;

-- 按照性别分组。分别查询男、女同学的平均分,人数 要求:分数低于70分的人,不参与分组,分组之后。人数要大于2个人
SELECT sex , AVG(math),COUNT(id) FROM student WHERE math > 70 GROUP BY sex HAVING COUNT(id) > 2;
SELECT sex , AVG(math),COUNT(id) 人数 FROM student WHERE math > 70 GROUP BY sex HAVING 人数 > 2;

6、内连接查询

1、隐式内连接

使用where条件消除无用数据

-- 查询员工表的名称,性别。部门表的名称
SELECT emp.name,emp.gender,dept.name FROM emp,dept WHERE emp.`dept_id` = dept.`id`;
 
SELECT 
    t1.name, -- 员工表的姓名
    t1.gender,-- 员工表的性别
    t2.name -- 部门表的名称
FROM
    emp t1,
    dept t2
WHERE 
    t1.`dept_id` = t2.`id`;

2、显示内连接

-- 语法: 
select 字段列表 from 表名1 [inner] join 表名2 on 条件

-- 例如:
SELECT * FROM emp INNER JOIN dept ON emp.`dept_id` = dept.`id`;   
SELECT * FROM emp JOIN dept ON emp.`dept_id` = dept.`id`;	#查询emp中dept_id与dept中id相同的

7、外连接查询

1、左外连接

查询的是左表所有数据以及其交集部分

-- 语法:
select 字段列表 from1 left [outer] join2 on 条件;

-- 例子:
-- 查询所有员工信息,如果员工有部门,则查询部门名称,没有部门,则不显示部门名称
SELECT  t1.*,t2.`name` FROM emp t1 left join dept t2 on t1.`dept_id` = t2.`id`;

2、右外连接

查询的是右表所有数据以及其交集部分

-- 语法:
select 字段列表 from1 right [outer] join2 on 条件;

-- 例子:
SELECT  * FROM dept t2 RIGHT JOIN emp t1 ON t1.`dept_id` = t2.`id`;

8、子查询

查询中的嵌套查询

-- 查询工资最高的员工信息
-- 1 查询最高的工资是多少 9000
SELECT MAX(salary) FROM emp;
 
-- 2 查询员工信息,并且工资等于9000的
SELECT * FROM emp WHERE emp.`salary` = 9000;
 
-- 一条sql就完成这个操作。这就是子查询
SELECT * FROM emp WHERE emp.`salary` = (SELECT MAX(salary) FROM emp);

1、子查询的结果是单行单列

子查询可以作为条件,使用运算符去判断。

> >= < <= =
-- 查询员工工资小于平均工资的人
SELECT * FROM emp WHERE emp.salary < (SELECT AVG(salary) FROM emp);

2、子查询的结果是多行单列

子查询可以作为条件,使用运算符in来判断

-- 查询'财务部'和'市场部'所有的员工信息
SELECT id FROM dept WHERE NAME = '财务部' OR NAME = '市场部';
SELECT * FROM emp WHERE dept_id = 3 OR dept_id = 2;
 
-- 子查询
SELECT * FROM emp WHERE dept_id IN (SELECT id FROM dept WHERE NAME = '财务部' OR NAME = '市场部');

3、子查询的结果是多行多列

子查询可以作为一张虚拟表参与查询

-- 查询员工入职日期是2011-11-11日之后的员工信息和部门信息
-- 子查询
SELECT * FROM dept t1 ,(SELECT * FROM emp WHERE emp.`join_date` > '2011-11-11') t2 WHERE t1.id = t2.dept_id;
 
-- 普通内连接
SELECT * FROM emp t1,dept t2 WHERE t1.`dept_id` = t2.`id` AND t1.`join_date` >  '2011-11-11'

五、存储过程

1、简介

SQL语句需要先编译然后执行,而存储过程(Stored Procedure)是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给定参数(如果该存储过程带有参数)来调用执行它。

​ 存储过程是可编程的函数,在数据库中创建并保存,可以由SQL语句和控制结构组成。当想要在不同的应用程序或平台上执行相同的函数,或者封装特定功能时,存储过程是非常有用的。数据库中的存储过程可以看做是对编程中面向对象方法的模拟,它允许控制数据的访问方式。

优点 :

存储过程可封装,并隐藏复杂的商业逻辑。
存储过程可以回传值,并可以接受参数。
存储过程无法使用 SELECT 指令来运行,因为它是子程序,与查看表,数据表或用户定义函数不同。
存储过程可以用在数据检验,强制实行商业逻辑等。

缺点:

存储过程,往往定制化于特定的数据库上,因为支持的编程语言不同。当切换到其他厂商的数据库系统时,需要重写原有的存储过程。
存储过程的性能调校与撰写,受限于各种数据库系统。

2、使用

--------------创建存储过程-----------------
 
CREATE PROC [ EDURE ] procedure_name [ ; number ]	
-- procedure_name :存储过程的名称,在前面加#为局部临时存储过程,加##为全局临时存储过程。
-- number:是可选的整数,用来对同名的过程分组,以便用一条 DROP PROCEDURE 语句即可将同组的过程一起除去。例如,名为 orders 的应用程序使用的过程可以命名为 orderproc;1、orderproc;2 等。DROP PROCEDURE orderproc 语句将除去整个组。如果名称中包含定界标识符,则数字不应包含在标识符中,只应在 procedure_name 前后使用适当的定界符。

    [ { @parameter data_type }
-- @parameter:存储过程的参数。可以有一个或多个。用户必须在执行过程时提供每个所声明参数的值(除非定义了该参数的默认值)。存储过程最多可以有 2.100 个参数。 
-- data_type:参数的数据类型。所有数据类型(包括 text、ntext 和 image)均可以用作存储过程的参数。不过,cursor 数据类型只能用于 OUTPUT 参数。如果指定的数据类型为 cursor,也必须同时指定 VARYING 和 OUTPUT 关键字。
     
        [ VARYING ] [ = default ] [ OUTPUT ]
-- VARYING:指定作为输出参数支持的结果集(由存储过程动态构造,内容可以变化)。仅适用于游标参数。 
-- default:参数的默认值。如果定义了默认值,不必指定该参数的值即可执行过程。默认值必须是常量或 NULL。如果过程将对该参数使用 LIKE 关键字,那么默认值中可以包含通配符(%、_、[] 和 [^])。
-- OUTPUT:表明参数是返回参数。该选项的值可以返回给 EXEC[UTE]。使用 OUTPUT 参数可将信息返回给调用过程。Text、ntext 和 image 参数可用作 OUTPUT 参数。使用 OUTPUT 关键字的输出参数可以是游标占位符。 
     
    ] [ ,...n ]
 
[ WITH
    { RECOMPILE | ENCRYPTION | RECOMPILE , ENCRYPTION } ]
-- RECOMPILE: 表明 SQL Server 不会缓存该过程的计划,该过程将在运行时重新编译。在使用非典型值或临时值而不希望覆盖缓存在内存中的执行计划时,请使用 RECOMPILE 选项。
-- 表示 SQL Server 加密 syscomments 表中包含 CREATE PROCEDURE 语句文本的条目。使用 ENCRYPTION 可防止将过程作为 SQL Server 复制的一部分发布。 说明 在升级过程中,SQL Server 利用存储在 syscomments 中的加密注释来重新创建加密过程。

[ FOR REPLICATION ]
-- FOR REPLICATION :指定不能在订阅服务器上执行为复制创建的存储过程。.使用 FOR REPLICATION 选项创建的存储过程可用作存储过程筛选,且只能在复制过程中执行。本选项不能和 WITH RECOMPILE 选项一起使用。

BEGIN sql_statement [ ...n ]
-- begin :指定过程要执行的操作。
-- sql_statement :过程中要包含的任意数目和类型的 Transact-SQL 语句

END
-- 结束符

------------ -- 调用存储过程 -- ---------------
 
EXECUTE Procedure_name '' 
-- 存储过程如果有参数,后面加参数格式为:@参数名=value,也可直接为参数值value
 
------------ -- 删除存储过程 -- ---------------
 
drop procedure procedure_name    
-- 在存储过程中能调用另外一个存储过程,而不能删除另外一个存储过程

MySQL存储过程的参数用在存储过程的定义,共有三种参数类型

IN,OUT,INOUT
格式为:Create procedure|function([[IN |OUT |INOUT ] 参数名 数据类形...])

IN 输入参数
    表示该参数的值必须在调用存储过程时指定,在存储过程中修改该参数的值不能被返回,为默认值

OUT 输出参数
    该值可在存储过程内部被改变,并可返回

INOUT 输入输出参数
    调用时指定,并且可被改变和返回

3、实例

delimiter //	-- 更改分隔符为//
create procedure test()	-- 创建存储过程test()
begin	
select * from student;
end //

MySQL数据库从入门到入土!_第10张图片

DELIMITER $$

CREATE PROCEDURE p3(IN aid INT)

COMMENT '向表中插入一条记录'

BEGIN

DECLARE v1 INT;	##定义一个整形变量

SET v1 = aid;	##将输入参数的值赋给变量

INSERT INTO tb(id,cname) VALUES(v1,CONCAT('第',v1,'条记录'));	##执行插入操作

END$$

DELIMITER ;

##调用存储过程,使用一个参数,就会在表中插入一条记录

CALL p3(99);

SELECT * FROM tb;
mysql> delimiter /
mysql> create procedure pi(in n int)
    -> begin
    -> declare total int default 0;
    -> declare num int default 0;
    -> while num < n do
    -> set num:=num+1;
    -> set total:=total+num;
    -> end while;
    -> select total;
    -> end /
    
mysql> call pi(10)/

MySQL数据库从入门到入土!_第11张图片

六、函数

1、聚合函数

Name Description
avg() 求平均值
count() 计数
group_concat() 将分组中括号里对应的字符串连接,如果有多行,会将多行字符串连接
max() 求最大值
min() 求最小值
sum() 求和

2、数值计算类函数

Name Description
abs(x) 求绝对值
ceil(x) 求大于x的最大整数值
floot(x) 求小于x的最大整数值
mod(x,y) 求x/y的模
rand() 求0~1随机数
round(x,y) 返回离x最近的整数
truncate(x,y) 返回数字x 截断到y 位小数的值
exp(x) 求e的x次方
greatest(expr1,expr2,expr3,…) 返回列表的最大值

3、日期时间相关

Name Description
now() 返回现在的日期时间
datediff(x,y) 返回起始时间x和结束时间y之间的天数

七、MyISAM和InnoDB的区别

1、MyISAM

​ MyISAM是默认存储引擎(Mysql5.1前)。它基于更老的ISAM代码,但有很多有用的扩展。(注意MySQL 5.1不支持ISAM)。

​ 每个MyISAM在磁盘上存储成三个文件,每一个文件的名字均以表的名字开始,扩展名指出文件类型。

.frm 文件 存储 表定义;

.MYD (MYData)文件 存储 表的数据;

.MYI (MYIndex)文件 存储 表的索引

2、InnoDB

​ InnoDB,是MySQL的数据库引擎之一,现为MySQL的默认存储引擎,为MySQL AB发布binary的标准之一。

​ InnoDB 由Innobase Oy公司所开发 ,2006年五月时由甲骨文公司并购。与传统的ISAM与MyISAM相比,InnoDB的最大特色就是 支持了ACID兼容的事务(Transaction)功能 ,类似于PostgreSQL。

​ InnoDB 物理文件结构为:

.frm 文件:与表相关的元数据信息都存放在frm文件,包括表结构的定义信息等;

.ibd 文件或 .ibdata 文件: 这两种文件都是存放 InnoDB 数据的文件,之所以有两种文件形式存放 InnoDB 的数据,是因为 InnoDB 的数据存储方式能够通过配置来决定是使用共享表空间存放存储数据,还是用独享表空间存放存储数据。

独享表空间存储方式使用**.ibd文件,并且每个表一个.ibd文件;
共享表空间存储方式使用
.ibdata**文件,所有表共同使用一个.ibdata文件(或多个,可自己配置)。

3、区别

1、InnoDB 支持事务,MyISAM 不支持事务。

​ 对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务

2、InnoDB支持外键,而MyISAM不支持。

​ 对一个包含外键的 InnoDB 表转为 MYISAM 会失败

3、InnoDB 是聚簇索引,MyISAM 是非聚簇索引

​ InnoDB 是聚簇索引,使用B+Tree作为索引结构,数据文件是和(主键)索引绑在一起的(表数据文件本身就是按B+Tree组织的一个索引结构),必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。

MyISAM是非聚集索引,也是使用B+Tree作为索引结构,索引和数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。

4、InnoDB 不保存表的具体行数,MyISAM 用一个变量保存了整个表的行数

InnoDB执行select count(*) from table时需要全表扫描。

​ 而MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快(注意不能加有任何WHERE条件)

​ 由于InnoDB的事务特性,在同一时刻表中的行数对于不同的事务而言是不一样的,因此count统计会计算对于当前事务而言可以统计到的行数,而不是将总行数储存起来方便快速查询。InnoDB会尝试遍历一个尽可能小的索引除非优化器提示使用别的索引。如果二级索引不存在,InnoDB还会尝试去遍历其他聚簇索引。
如果索引并没有完全处于InnoDB维护的缓冲区(Buffer Pool)中,count操作会比较费时。可以建立一个记录总行数的表并让你的程序在INSERT/DELETE时更新对应的数据。和上面提到的问题一样,如果此时存在多个事务的话这种方案也不太好用。如果得到大致的行数值已经足够满足需求可以尝试SHOW TABLE STATUS

5、InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁

​ InnoDB的行锁是实现在索引上的,而不是锁在物理行记录上。潜台词是,如果访问没有命中索引,也无法使用行锁,将要退化为表锁。

​ MyISAM一个更新语句会锁住整张表,导致其他查询和更新都会被阻塞,因此并发访问受限。

6、InnoDB表必须有唯一索引(如主键)(用户没有指定的话会自己找/生产一个隐藏列Row_id来充当默认主键),而Myisam可以没有

7、对比图

对比项 MyISAM InnoDB
主外键 不支持 支持
事务 不支持 支持
行表锁 表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作 行锁,操作时只锁某一行,不对其它行有影响,适合高并发的操作
缓存 只缓存索引,不缓存真实数据 不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响
表空间
关注点 性能 事务

八、索引

1、索引的含义

​ 数据库索引是数据库管理系统中的一个排序的数据结构,以协助快速查询,更新数据库中表的数据。索引的实现通常使用B树和变种的B+树(MySQL常用的索引就是B+树)

​ 除了数据之外,数据库系统还维护为满足特定查找算法的数据结构,这些数据结构以某种方式引用数据,这种数据结构就是索引

2、索引的意义

​ 合理使用索引,可以加快数据库的查询效率和提升程序性能

3、索引的作用

​ 1.通过创建索引,可以在查询的过程中,提高系统的性能

​ 2.通过创建唯一性索引,可以保持数据库表中每一行数据的唯一性

​ 3.在使用分组和排序子句进行数据检索时,可以减少查询中的分组和排序的时间

4、索引的缺点

​ 1.创建索引和维护索引要耗费时间,而且时间随着数据量的增加而增大

​ 2.索引需要占用物理空间,如果要建立聚簇索引,所需要的空间会更大

​ 3.在对表中的数据进行增删改查时需要耗费较多时间,因为索引也要动态的维护

5、索引的使用场景

5.1、建议使用索引的场景

​ 1.经常需要搜索的列上

​ 2.作为主键的列上

​ 3.经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度

​ 4.经常需要根据范围进行搜索的列上

​ 5.经常需要排序的列上

​ 6.经常使用在where子句上面的列上

5.2、不建议使用索引的场景

​ 1.查询中很少用到的列

​ 2.对于那些具有很少数据值的列,比如性别列

​ 3.对于那些定义为text,image的列,因为这些列的数据量相当的大

​ 4.对于修改性能的要求远远大于搜索性能的列,因为增加索引,会提高搜索性能,但会降低修改性能

6、索引的分类与说明

6.1、主键索引

设定为主键后数据库会自动建立索引,InnoDB为聚簇索引

-- 随表一起建立索引
create table customer (
    id int(10) unsigned auto_increment , 
-- unsigned auto_increment 主键从0开始自增,
    customer_no varchar(200) , 
    customer_name varchar(200) , 
    primary key(id)
);
-- auto_increment是用于主键自动增长的,从1开始增长,当你把第一条记录删除时,再插入第二条数据时,主键值是2,不是1


-- 使用AUTO_INCREMENT关键字的列必须有索引(只要有索引就行)
create table customer2 (id int(10) unsigned ,
                       customer_no varchar(200) ,
                       customer_name varchar(200) ,
                       primary key(id)
 );
 
-- 单独建主键索引
alter table customer add primary key customer(customer_no);

-- 删除建主键索引
alter table customer drop primary key;
-- 修改主键索引
-- 必须先删除掉(drop)原索引,再新建(add)索引
6.2、单列索引

一个索引只包含单个列,一个表可以有多个单列索引

-- 随表一起建索引
create table customer (id int(10) unsigned auto_increment ,
                      customer_no varchar(200) , 
                      customer_name varchar(200) ,
                      primary key(id) ,
                      key(customer_name)
);
-- 随表一起建立的索引 索引名同列名(customer——name)

-- 单独建单值索引
create index idx_customer_name on customer(customer_name);

-- 删除索引
drop index idx_customer_name;
6.3、唯一索引

索引列的值必须唯一,但允许有空值

-- 随表一起建索引
create table customer (id int(10) unsigned auto_increment ,
                       customer_no varchar(200),
                       customer_name varchar(200),
                       primary key(id),
                       KEY(customer_name),
                       unique(customer_no)
);
-- 建立唯一索引时必须保证所有的值是唯一的(除了null),若有重复数据,会报错

-- 单独建唯一索引
create unique index idx_customer_no on customer(customer_no);

-- 删除索引
drop index idx_customer_no on customer;
6.4、复合索引

一个索引包含多个列,在数据库操作期间,复合索引比单值索引所需要的开销更小(对于相同的多个列建索引)
如果一个表中的数据在查询时有多个字段总是同时出现则这些字段就可以作为复合索引,形成索引覆盖可以提高查询的效率!

-- 随表一起建索引:
CREATE TABLE customer (id INT(10) UNSIGNED AUTO_INCREMENT ,
                       customer_no VARCHAR(200),
                       customer_name VARCHAR(200),
                       PRIMARY KEY(id),
                       KEY (customer_name),
                       UNIQUE (customer_name),
                       KEY (customer_no,customer_name)
);

-- 单独建索引:
CREATE INDEX idx_no_name ON customer(customer_no,customer_name); 

-- 删除索引:
DROP INDEX idx_no_name  on customer ;
6.5、聚集索引与非聚集索引

6.5.1、聚集索引

​ 指索引项的排序方式和表中数据记录排序方式一致的索引。

​ 它会根据聚集索引键的顺序来存储表中的数据,即对表的数据按索引键的顺序进行排序,然后重新存储到磁盘上。因为数据在物理存放只能有一种排列方式,所以一个表只能有一个聚集索引。

​ 比如字典中,用拼音查汉字,就是聚集索引。因为正文中字都是按照拼音排序的。而用偏旁部首查汉字,就是非聚集索引,因为正文中的字并不是按照偏旁部首排序的,我们通过检字表得到正文中的字在索引中的映射,然后通过映射找到所需要的字。

6.5.2、非聚集索引

​ 非聚集索引:与聚集索引相反,索引顺序与物理存储顺序不一致

​ 非聚集索引必须是稠密索引

6.5.3、使用

create [unique] [clustered] [nonclustered] 
index index_name  
on {tabel/view} (column[dese/asc][....n])

九、事务

1、事务的定义

​ 事务就是用户定义的一系列数据库操作,这些操作可以视为一个完整的逻辑处理工作单元,要么全部执行,要么全部不执行,是不可分割的工作单元。

2、事务的使用

start transaction;	-- 开始一个事务

savepoint 保存点;	-- 做一个保存点

rollback to 保存点;	-- 回滚到某个保存点

rollback;	-- 回滚事务。即撤销正在进行的所有没有提交的修改

commit;	-- 提交

delete from account where id = 100;	-- 删除一个用户

(1) 没有设置保存点

​ 开始事务时,事务会默认给你创建一个保存点,如果你希望回退也可以使用rollback , 就可以直接回退到事务开始的状态.

(2) 多个保存点

​ 我们可以设置多个保底点,但是如果我们回退时,需要按顺序回退。即如果你回退到前面的某个保底点,那么后面的保存点就没有了.

(3) 存储引擎

​ 如果要支持事务,需要存储引擎是 innodb;

(4) 开始事务方式

start transaction;

set autocommit = false;	-- 关闭隐式事务

3、事务的产生

​ 数据库中的数据是共享资源,因此数据库系统通常要支持多个用户的或不同应用程序的访问,并且各个访问进程都是独立执行的,这样就有可能出现并发存取数据的现象,这里有点类似Java开发中的多线程安全问题(解决共享变量安全存取问题),如果不采取一定措施会出现数据异常的情况。

​ DBMS系统必须对这种并发操作提供一种相应的处理机制来保证,访问彼此之间不受任何干扰,从而保证数据库的正确性不受到破坏,为了避免数据库的不一致性,这种处理机制称之为“并发控制”,其中事务就是为了保证数据的一致性而产生的一种概念和手段(事务不是唯一手段)

4、事务的特征

为了保证数据库的正确性与一致性事务具有四个特征:

(1) 原子性(Atomicity)

​ 事务的原子性保证事务中包含的一组更新操作是原子的,不可分割的,不可分割是事务最小的工作单位,所包含的操作被视为一个整体,执行过程中遵循“要么全部执行,要不都不执行”,不存在一半执行,一半未执行的情况。

(2)一致性(Consistency)

​ 事务的一致性要求事务必须满足数据库的完整性约束,且事务执行完毕后会将数据库由一个一致性的状态变为另一个一致性的状态。事务的一致性与原子性是密不可分的,如银行转账的例子 A账户向B账户转1000元钱,首先A账户减去1000元钱,然后B账户增加1000元钱,这两动作是一个整体,失去任何一个操作数据的一致性状态都会遭到破坏,所以这两个动作是一个整体,要么全部操作,要么都不执行,可见事务的一致性与原子性息息相关。

(3)隔离性(Isolation)

​ 事务的隔离性要求事务之间是彼此独立的,隔离的。及一个事务的执行不可以被其他事务干扰。具体到操作是指一个事务的操作必须在一个事务commit之后才可以进行操作。多事务并发执行时,相当于将并发事务变成串行事务,顺序执行,如同串行调度般的执行事务。这里可以思考事务如何保证它的可串行化的呢?答案锁,接下来会讲到。

(4)持续性(Durability)

​ 事物的持续性也称持久性,是指一个事务一旦提交,它对数据库的改变将是永久性的,因为数据刷进了物理磁盘了,其他操作将不会对它产生任何影响。

原子性由undolog保证,隔离性是由锁和mvcc保证,持久性由redolog保证,一致性则是前面三个保证的。

十、锁

1、概念

相对其他数据库而言,MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。

MySQL大致可归纳为以下3种锁:

  • 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
  • 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
  • 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

2、MyIsam

在使用MyIsam时,我们只可以使用表级锁,而MySQL的表级锁有两种模式:

表共享锁(Table Read Lock)和表独占锁(Table Write Lock),他们在工作时表现如下:

  • 对某一个表的读操作,不会阻塞其他用户对同一表请求,但会阻塞对同一表的写请求;
  • 对MyISAM的写操作,则会阻塞其他用户对同一表的读和写操作;
  • MyISAM表的读操作和写操作之间,以及写操作之间是串行的。

当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。

2.1、如何加表锁

​ MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。

​ 给MyISAM表显式加锁,一般是为了一定程度模拟事务操作实现对某一时间点多个表的一致性读取。例如,有一个订单表orders,其中记录有订单的总金额total,同时还有一个订单明细表order_detail,其中记录有订单每一产品的金额小计subtotal,假设我们需要检查这两个表的金额合计是否相等,可能就需要执行如下两条SQL:

SELECT SUM(total) FROM orders;
SELECT SUM(subtotal) FROM order_detail;

​ 这时,如果不先给这两个表加锁,就可能产生错误的结果,因为第一条语句执行过程中,order_detail表可能已经发生了改变。因此,正确的方法应该是:

LOCK tables orders read local,order_detail read local;	-- 加锁
SELECT SUM(total) FROM orders;
SELECT SUM(subtotal) FROM order_detail;
Unlock tables;	-- 解锁

要特别说明以下两点内容:

  • 上面的例子在LOCK TABLES时加了‘local’选项,其作用就是在满足MyISAM表并发插入条件的情况下,允许其他用户在表尾插入记录
  • 在用LOCKTABLES给表显式加表锁时,必须同时取得所有涉及的表的锁。也就是说,在执行LOCK TABLES后,只能访问显式加锁的这些表,而不能访问未加锁的表;同时,如果加的是读锁,那么只能执行查询操作,而不能执行更新操作。其实,在自动加锁的情况下也基本如此,MySQL会一次性获得SQL语句所需要的全部锁。这也正是MyISAM表不会出现死锁(Deadlock Free)的原因。另外,MySQL支持锁升级,即在条件满足时,允许从表共享锁升级为表独占锁。

​ 一个session使用LOCK TABLE 命令给表film_text加了读锁,这个session可以查询锁定表中的记录,但更新或访问其他表都会提示错误;同时,另外一个session可以查询表中的记录,但更新就会出现锁等待。

​ 当使用LOCK TABLE时,不仅需要一次锁定用到的所有表,而且,同一个表在SQL语句中出现多少次,就要通过与SQL语句中相同的别名锁多少次,否则也会出错!

2.2、并发锁

​ 在一定条件下,MyISAM也支持查询和操作的并发进行。

​ MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为,其值分别可以为0、1或2。

  • 当concurrent_insert设置为0时,不允许并发插入
  • 当concurrent_insert设置为1时,如果MyISAM允许在一个读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置
  • 当concurrent_insert设置为2时,无论MyISAM表中有没有空洞,都允许在表尾插入记录,都允许在表尾并发插入记录

​ 可以利用MyISAM存储引擎的并发插入特性,来解决应用中对同一表查询和插入锁争用。例如,将concurrent_insert系统变量为2,总是允许并发插入;同时,通过定期在系统空闲时段执行OPTIONMIZE TABLE语句来整理空间碎片,收集因删除记录而产生的中间空洞。

2.3、MyISAM的锁调度

​ 前面讲过,MyISAM存储引擎的读和写锁是互斥,读操作是串行的。那么,一个进程请求某个MyISAM表的读锁,同时另一个进程也请求同一表的写锁,MySQL如何处理呢?答案是写进程先获得锁。不仅如此,即使读进程先请求先到锁等待队列,写请求后到,写锁也会插到读请求之前!这是因为MySQL认为写请求一般比读请求重要。这也正是MyISAM表不太适合于有大量更新操作和查询操作应用的原因,因为,大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞。这种情况有时可能会变得非常糟糕!幸好我们可以通过一些设置来调节MyISAM的调度行为。

  • 通过指定启动参数low-priority-updates,使MyISAM引擎默认给予读请求以优先的权利。
  • 通过执行命令SET LOW_PRIORITY_UPDATES=1,使该连接发出的更新请求优先级降低。
  • 通过指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,降低该语句的优先级。

​ 虽然上面3种方法都是要么更新优先,要么查询优先的方法,但还是可以用其来解决查询相对重要的应用(如用户登录系统)中,读锁等待严重的问题。

​ 另外,MySQL也提供了一种折中的办法来调节读写冲突,即给系统参数max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL变暂时将写请求的优先级降低,给读进程一定获得锁的机会。

​ 上面已经讨论了写优先调度机制和解决办法。这里还要强调一点:一些需要长时间运行的查询操作,也会使写进程“饿死”!因此,应用中应尽量避免出现长时间运行的查询操作,不要总想用一条SELECT语句来解决问题。因为这种看似巧妙的SQL语句,往往比较复杂,执行时间较长,在可能的情况下可以通过使用中间表等措施对SQL语句做一定的“分解”,使每一步查询都能在较短时间完成,从而减少锁冲突。如果复杂查询不可避免,应尽量安排在数据库空闲时段执行,比如一些定期统计可以安排在夜间执行。

3、InnoDB

​ InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁。

​ 行级锁和表级锁本来就有许多不同之处,另外,事务的引入也带来了一些新问题。

3.1、并发事务带来的问题

​ 相对于串行处理来说,并发事务处理能大大增加数据库资源的利用率,提高数据库系统的事务吞吐量,从而可以支持可以支持更多的用户。但并发事务处理也会带来一些问题,主要包括以下几种情况。

  • 更新丢失(Lost Update):当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题——最后的更新覆盖了其他事务所做的更新。例如,两个编辑人员制作了同一文档的电子副本。每个编辑人员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改保存其更改副本的编辑人员覆盖另一个编辑人员所做的修改。如果在一个编辑人员完成并提交事务之前,另一个编辑人员不能访问同一文件,则可避免此问题

  • 脏读(Dirty Reads):一个事务正在对一条记录做修改,在这个事务并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”的数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做“脏读”。

  • 不可重复读(Non-Repeatable Reads):一个事务在读取某些数据已经发生了改变、或某些记录已经被删除了!这种现象叫做“不可重复读”。

  • 幻读(Phantom Reads):一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。

    ​ 产生幻读的原因是,行锁只能锁住行,但是新插入记录这个动作,要更新的是记录之间的“间隙”。因此,为了解决幻读问题InnoDB只好引入新的锁,也就是间隙锁(Gap Lock),这个后文会继续介绍。

3.2、InnoDB都有哪些锁?
  1. 行锁
    1. 共享锁(lock in share mode)
    2. 排他锁(for update)
  2. 意向锁(表级别)
    1. 意向共享锁
    2. 意向排他锁
  3. 间隙锁
  4. Next-key lock:行锁(排他锁)+间隙锁
3.3、行锁模式及加锁方法

数据集:是一个数据的集合,通常以表格形式出现。每一列代表一个特定变量。每一行都对应于某一成员的数据集的问题。

InnoDB实现了以下两种类型的行锁。

  • 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。

    xxx lock in share mode
    
  • 排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁排他写锁

    xxx for update
    

另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁

意向共享锁(IS):事务打算给数据行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。

意向排他锁(IX):事务打算给数据行加排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

InnoDB行锁模式兼容性列表

当前锁模式/是否兼容/请求锁模式 X IX S IS
X 冲突 冲突 冲突 冲突
IX 冲突 兼容 冲突 兼容
S 冲突 冲突 兼容 兼容
IS 冲突 兼容 兼容 兼容

​ 如果一个事务请求的锁模式与当前的锁兼容,InnoDB就请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。

意向锁是InnoDB自动加的,不需用户干预。

​ 对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及的数据集加排他锁(X);

​ 对于普通SELECT语句,InnoDB会自动给涉及数据集加共享锁(S);

-- 事务可以通过以下语句显式给记录集加共享锁或排锁:

-- 共享锁(S)
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE

-- 排他锁(X)
SELECT * FROM table_name WHERE ... FOR UPDATE

​ 用SELECT … IN SHARE MODE获得共享锁,主要用在需要数据依存关系时确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作。

​ 但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用SELECT … FOR UPDATE方式获取排他锁。

3.4、InnoDB行锁实现方式

InnoDB行锁是通过索引上的索引项来实现的,这一点MySQL与Oracle不同,后者是通过在数据中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味着:

只有通过索引条件检索数据,InnoDB才会使用行级锁

否则,InnoDB将使用表锁(如果是RR / Serializable 级别,将在主键上使用Next-Key Locks(行锁+间隙锁)来实现锁表的操作)

在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。

​ 另外,在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。

​ 因此,如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。

3.5、间隙锁

​ 当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB(可重复读、串行化级别下才有效)会给符合条件的已有数据的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁它通常是一个开区间(xx, xx)。

​ 举例来说,假如emp表中只有101条记录,其empid的值分别是1,2,…,100,101,下面的SQL:

SELECT * FROM emp WHERE empid > 100 FOR UPDATE

​ 是一个范围条件的检索,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。

​ InnoDB使用间隙锁的目的,一方面是为了防止幻读,以满足相关隔离级别的要求,对于上面的例子,要是不使用间隙锁,如果其他事务插入了empid大于100的任何记录,那么本事务如果再次执行上述语句,就会发生幻读;另一方面,是为了满足其恢复和复制的需要。有关其恢复和复制对机制的影响,以及不同隔离级别下InnoDB使用间隙锁的情况。

​ 很显然,在使用范围条件检索并锁定记录时,InnoDB这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待。因此,在实际开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。

​ 其次,间隙锁的存在可能会导致死锁,如下:

​ 假设id是主键。表中有id=5,10的记录,没有id=9的记录。

MySQL数据库从入门到入土!_第12张图片

  1. session A 执行select … for update语句,由于id=9这一行并不存在,因此会加上间隙锁(5,10); (PS:加锁的基本单位是 next-key lock,现在由于id=9的记录不存在,因此next-key lock退化为间隙锁)
  2. session B 执行select … for update语句,同样会加上间隙锁(5,10),间隙锁之间不会冲突,因此这个语句可以执行成功;
  3. session B 试图插入一行(9,9,9),被session A的间隙锁挡住了,只好进入等待;
  4. session A试图插入一行(9,9,9),被session B的间隙锁挡住了。

注意:不同session下的间隙锁之间不会冲突(间隙锁不互锁),跟间隙锁存在冲突关系的,是“往这个间隙中插入一个记录”这个操作

3.6、Next-Key锁

​ next-key lock是InnoDB加锁的基本单位,它是一个前开后闭的区间,即行锁+间隙锁

3.7、InnoDB加锁规则

两个“原则”、两个“优化”和一个“bug”:

  • 原则1:加锁的基本单位是next-key lock。next-key lock是前开后闭区间。
  • 原则2:查找过程中访问到的对象才会加锁。
  • 优化1:索引上的等值查询,给唯一索引加锁的时候,next-key lock退化为行锁。
  • 优化2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁。
  • 一个bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。
3.8、什么时候使用表锁

​ 对于InnoDB表,在绝大部分情况下都应该使用行级锁,因为事务和行锁往往是我们之所以选择InnoDB表的理由。但在个别特殊事务中,也可以考虑使用表级锁。

  • 第一种情况是:事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高该事务的执行速度。
  • 第二种情况是:事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。

​ 当然,应用中这两种事务不能太多,否则,就应该考虑使用MyISAM表。

在InnoDB下 ,使用表锁要注意以下两点。

(1)使用LOCK TALBES虽然可以给InnoDB加表级锁,但必须说明的是,表锁不是由InnoDB存储引擎层管理的,而是由其上一层MySQL Server负责的,仅当autocommit=0、innodb_table_lock=1(默认设置)时,InnoDB层才能知道MySQL加的表锁,MySQL Server才能感知InnoDB加的行锁,这种情况下,InnoDB才能自动识别涉及表级锁的死锁;否则,InnoDB将无法自动检测并处理这种死锁。

(2)在用LOCAK TABLES对InnoDB锁时要注意,要将AUTOCOMMIT设为0,否则MySQL不会给表加锁;事务结束前,不要用UNLOCAK TABLES释放表锁,因为UNLOCK TABLES会隐含地提交事务;而COMMIT或ROLLBACK并不能释放用LOCAK TABLES加的表级锁,所以一般我们必须先提交事务后,再用UNLOCK TABLES释放表锁,正确的方式见如下语句。

SET AUTOCOMMIT=0;
LOCAK TABLES t1 WRITE, t2 READ, ...;
[do something with tables t1 and here];
COMMIT;
UNLOCK TABLES;

4、死锁

​ MyISAM表锁是deadlock free的,这是因为MyISAM总是一次性获得所需的全部锁,要么全部满足,要么等待,因此不会出现死锁。但是在InnoDB中,除单个SQL组成的事务外,锁是逐步获得的,这就决定了InnoDB发生死锁是可能的。

​ 发生死锁后,InnoDB一般都能自动检测到,并使一个事务释放锁并退回,另一个事务获得锁,继续完成事务。有以下两种处理方式

​ 1、直接进入等待,直到超时。这个超时时间可以通过参数innodb_lock_wait_timeout来设置(默认50s)

​ (1)对于在线服务来说,这个等待时间往往是无法接受的。

​ (2)如果设置成1s,这样当出现死锁的时候,确实很快就可以解开,但如果不是死锁,而是简单的锁等待,则会造成很多误伤

​ 2、(推荐)主动死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数innodb_deadlock_detect设置为on,表示开启这个逻辑

​ 如果出现很多事务都要更新同一行的场景(热点行),每个新来的被堵住的线程,都要判断会不会由于自己的加入导致了死锁,这是一个时间复杂度是O(n)的操作。假设有1000个并发线程要同时更新同一行,那么死锁检测操作就是100万这个量级的。虽然最终检测的结果是没有死锁,但是这期间要消耗大量的CPU资源。因此,你就会看到CPU利用率很高,但是每秒却执行不了几个事务。

​ 1、对于上述的情况,如果能确保这个业务一定不会出现死锁,可以临时把死锁检测关掉(头痛医头)

​ 2、**控制并发度,**如过同一行同时最多只有10个线程在更新,那么死锁检测的成本很低,就不会CPU占用高的问题。这个并发控制最好是在数据库Server端 / 中间件进行,而不能在客户端,因为通常会有很多客户端/很多连接/很多线程。其思路一般是:对于相同行的更新,在进入引擎之前排队。这样在InnoDB内部就不会有大量的死锁检测工作了。

​ 3、将一行改成逻辑上的多行来减少锁冲突

​ 但在涉及外部锁,或涉及锁的情况下,InnoDB并不能完全自动检测到死锁,这需要通过设置锁等待超时参数innodb_lock_wait_timeout来解决。需要说明的是,这个参数并不是只用来解决死锁问题,在并发访问比较高的情况下,如果大量事务因无法立即获取所需的锁而挂起,会占用大量计算机资源,造成严重性能问题,甚至拖垮数据库。我们通过设置合适的锁等待超时阈值,可以避免这种情况发生。

​ 通常来说,死锁都是应用设计的问题,通过调整业务流程、数据库对象设计、事务大小、以及访问数据库的SQL语句,绝大部分都可以避免。下面就通过实例来介绍几种死锁的常用方法。

(1)在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序为访问表,这样可以大大降低产生死锁的机会。如果两个session访问两个表的顺序不同,发生死锁的机会就非常高!但如果以相同的顺序来访问,死锁就可能避免。

(2)在程序以批量方式处理数据的时候,如果事先对数据排序,保证每个线程按固定的顺序来处理记录,也可以大大降低死锁的可能。

(3)在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应该先申请共享锁,更新时再申请排他锁,甚至死锁。

(4)在REPEATEABLE-READ隔离级别下,如果两个线程同时对相同条件记录用SELECT…FOR UPDATE加排他锁,在没有符合该记录情况下,两个线程都会加锁成功。程序发现记录尚不存在,就试图插入一条新记录,如果两个线程都这么做,就会出现死锁。这种情况下,将隔离级别改成READ COMMITTED,就可以避免问题。

(5)当隔离级别为READ COMMITED时,如果两个线程都先执行SELECT…FOR UPDATE,判断是否存在符合条件的记录,如果没有,就插入记录。此时,只有一个线程能插入成功,另一个线程会出现锁等待,当第1个线程提交后,第2个线程会因主键重出错,但虽然这个线程出错了,却会获得一个排他锁!这时如果有第3个线程又来申请排他锁,也会出现死锁。对于这种情况,可以直接做插入操作,然后再捕获主键重异常,或者在遇到主键重错误时,总是执行ROLLBACK释放获得的排他锁。

​ 尽管通过上面的设计和优化等措施,可以大减少死锁,但死锁很难完全避免。因此,在程序设计中总是捕获并处理死锁异常是一个很好的编程习惯。

​ 如果出现死锁,可以用SHOW ENGINE INNODB STATUS命令来确定最后一个死锁产生的原因和改进措施。

5、总结

对于MyISAM的表锁,主要有以下几点

(1)共享读锁(S)之间是兼容的,但共享读锁(S)和排他写锁(X)之间,以及排他写锁之间(X)是互斥的,也就是说读和写是串行的。

(2)在一定条件下,MyISAM允许查询和插入并发执行,我们可以利用这一点来解决应用中对同一表和插入的锁争用问题。

(3)MyISAM默认的锁调度机制是写优先,这并不一定适合所有应用,用户可以通过设置LOW_PRIPORITY_UPDATES参数,或在INSERT、UPDATE、DELETE语句中指定LOW_PRIORITY选项来调节读写锁的争用。

(4)由于表锁的锁定粒度大,读写之间又是串行的,因此,如果更新操作较多,MyISAM表可能会出现严重的锁等待,可以考虑采用InnoDB表来减少锁冲突。

对于InnoDB表,主要有以下几点

(1)InnoDB的行销是基于索引实现的,如果不通过索引访问数据,InnoDB会使用表锁。

(2)InnoDB间隙锁机制,以及InnoDB使用间隙锁的原因。

(3)在不同的隔离级别下,InnoDB的锁机制和一致性读策略不同。

(4)MySQL的恢复和复制对InnoDB锁机制和一致性读策略也有较大影响。

(5)锁冲突甚至死锁很难完全避免。

在了解InnoDB的锁特性后,用户可以通过设计和SQL调整等措施减少锁冲突和死锁,包括:

  • 尽量使用较低的隔离级别
  • 精心设计索引,并尽量使用索引访问数据,使加锁更精确,从而减少锁冲突的机会。
  • 选择合理的事务大小,小事务发生锁冲突的几率也更小。
  • 给记录集显示加锁时,最好一次性请求足够级别的锁。比如要修改数据的话,最好直接申请排他锁,而不是先申请共享锁,修改时再请求排他锁,这样容易产生死锁。
  • 不同的程序访问一组表时,应尽量约定以相同的顺序访问各表,对一个表而言,尽可能以固定的顺序存取表中的行。这样可以大减少死锁的机会。
  • 尽量用相等条件访问数据,这样可以避免间隙锁对并发插入的影响。
  • 不要申请超过实际需要的锁级别;除非必须,查询时不要显示加锁。
  • 对于一些特定的事务,可以使用表锁来提高处理速度或减少死锁的可能。

十一、MVCC—多版本并发控制

1、什么是MVCC?

​ MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存

2、MVCC的作用

​ MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读—写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读

3、前提

什么是当前读和快照读?

当前读
像 select lock in share mode (共享锁), select for update; update; insert; delete (排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁

快照读
像不加锁的 select 操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即 MVCC ,可以认为 MVCC 是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本

说白了 MVCC 就是为了实现读-写冲突不加锁,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现

4、MVCC的优点

数据库并发场景有三种,分别为:

读-读:不存在任何问题,也不需要并发控制
读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失

MVCC 带来的好处是?
多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。 所以 MVCC 可以为数据库解决以下问题

​ 1、在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
​ 2、同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题

5、总结

​ 简而言之,MVCC 就是因为大佬们,不满意只让数据库采用悲观锁这样性能不佳的形式去解决读-写冲突问题,而提出的解决方案,所以在数据库中,因为有了 MVCC,所以我们可以形成两个组合:

MVCC + 悲观锁:MVCC解决读写冲突,悲观锁解决写写冲突
MVCC + 乐观锁:MVCC解决读写冲突,乐观锁解决写写冲突

​ 这种组合的方式就可以最大程度的提高数据库并发性能,并解决读写冲突,和写写冲突导致的问题

十二、日志

1、MySQL的逻辑架构

2、redo log

2.1、redo log的起因

​ 我们都知道,事务的四大特性里面有一个是持久性,具体来说就是只要事务提交成功,那么对数据库做的修改就被永久保存下来了,不可能因为任何原因再回到原来的状态。

​ 事务在运行过程中,都是在内存的Buffer Pool修改页面,事务提交后,这些被修改后的脏页并不会立刻刷盘(立刻刷盘开销太大,一方面是一个页面可能就修改了一点点,将整个页面刷盘不值当,另一方面是一个事务会涉及不同的页面,如果将这些页面都刷盘会产生很多的随机IO)。
​ 但如果不采取其他措施,那么在事务提交后MySQL发生故障,导致内存中数据丢失,那么这个已提交事务作出的更改也会丢失,那么mysql是如何保证内存和磁盘的一致性的呢?

​ 最简单的做法是在每次事务提交的时候,将该事务涉及修改的数据页全部刷新到磁盘中。但是这么做会有严重的性能问题,主要体现在两个方面:
​ 1) 因为Innodb是以页为单位进行磁盘交互的,而一个事务很可能只修改一个数据页里面的几个字节,这个时候将完整的数据页刷到磁盘的话,太浪费资源了!
​ 2) 一个事务可能涉及修改多个数据页,并且这些数据页在物理上并不连续,使用随机IO写入性能太差!

​ 所以这里就需要引入redo日志,对任意页面进行修改的操作都会生成redo日志,在事务提交时,只要保证生成的redo日志成功落盘即可,这样,即使MySQL发生故障导致内存中的数据丢失,也可以根据已落盘的redo日志恢复数据

2.2、redo log基本概念

​ redo log是InnoDB存储引擎层的日志,又称重做日志文件,用于记录事务操作的变化,记录的是数据修改之后的值,不管事务是否提交都会记录下来。一个事务生成的redo日志是按顺序写入磁盘的,是顺序IO,在实例和介质失败(media failure)时,redo log文件就能派上用场,如数据库掉电,InnoDB存储引擎会使用redo log恢复到掉电前的时刻,以此来保证数据的完整性。

​ redo log包括两部分:一个是内存中的日志缓冲(redo log buffer),另一个是磁盘上的日志文件(redo log file)。mysql每执行一条DML语句,先将记录写入redo log buffer,后续某个时间点再一次性将多个操作记录写到redo log file。这种先写日志,再写磁盘的技术就是MySQL里经常说到的WAL(Write-Ahead Logging) 技术。

2.3、redo log记录形式

redo log日志的大小是固定的,即记录满了以后就从头循环写。

​ redolog记录方式:

简单的redo日志——记录哪个表空间中的哪个页面从哪个位置开始的多少个节点要修改成什么

复杂的redo日志——记录了对哪个表空间的哪个页面进行修改,存储了对该页面进行修改操作的一些必备要素,重启时,MySQL会根据redo日志的类型,将redo日志中的必备要素作为参数,调用日志类型对应的函数,恢复数据

​ 在计算机操作系统中,用户空间(user space)下的缓冲区数据一般情况下是无法直接写入磁盘的,中间必须经过操作系统内核空间(kernel space)缓冲区(OS Buffer)。因此,redo log buffer写入redo log file实际上是先写入OS Buffer,然后再通过系统调用fsync()将其刷到redo log file中,过程如下:

MySQL数据库从入门到入土!_第13张图片

​ mysql支持三种将redo log buffer写入redo log file的时机,可以通过innodb_flush_log_at_trx_commit参数配置,各参数值含义如下:

MySQL数据库从入门到入土!_第14张图片

​ redo log实际上记录数据页的变更,而这种变更记录是没必要全部保存,因此redo log实现上采用了大小固定,循环写入的方式,当写到结尾时,会回到开头循环写日志。

MySQL数据库从入门到入土!_第15张图片

总结:确保事务的持久性。防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。

3、bin log

3.1、bin log基本概念

​ binlog是属于MySQL Server层面的,又称为归档日志,属于逻辑日志,是以二进制的形式记录的,用于记录数据库执行的写入性操作(不包括查询)信息,依靠binlog是没有crash-safe能力的

​ 逻辑日志、物理日志:
逻辑日志:可以简单理解为记录的就是sql语句
物理日志:因为mysql数据最终是保存在数据页中的,物理日志记录的就是数据页变更

​ 另外,binlog是通过追加的方式进行写入的,可以通过max_binlog_size参数设置每个binlog文件的大小,当文件大小达到给定值之后,会生成新的文件来保存日志。

3.2、bin log使用场景

​ 在实际应用中,binlog的主要使用场景有两个,分别是主从复制和数据恢复。
​ 1)主从复制:在Master端开启binlog,然后将binlog发送到各个Slave端,Slave端重放binlog从而达到主从数据一致。
​ 2) 数据恢复:通过使用mysqlbinlog工具来恢复数据。

3.3、bin log日志格式

​ bin log日志有三种格式,分别为STATMENT、ROW、MIXED。

​ 在 MySQL 5.7.7之前,默认的格式是STATEMENT,MySQL 5.7.7之后,默认值是ROW。日志格式通过binlog-format指定。

STATMENT 基于SQL语句的复制(statement-based replication, SBR),每一条会修改数据的sql语句会记录到binlog中。

​ 优点:不需要记录每一行的变化,减少了binlog日志量,节约了IO, 从而提高了性能;

​ 缺点:在某些情况下会导致主从数据不一致,比如执行sysdate()、slepp()等。

ROW 基于行的复制(row-based replication, RBR),不记录每条sql语句的上下文信息,仅需记录哪条数据被修改了。

​ 优点:不会出现某些特定情况下的存储过程、或function、或trigger的调用和触发无法被正确复制的问题;

​ 缺点:会产生大量的日志,尤其是alter table的时候会让日志暴涨

MIXED 基于STATMENT和ROW两种模式的混合复制(mixed-based replication, MBR),一般的复制使用STATEMENT模式保存binlog,对于STATEMENT模式无法复制的操作使用ROW模式保存binlog

4、redo log和bin log的区别

1、redo log是属于innoDB层面,binlog属于MySQL Server层面的,这样在数据库用别的存储引擎时可以达到一致性的要求。
2、redo log是物理日志,记录该数据页更新的内容;binlog是逻辑日志,记录的是这个更新语句的原始逻辑
3、redo log是循环写,日志空间大小固定;binlog是追加写,是指一份写到一定大小的时候会更换下一个文件,不会覆盖。
4、binlog可以作为恢复数据使用,主从复制搭建,redo log作为异常宕机或者介质故障后的数据恢复使用。

5、redo log是InnoDB存储引擎层的日志,binlog是MySQL Server层记录的日志, 两者都是记录了某些操作的日志(不是所有)自然有些重复(但两者记录的格式不同)。

5、 undo log

​ 数据库事务四大特性中有一个是原子性,具体来说就是 原子性是指对数据库的一系列操作,要么全部成功,要么全部失败,不可能出现部分成功的情况。

​ 实际上,原子性底层就是通过undo log实现的。

​ undo log主要记录了数据的逻辑变化,比如一条INSERT语句,对应一条DELETE的undo log,对于每个UPDATE语句,对应一条相反的UPDATE的undo log,这样在发生错误时,就能回滚到事务之前的数据状态。

​ undo log保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读

十三、运维

1、mysqldump

1.1、简介

​ mysqldump是MySQL自带的逻辑备份工具

​ 它的备份原理是通过协议连接到MySQL数据库,将需要备份的数据查询出来,将查询出的数据转换成对应的insert语句,当我们需要还原这些数据时,只要执行这些insert语句,即可将对应的数据还原。

1.2、备份命令

1.2.1、命令格式
mysqldump [选项] 数据库名 [表名] > 脚本名

mysqldump [选项] 数据库名 [选项 表名] > 脚本名

mysqldump [选项] all-database [选项] > 脚本名
1.2.2、选项说明
参数 缩写 含义
host -h 服务器IP地址
port -p 服务器端口号
user -u MySQL 用户名
pasword -p MySQL 密码
databases 指定要备份的数据库
all-databases 备份mysql服务器上的所有数据库
compact 压缩模式,产生更少的输出
comments 添加注释信息
complete-insert 输出完成的插入语句
lock-tables 备份前,锁定所有数据库表
no-create-db/no-create-info 禁止生成创建数据库语句
force 当出现错误时仍然继续备份操作
default-character-set 指定默认字符集
add-locks 备份数据库表时锁定数据库表
1.2.3、实例

备份所有数据库

mysqldump -uroot -p --all-database > /backup/mysqldump/all.db

备份指定数据库

mysqldump -uroot -p test > /backup/mysqldump/test.db

备份指定数据库、指定表(多个表以空格间隔)

mysqldump -uroot -p mysql db event > /backup/mysqldump/2table.db

备份指定数据库排除某些表

mysqldump -uroot -p test --ignore-table=test.t1 --ignore-table=test.t2 > /backup/mysqldump/test2.db

1.3、还原命令

1.3.1、系统行命令
mysqladmin -uroot -p create db_name 
mysql -uroot -p  db_name < /backup/mysqldump/db_name.db

注:在导入备份数据库前,db_name如果没有,是需要创建的; 而且与db_name.db中数据库名是一样的才可以导入。

1.3.2、source方法
mysql > use db_name
mysql > source /backup/mysqldump/db_name.db

2、show processlist

2.1、简介

​ 通常我们通过top检查发现mysqlCPU或者iowait过高那么解决这些问题都离不开通过 show processlist 查询当前mysql有些线程正在运行,然后分析其中的参数,找出那些有问题的线程,该kill的kill,该优化的优化!

注意:show processlist 只显示前100条 我们可以通过 show full processlist 显示全部

2.2、show processlist参数分析

参数 含义
Id 用户登录mysql时,系统分配的"connection_id",可以使用函数connection_id()查看
User 显示当前用户。如果不是root,这个命令就只显示用户权限范围的sql语句
Host 显示这个语句是从哪个ip的哪个端口上发的,可以用来跟踪出现问题语句的用户
db 显示这个进程目前连接的是哪个数据库
Command 显示当前连接的执行的命令,一般取值为休眠(sleep),查询(query),连接(connect)等
Time 显示这个状态持续的时间,单位是秒
State 显示使用当前连接的sql语句的状态,很重要的列。state描述的是语句执行中的某一个状态。一个sql语句,以查询为例,可能需要经过copying to tmp table、sorting result、sending data等状态才可以完成
Info 显示这个sql语句,是判断问题语句的一个重要依据

2.3、show processlist-state参数分析

state是当前sql的动作指令也是参考sql状态的重要依据

参数 含义
Checking table 正在检查数据表(这是自动的)。
Closing tables 正在将表中修改的数据刷新到磁盘中,同时正在关闭已经用完的表。这是一个很快的操作,如果不是这样的话,就应该确认磁盘空间是否已经满了或者磁盘是否正处于重负中。
Connect Out 复制从服务器正在连接主服务器。
Copying to tmp table on disk 由于临时结果集大于 tmp_table_size,正在将临时表从内存存储转为磁盘存储以此节省内存
Creating tmp table 正在创建临时表以存放部分查询结果。
deleting from main table
deleting from reference tables 服务器正在执行多表删除中的第二部分,正在删除其他表的记录。
Flushing tables 正在执行 FLUSH TABLES,等待其他线程关闭数据表。
Killed 发送了一个kill请求给某线程,那么这个线程将会检查kill标志位,同时会放弃下一个kill请求。MySQL会在每次的主循环中检查kill标志位,不过有些情况下该线程可能会过一小段才能死掉。如果该线程程被其他线程锁住了,那么kill请求会在锁释放时马上生效。
Locked 被其他查询锁住了。
Sending data 正在处理 SELECT 查询的记录,同时正在把结果发送给客户端。
Sorting for group 正在为 GROUP BY 做排序。
Sorting for order 正在为 ORDER BY 做排序。
Opening tables 这个过程应该会很快,除非受到其他因素的干扰。例如,在执 ALTER TABLE 或 LOCK TABLE 语句行完以前,数据表无法被其他线程打开。 正尝试打开一个表。
Removing duplicates 正在执行一个 SELECT DISTINCT 方式的查询,但是MySQL无法在前一个阶段优化掉那些重复的记录。因此,MySQL需要再次去掉重复的记录,然后再把结果发送给客户端。
Reopen table 获得了对一个表的锁,但是必须在表结构修改之后才能获得这个锁。已经释放锁,关闭数据表,正尝试重新打开数据表。
Repair by sorting 修复指令正在排序以创建索引。
Repair with keycache 修复指令正在利用索引缓存一个一个地创建新索引。它会比 Repair by sorting 慢些。
Searching rows for update 正在讲符合条件的记录找出来以备更新。它必须在 UPDATE 要修改相关的记录之前就完成了。
Sleeping 正在等待客户端发送新请求。
System lock 正在等待取得一个外部的系统锁。如果当前没有运行多个 mysqld 服务器同时请求同一个表,那么可以通过增加 –skip-external-locking参数来禁止外部系统锁。
Upgrading lock INSERT DELAYED 正在尝试取得一个锁表以插入新记录。
Updating 正在搜索匹配的记录,并且修改它们。
User Lock 正在等待 GET_LOCK()。
Waiting for tables 该线程得到通知,数据表结构已经被修改了,需要重新打开数据表以取得新的结构。然后,为了能的重新打开数据表,必须等到所有其他线程关闭这个表。以下几种情况下会产生这个通知:FLUSH TABLES tbl_name, ALTER TABLE, RENAME TABLE, REPAIR TABLE, ANALYZE TABLE, 或 OPTIMIZE TABLE。
waiting for handler insert INSERT DELAYED 已经处理完了所有待处理的插入操作,正在等待新的请求。
Waiting for net, reading from net, writing to net 偶尔出现无妨

3、show engine innodb status

3.1、简介

​ show engine innodb status是mysql提供的一个用于查看innodb引擎时间信息的工具,就目前来说有两处比较常用的地方:

​ 1)死锁分析

​ 2)innodb内存使用情况

3.2、分析

mysql> show engine innodb status ;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PN74yz7x-1667548411554)(C:\Users\19407\Desktop\工作文件\学习整理\image-20221104153330284.png)]

| Type   | Name | Status | InnoDB |      | 
=====================================
2022-11-04 14:54:30 0x7ff0bc053700 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 4 seconds
//计算出这一平均值的时间间隔,即自上次输出以来的时间,或者是距上次内部复位的时长
    
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops:
18 srv_active,
0 srv_shutdown, 
30345 srv_idle srv_master_thread log flush and writes: 30363
//Srv_master_thread loops  Master线程的循环次数,master线程在每次loop过程中都会sleep,sleep的时间为1秒。而在每次loop的过程中会选择active、shutdown、idle中一种状态执行。Master线程在不停循环,所以其值是随时间递增的。
    
//Srv_shutdown  这个参数的值一直为0,因为srv_shutdown只有在mysql服务关闭的时候才会增加
    
//Srv_idle  这个参数是在master线程空闲的时候增加,即没有任何数据库改动操作时
    
//Log_flush_and_write  Master线程在后台会定期刷新日志,日志刷新是由参数innodb_flush_log_at_timeout参数控制前后刷新时间差
    
//Master thread是一个非常核心的后台线程,主要负责缓冲池中的数据异步刷新到磁盘,保证数据的一致性。Master thread具有最高的线程优先级别,其内部由多个循环(loop)组成:主循环,后台循环,刷新循环,暂停循环。
    
//注:Background thread部分信息为统计信息,即mysql服务启动之后该部分值会一直递增,因为它显示的是自mysqld服务启动之后master线程所有的loop和log刷新操作。通过对比active和idle的值,可以获知系统整体负载情况。Active的值越大,证明服务越繁忙。
    
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 54
OS WAIT ARRAY INFO: signal count 50
RW-shared spins 0, rounds 68, OS waits 34
RW-excl spins 0, rounds 0, OS waits 0
RW-sx spins 0, rounds 0, OS waits 0
Spin rounds per wait: 68.00 RW-shared, 0.00 RW-excl, 0.00 RW-sx
    
//reservation count: 线程尝试访问os wait array的次数,大于等于线程进入os wait状态的线程数(因为尝试放入os wait array可能不成功,不成功的时候reservation count也会++)
    
//Signal count:线程被唤醒的次数,进入os wait的线程,在占用资源线程释放mutex的时候会通过signal唤醒等待线程。
    
//Mutex spin waits 是线程无法获取锁,而进入的spin wait
    
//rounds 是spin wait进行轮询检查Mutextes的次数
    
//OS waits 是线程放弃spin-wait进入挂起状态

    
/*
--Thread 140653057947392 has waited at btr0pcur.c line 437 for 0.00 seconds the semaphore:

S-lock on RW-latch at 0x7ff536c7d3c0 created in file buf0buf.c line 916

a writer (thread id 140653057947392) has reserved it in mode  exclusive

number of readers 0, waiters flag 1, lock_word: 0

Last time read locked in file row0sel.c line 3097

Last time write locked in file /usr/local/src/soft/mysql-5.5.24/storage/innobase/buf/buf0buf.c line 3151


--Thread 140653677291264 has waited at btr0pcur.c line 437 for 0.00 seconds the semaphore:

S-lock on RW-latch at 0x7ff53945b240 created in file buf0buf.c line 916

a writer (thread id 140653677291264) has reserved it in mode  exclusive

number of readers 0, waiters flag 1, lock_word: 0

Last time read locked in file row0sel.c line 3097

Last time write locked in file /usr/local/src/soft/mysql-5.5.24/storage/innobase/buf/buf0buf.c line 3151
*/

//这部分显示的是当前正在等待互斥量的innodb线程,在这里可以看到有两个线程正在等待,每一个都是以--Thread <数字> has waited...开始,这一段内容在正常情况下应该是空的(即查看的时候没有这部分内容),除非服务器运行着高并发的工作负载,促使innodb采取让操作系统等待的措施,除非你对innodb源码熟悉,否则这里看到的最有用的信息就是发生线程等待的代码文件名 /usr/local/src/soft/mysql-5.5.24/storage/innobase/buf/buf0buf.c line 3151
    
//buf0buf.c实际上表示服务器有buffer pool争用的情况。

------------
TRANSACTIONS
------------
Trx id counter 1332
Purge done for trx's n:o < 1332 undo n:o < 0 state: running but idle
History list length 0	//历史记录的长度
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 422147236702032, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
--------
FILE I/O
--------
I/O thread 0 state: waiting for completed aio requests (insert buffer thread)
I/O thread 1 state: waiting for completed aio requests (log thread)
I/O thread 2 state: waiting for completed aio requests (read thread)
I/O thread 3 state: waiting for completed aio requests (read thread)
I/O thread 4 state: waiting for completed aio requests (read thread)
I/O thread 5 state: waiting for completed aio requests (read thread)
I/O thread 6 state: waiting for completed aio requests (write thread)
I/O thread 7 state: waiting for completed aio requests (write thread)
I/O thread 8 state: waiting for completed aio requests (write thread)
I/O thread 9 state: waiting for completed aio requests (write thread)
Pending normal aio reads: [0, 0, 0, 0] , aio writes: [0, 0, 0, 0] ,
//读线程和写线程挂起操作的数目等,aio的意思是异步I/O
 ibuf aio reads:, log i/o's:, sync i/o's:
//insert buffer thread挂起的fsync()操作数目等
Pending flushes (fsync) log: 0; buffer pool: 0
//log thread挂起的fsync()操作数目等
326 OS file reads, 352 OS file writes, 187 OS fsyncs
0.00 reads/s, 0 avg bytes/read, 0.00 writes/s, 0.00 fsyncs/s
//这行显示了读、写和fsync()调用执行的数目,在你的机器环境负载下这些绝对值可能会有所不同,因此更重要的是监控它们过去一段时间内是如何改变的。
    
//insert buffer thread:负责插入缓冲合并,如:记录被从插入缓冲合并到表空间中

//log thread:负责异步刷事务日志

//read thread:执行预读操作以尝试预先读取innodb预感需要的数据

//write thread:刷新脏页缓冲
    
//三行挂起读写线程、缓冲池线程、日志线程的统计信息的值是检测I/O受限的应用的一个好方法,如果这些I/O大部分有挂起操作,那么负载可能I/O受限。在linux系统下使用参数:innodb_read_io_threads和innodb_write_io_threads两个变量来配置读写线程的数量,默认为各4个线程。

-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 2 merges
//这行显示了关于size(size 12代表了已经合并记录页的数量)、free list(代表了插入缓冲中空闲列表长度)和seg size大小(seg size 27572显示了当前insert buffer的长度,大小为27572*16K=440M左右)的信息。18074934 merges代表合并插入的次数
merged operations:
 insert 0, delete mark 0, delete 0
//这个标签下的一行信息insert,delete mark,delete分别表示merge操作合并了多少个insert buffer,delete buffer,purge buffer
discarded operations:
 insert 0, delete mark 0, delete 0
//这个标签下的一行信息表示当change buffer发生merge时表已经被删除了,就不需要再将记录合并到辅助索引中了
Hash table size 34679, node heap has 0 buffer(s)
//这行显示了自使用哈希索引的状态,其中,Hash table size 87709057表示AHI的大小,node heap has 10228 buffer(s)表示AHI的使用情况
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
0.00 hash searches/s, 0.00 non-hash searches/s
//这行显示了在头部第1部分提及的时间内Innodb每秒完成了多少哈希索引操作,1741.05 hash searches/s表示每秒使用AHI搜索的情况,539.48 non-hash searches/s表示每秒没有使用AHI搜索的情况(因为哈希索引只能用于等值查询,而范围查询,模糊查询是不能使用哈希索引的。),通过hash searches: non-hash searches的比例大概可以了解使用哈希索引后的效率,哈希索引查找与非哈希索引查找的比例仅供参考,自适应哈希索引无法配置,但是可以通过innodb_adaptive_hash_index=ON|OFF参数来选择是否需要这个特性。

/*注:
innodb从1.0.x开始引入change buffer,可以视为insert buffer的升级,从这个版本开始,innodb可以对DML操作(insert,delete,update)都进行缓冲,他们分别是insert buffer,delete buffer,purge buffer,当然和之前insert buffer一样,change buffer适用对象仍然是非唯一索引的辅助索引,因为没有update buffer,所以对一条记录进行update的操作可以分为两个过程:

A:将记录标记为删除

B:真正将记录删除

因此,delete buffer对应update 操作的第一个过程,即将记录标记为删除,purge buffer对应update的第二个过程,即将记录真正地删除
*/
     
---
LOG
---
Log sequence number 2791718
//这行显示了当前最新数据产生的日志序列号     
Log flushed up to   2791718
//这行显示了日志已经刷新到哪个位置了(已经落盘到事务日志中的日志序列号)
Pages flushed up to 2791718
Last checkpoint at  2791709
//这行显示了上一次检查点的位置(一个检查点表示一个数据和日志文件都处于一致状态的时刻,并且能用于恢复数据),如果上一次检查点落后与上一行太多,并且差异接近于事务日志文件的大小,Innodb会触发“疯狂刷”,这对性能而言非常糟糕。
0 pending log flushes, 0 pending chkp writes
//这行显示了当前挂起的日志读写操作,可以将这行的值与第7部分FILE I/O对应的值做比较,以了解你的I/O有多少是由于日志系统引起的。     
114 log i/o's done, 0.00 log i/o's/second
//这行显示了日志操作的统计和每秒日志I/O数,可以将这行的值与第7部分FILE I/O对应的值做比较,以了解你的I/O有多少是由于日志系统引起的
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 137428992
//为innodb 分配的总内存数(byte)
Dictionary memory allocated 151216
//为innodb数据字典分配的内存数(byte)
Buffer pool size   8192
//innodb_buffer_pool的大小(page)     
Free buffers       7840
//innodb_buffer_pool lru列表中的空闲页面数量     
Database pages     352
//innodb_buffer_pool lru列表中的非空闲页面数     
Old database pages 0
//innodb_buffer_pool old子列表的页面数量    
Modified db pages  0
//innodb_buffer_pool 中脏页的数量     
Pending reads      0
//挂起读的数量     
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 293, created 63, written 202
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 352, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB, 0 queries in queue
//这行显示了innodb内核内有多少个线程,队列中有多少个线程,队列中的查询是innodb为限制并发执行的线程数量而不运行进入内核的线程。查询在进入队列之前会休眠等待。
0 read views open inside InnoDB
//这行显示了有多少打开的innodb读视图,读视图是包含事务开始点的数据库内容的MVCC快照,你可以看看某特定事务在第6部分TRANSACTIONS是否有读视图
Process ID=6436, Main thread ID=140671960065792, state: sleeping
//这行显示了内核的主线程状态
Number of rows inserted 31, updated 1, deleted 0, read 56
//这行显示了多少行被插入,更新和删除,读取
0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.00 reads/s
//这行显示了对应上面一行的每秒平均值,如果想查看innodb有多少工作量在进行,那么这两行是很好的参考值
----------------------------
END OF INNODB MONITOR OUTPUT
============================

参考博客

Linux安装mysql5.7(rpm方式)
聊聊数据库中的关键字——字段、属性、列、元组、记录、表、主键、外键
mysql中编写一个存储过程_mysql编写存储过程(1)
数据库存储过程讲解与实例
Linux中MySQL数据库的使用④-----常用查询语句、常用函数
MySQL常用函数大全(总结篇)
ISAM、MyISAM、InnoDB、ACID详解
MyISAM与InnoDB 的区别(9个不同点)
数据库索引详解
如何使用事务
Mysql数据库中的各种锁
面试官:MVCC是如何实现的?
MVCC详解,深入浅出简单易懂
MySQL三大日志——binlog、redoLog、undoLog详解
show processlist 史上最全参数详解及解决方案
MySQL之mysqldump的使用
show engine innodb status解读
【MySQL中auto_increment有什么作用?】
【MySQL笔记】正确的理解MySQL的MVCC及实现原理

你可能感兴趣的:(MySQL,mysql,数据库,centos,运维)