本文涵盖视图、存储过程、存储函数、触发器、锁机制、InnoDB引擎及MySQL管理工具。
- 视图可简化操作并提高安全性
- 存储过程和函数能实现复杂逻辑与重复利用
- 触发器用于记录操作日志
- 锁机制保障并发数据一致性
- InnoDB引擎支持事务与MVCC实现高效并发
- MySQL管理工具助力数据库的日常管理和维护
1. 视图/存储过程/触发器
1.1 视图
在MySQL中,视图(View)是一种虚拟表,其内容由SQL查询定义 。视图并不实际存储数据,而是基于一个或多个基础表(实际的物理表)的查询结果动态生成数据。以下是关于视图的简要介绍:
1.1.1 介绍
视图是基于SQL语句的结果集的可视化表示。它类似于一个表,但并不实际存储数据。视图的结构和数据是通过定义它的查询语句来确定的。
1.1.2 语法
1). 创建
sql
#select语句执行的时私用的表就是当前创建视图的基表
CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句 [ WITH [
CASCADED | LOCAL ] CHECK OPTION ]
2). 查询
sql
查看创建视图语句:SHOW CREATE VIEW 视图名称;
查看视图数据:SELECT * FROM 视图名称 ...... ;
3). 修改
sql
方式一:CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句 [ WITH
[ CASCADED | LOCAL ] CHECK OPTION ]
方式二:ALTER VIEW 视图名称[(列名列表)] AS SELECT语句 [ WITH [ CASCADED |
LOCAL ] CHECK OPTION ]
4). 删除
sql
DROP VIEW [IF EXISTS] 视图名称 [,视图名称] ...
我们来测试能不能通过视图来插入、更新数据
sql
create or replace view stu_v_1 as select id,name from student where id <= 10 ;
select * from stu_v_1;
insert into stu_v_1 values(6,'Tom');
insert into stu_v_1 values(17,'Tom22');
基表 视图
然而,视图中并没有相关的id为6的数据**(由于视图中查询语句限制 id<=10)**
因为我们在创建视图的时候,指定的条件为 id<=10, id为17的数据,是不符合条件的,所以没有查 询出来 ,但是这条数据确实是已经成功的插入到了基表中。
如果我们定义视图时,如果指定了条件,然后我们在插入、修改、删除数据时,是否可以做到必须满足条件才能操作,否则不能够操作呢? 答案是可以的,这就需要借助于视图的检查选项了。
1.1.3 检查选项
当使用**WITH CHECK OPTION
** 子句创建视图时,MySQL会在通过视图进行插入、更新或删除操作时,检查每一行是否符合视图的定义。此外,如果视图是基于另一个视图创建的,MySQL还会检查依赖视图中的规则,以确保数据的一致性。为了确定检查的范围,MySQL提供了两个选项:CASCADED
(级联)和**LOCAL
** ,默认值为**CASCADED
**。
-
CASCADED(级联) :如果视图(如**
v2
** )是基于另一个视图(如**v1
** )创建的,并且在**v2
** 视图创建时指定了**WITH CHECK OPTION CASCADED
** ,那么在执行数据修改操作时,MySQL不仅会检查**v2
** 视图的规则,还会级联检查v1
视图的规则,即使v1
视图在创建时未指定WITH -
LOCAL(本地) :如果视图(如**
v2
** )是基于另一个视图(如**v1
** )创建的,并且在**v2
** 视图创建时指定了**WITH CHECK OPTION LOCAL
** ,那么在执行数据修改操作时,MySQL只会检查**v2
** 视图本身的规则,而不会检查其依赖的**v1
** 视图的规则,即使v1
视图在创建时未指定**WITH CHECK OPTION
**。


1.1.4 视图的更新

1.1.5 视图作用
1). 简单
视图不仅可以简化用户对数据的理解,也可以简化他们的操作。那些被经常使用的查询可以被定义为视 图,从而使得用户不必为以后的操作每次指定全部的条件。
2). 安全
数据库可以授权,但不能授权到数据库特定行和特定的列上。通过视图用户只能查询和修改他们所能见 到的数据
3). 数据独立
视图可帮助用户屏蔽真实表结构变化带来的影响
1.1.6 案例

sql
create view tb_user_view as select id,name,profession,age,gender,status,createtime
from tb_user;
select * from tb_user_view;
create view tb_stu_course_view as select s.name student_name , s.no student_no ,
c.name course_name from student s, student_course sc , course c where s.id =
sc.studentid and sc.courseid = c.id;
select * from tb_stu_course_view;
2.2 存储过程
2.2.1 介绍


2.2.2 基本语法
1). 创建
sql
CREATE PROCEDURE 存储过程名称 ([ 参数列表 ])
BEGIN-- SQL语句
END ;
2). 调用
sql
CALL 名称 ([ 参数 ]);
3). 查看
sql
SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'xxx'; -- 查询指
定数据库的存储过程及状态信息
SHOW CREATE PROCEDURE 存储过程名称 ; -- 查询某个存储过程的定义
4). 删除
sql
DROP PROCEDURE [ IF EXISTS ] 存储过程名称 ;


在命令行中,执行创建存储过程的SQL时,需通过关键字delimiter指定SQL语句的结束符。
2.2.3 变量
在MySQL中变量分为三种类型: 系统变量、用户定义变量、局部变量。
2.2.3.1 系统变量
系统变量 是MySQL服务器提供,不是用户定义的,属于服务器层面。分为全局变量(GLOBAL)、会话变量(SESSION)
1). 查看系统变量
sql
SHOW [ SESSION | GLOBAL ] VARIABLES ; -- 查看所有系统变量
SHOW [ SESSION | GLOBAL ] VARIABLES LIKE '......'; -- 可以通过LIKE模糊匹配方
式查找变量
SELECT @@[SESSION | GLOBAL] 系统变量名; -- 查看指定变量的值
2). 设置系统变量
sql
SET [ SESSION | GLOBAL ] 系统变量名 = 值 ;
SET @@[SESSION | GLOBAL]系统变量名 = 值 ;

如果没有指定SESSION/GLOBAL,默认是SESSION,会话变量。
mysql服务重启之后,所设置的全局参数会失效,要想不失效,可以在 /etc/my.cnf 中配置。
A. 全局变量(GLOBAL): 全局变量针对于所有的会话。
B. 会话变量(SESSION): 会话变量针对于单个会话,在另外一个会话窗口就不生效了。
2.2.3.2 用户定义变量
用户定义变量是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用 "@变量 名" 使用就可以。其作用域为当前连接。
1). 赋值
sql
#方式一
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 表名;

注意: 用户定义的变量无需对其进行声明或初始化,只不过获取到的值为NULL。
2.2.3.3 局部变量
局部变量是根据需要定义的在局部生效的变量,访问之前,需要DECLARE声明。可用作存储过程内的 局部变量和输入参数,局部变量的范围是在其内声明的BEGIN ... END块。
1). 声明
sql
DECLARE 变量名 变量类型 [DEFAULT ... ] ;
变量类型就是数据库字段类型:INT、BIGINT、CHAR、VARCHAR、DATE、TIME等。
2). 赋值
sql
SET 变量名 = 值 ;
SET 变量名 := 值 ;
SELECT 字段名 INTO 变量名 FROM 表名 ... ;
演示示例:
sql
#声明局部变量 - declare-- 赋值
create procedure p2()
begin
declare stu_count int default 0;
select count(*) into stu_count from student;
select stu_count;
end;
call p2();
2.2.4 if
1). 介绍
if 用于做条件判断,具体的语法结构为:
sql
IF 条件1 THEN
.....
ELSEIF 条件2 THEN -- 可选
.....
ELSE -- 可选
.....
END IF
在if条件判断结构中,ELSE IF 结构可以有多个,也可以没有。 ELSE结构可以有,也可以没有。
案例:

2.2.5 参数
1). 介绍
参数的类型,主要分为以下三种:IN、OUT、INOUT。 具体的含义如下:

2). 用法
sql
CREATE PROCEDURE 存储过程名称 ([ IN/OUT/INOUT 参数名 参数类型 ])
BEGIN
-- SQL语句
END ;
3). 练习

4). 代码实现
sql
create procedure p4(in score int, out result varchar(10))
begin
if score >= 85 then
set result := '优秀';
elseif score >= 60 then
set result := '及格';
else
set result := '不及格';
end if;
end;
-- 定义用户变量 @result来接收返回的数据, 用户变量可以不用声明
call p4(18, @result);
select @result
sql
create procedure p5(inout score double)
begin
set score := score * 0.5;
end;
set @score = 198;
call p5(@score);
select @score;
2.2.6 case
1). 介绍
case结构及作用,和我们在基础篇中所讲解的流程控制函数很类似。有两种语法格式:
语法1:
sql
-- 含义: 当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;
语法2:
sql
-- 含义: 当条件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;
案例:

sql
create procedure p6(in month int)
begin
declare result varchar(10);
case
when month >= 1 and month <= 3 then
set result := '第一季度';
when month >= 4 and month <= 6 then
set result := '第二季度';
when month >= 7 and month <= 9 then
set result := '第三季度';
when month >= 10 and month <= 12 then
set result := '第四季度';
else
set result := '非法参数';
end case ;
select concat('您输入的月份为: ',month, ', 所属的季度为: ',result);
end;
call p6(16);
2.2.7 while
1). 介绍
while 循环是有条件的循环控制语句。满足条件后,再执行循环体中的SQL语句。具体语法为:
sql
-- 先判定条件,如果条件为true,则执行逻辑,否则,不执行逻辑
WHILE 条件 DO
SQL逻辑...
END WHILE;
2). 案例
计算从1累加到n的值,n为传入的参数值。
sql
create procedure p1(in n int)
begin
declare total int default 0;
while n>0 do
set total := total + n;
set n := n - 1;
end while;
select total;
end;
call p1(100);
2.2.8 repeat
1). 介绍
repeat是有条件的循环控制语句, 当满足until声明的条件的时候,则退出循环 。具体语法为:
sql
-- 先执行一次逻辑,然后判定UNTIL条件是否满足,如果满足,则退出。如果不满足,则继续下一次循环
REPEAT
SQL逻辑...
UNTIL 条件
END REPEAT;
2). 案例
计算从1累加到n的值,n为传入的参数值。(使用repeat实现)
sql
-- A. 定义局部变量, 记录累加之后的值;-- B. 每循环一次, 就会对n进行-1 , 如果n减到0, 则退出循环
create procedure p8(in n int)
begin
declare total int default 0;
repeat #后面没有其他语句
set total := total + n;
set n := n - 1;
until n <= 0 #后面没有分号
end repeat;
select total;
end;
call p8(10);
call p8(100);
2.2.9 loop
1). 介绍
LOOP 实现简单的循环,如果不在SQL逻辑中增加退出循环的条件,可以用其来实现简单的死循环。 LOOP可以配合一下两个语句使用: LEAVE :配合循环使用,退出循环。 ITERATE:必须用在循环中,作用是跳过当前循环剩下的语句,直接进入下一次循环。
sql
[begin_label:] LOOP
SQL逻辑...
END LOOP [end_label];
sql
LEAVE label; -- 退出指定标记的循环体
ITERATE label; -- 直接进入下一次循环
上述语法中出现的 begin_label,end_label,label 指的都是我们所自定义的标记。
2). 案例

sql
A. 定义局部变量, 记录累加之后的值;-- B. 每循环一次, 就会对n进行-1 , 如果n减到0, 则退出循环 ----> leave xx
create procedure p9(in n int)
begin
declare total int default 0;
sum:loop
if n<=0 then
leave sum;
end if;
set total := total + n;
set n := n - 1;
end loop sum;
select total;
end;
call p9(100);
sql
-- A. 定义局部变量, 记录累加之后的值;-- B. 每循环一次, 就会对n进行-1 , 如果n减到0, 则退出循环 ----> leave xx-- C. 如果当次累加的数据是奇数, 则直接进入下一次循环. --------> iterate xx
create procedure p10(in n int)
begin
declare total int default 0;
#自定义循环名称(sum)
sum:loop
if n<=0 then
leave sum;
end if;
if n%2 = 1 then
end if;
set n := n - 1;
iterate sum;
set total := total + n;
set n := n - 1;
end loop sum;
select total;
end;
call p10(100);
2.2.10 游标
1). 介绍
游标(CURSOR)是用来存储查询结果集的数据类型 , 在存储过程和函数中可以使用游标对结果集进 行循环的处理。游标的使用包括游标的声明、OPEN、FETCH 和 CLOSE,其语法分别如下。
A. 声明游标
sql
DECLARE 游标名称 CURSOR FOR 查询语句 ;
B. 打开游标
sql
OPEN 游标名称 ;
C. 获取游标记录
sql
FETCH 游标名称 INTO 变量 [, 变量 ] ;
D. 关闭游标
sql
CLOSE 游标名称 ;
2). 案例
根据传入的参数uage,来查询用户表tb_user中,所有的用户年龄小于等于uage的用户姓名 (name)和专业(profession),并将用户的姓名和专业插入到所创建的一张新表 (id,name,profession)中。
sql
-- 逻辑:
-- A. 声明游标, 存储查询结果集
-- B. 准备: 创建表结构
-- C. 开启游标
-- D. 获取游标中的记录
-- E. 插入数据到新表中
-- F. 关闭游标
create procedure p11(in uage int)
begin
declare uname varchar(100);
declare upro varchar(100);
-- A
declare u_cursor cursor for select name,profession from tb_user where age <=
uage;
drop table if exists tb_user_pro;
-- B
create table if not exists tb_user_pro(
id int primary key auto_increment,
name varchar(100),
profession varchar(100)
);
-- C
open u_cursor;
while true do
-- D
fetch u_cursor into uname,upro;
-- E
insert into tb_user_pro values (null, uname, upro);
end while;
-- F
close u_cursor;
end;
call p11(30);


上述的功能,虽然我们实现了,但是逻辑并不完善,而且程序执行完毕,获取不到数据,数据库还报 错。接下来,我们就需要来完成这个存储过程,并且解决这个问题。
要想解决这个问题,就需要通过MySQL中提供的 条件处理程序 Handler 来解决。
2.2.11 条件处理程序
1). 介绍
条件处理程序(Handler)可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤。具体语法为:
sql
DECLARE
...
handler_action
statement ;
handler_action 的取值:
HANDLER FOR
CONTINUE: 继续执行当前程序
EXIT: 终止执行当前程序
condition_value 的取值:
condition_value [, condition_value]
SQLSTATE sqlstate_value: 状态码,如 02000
SQLWARNING: 所有以01开头的SQLSTATE代码的简写
NOT FOUND: 所有以02开头的SQLSTATE代码的简写
SQLEXCEPTION: 所有没有被SQLWARNING 或 NOT FOUND捕获的SQLSTATE代码的简写
A. 通过SQLSTATE指定具体的状态码
sql
-- 声明条件处理程序 : 当SQL语句执行抛出的状态码为02000时,将关闭游标u_cursor,并退出
declare exit handler for SQLSTATE '02000' close u_cursor;
B. 通过SQLSTATE的代码简写方式 NOT FOUND
sql
-- 声明条件处理程序 : 当SQL语句执行抛出的状态码为02开头时,将关闭游标u_cursor,并退出
declare exit handler for not found close u_cursor;
2.3 存储函数
1). 介绍
存储函数是有返回值的存储过程,存储函数的参数只能是IN类型的。具体语法如下:
sql
CREATE FUNCTION 存储函数名称 ([ 参数列表 ])
RETURNS type [characteristic ...]
BEGIN-- SQL语句
RETURN ...;
END ;
characteristic说明:
- NO SQL :不包含 SQL 语句。
- DETERMINISTIC:相同的输入参数总是产生相同的结果。
- READS SQL DATA:包含读取数据的语句,但不包含写入数据的语句。
2). 案例
计算从1累加到n的值,n为传入的参数值。
sql
create function fun1(n int)
returns int deterministic
begin
declare total int default 0;
while n>0 do
set total := total + n;
set n := n - 1;
end while;
return total;
end;
select fun1(50);
2.4 触发器
2.4.1 介绍

触发器与AOP的相似点
关注点分离
MySQL触发器:触发器允许开发者在数据库层面定义一些自动执行的操作,这些操作与主业务逻辑分离。例如,当表中的数据被插入、更新或删除时,触发器可以自动执行一些额外的逻辑,如记录日志、更新关联表等。这样,触发器将这些辅助逻辑从业务逻辑中分离出来,使得主业务逻辑更加清晰。
面向切面编程(AOP):AOP的核心思想是将横切关注点(如日志记录、事务管理、权限校验等)从业务逻辑中分离出来。通过定义切面(Aspect),这些横切关注点可以独立于主业务逻辑存在,从而提高代码的可维护性和可扩展性。
共同点:两者都实现了关注点的分离,将一些通用的、辅助性的逻辑从业务逻辑中分离出来,避免了代码的重复和混乱,使得主逻辑更加清晰和简洁。
自动执行
MySQL触发器:触发器是自动触发的。当满足特定的数据库操作(如INSERT、UPDATE、DELETE)时,触发器会自动执行预定义的SQL语句或逻辑,无需在应用程序代码中显式调用。
面向切面编程(AOP):AOP框架(如Spring AOP)会自动拦截方法调用,并在合适的位置(如方法执行前、执行后、抛出异常时等)插入切面逻辑。开发者只需要定义好切面和切入点,框架会自动处理切面逻辑的执行。
共同点:两者都具有自动执行的特性,无需在主逻辑中显式调用辅助逻辑,减少了开发者的负担,提高了代码的可维护性。
增强功能
MySQL触发器:通过触发器,可以在数据库层面增强数据的完整性和一致性。例如,可以在插入或更新数据时自动校验数据的有效性,或者在删除数据时自动清理相关联的数据。
面向切面编程(AOP):通过切面,可以在不修改主业务逻辑代码的情况下,为系统添加额外的功能,如日志记录、性能监控、事务管理等。
共同点:两者都可以在不直接修改主逻辑代码的情况下,为系统添加额外的功能,增强了系统的整体能力。
2.4.2 语法
1). 创建
sql
CREATE TRIGGER trigger_name
BEFORE/AFTER INSERT/UPDATE/DELETE
ON tbl_name FOR EACH ROW -- 行级触发器
BEGIN
trigger_stmt ;
END;
2). 查看
sql
SHOW TRIGGERS ;
3). 删除
sql
DROP TRIGGER [schema_name.]trigger_name ; --如果没有指定 schema_name,默认当前数据库
4). 案例

sql
create table user_logs(
id int(11) not null auto_increment,
operation varchar(20) not null comment '操作类型, insert/update/delete',
operate_time datetime not null comment '操作时间',
operate_id int(11) not null comment '操作的ID',
operate_params varchar(500) comment '操作参数',
primary key(`id`)
)engine=innodb default charset=utf8;
--记录插入操作的日志,故而仅仅记录插入的操作信息
create trigger carbon_insert_trigger
after insert on carbon_emission for each row
begin
insert into user_logs(id, operation, operate_time, operate_id, operate_params) VALUES
(null,'insert',now(),new.id,concat('插入的数据内容为:id=',NEW.id,'name=',new.name,',description=',new.description));
end;
show triggers ;
--记录修改操作的日志,故而仅仅记录修改前后的操作信息
create trigger tb_user_update_trigger
after update on tb_user for each row
begin
insert into user_logs(id, operation, operate_time, operate_id, operate_params)
VALUES
(null, 'update', now(), new.id,
concat('更新之前的数据: id=',old.id,',name=',old.name, ', phone=',
old.phone, ', email=', old.email, ', profession=', old.profession,
' | 更新之后的数据: id=',new.id,',name=',new.name, ', phone=',
NEW.phone, ', email=', NEW.email, ', profession=', NEW.profession));
end;
--记录删除操作的日志,故而仅仅记录删除的操作信息
create trigger tb_user_delete_trigger
after delete on tb_user for each row
begin
insert into user_logs(id, operation, operate_time, operate_id, operate_params)
VALUES
(null, 'delete', now(), old.id,
concat('删除之前的数据: id=',old.id,',name=',old.name, ', phone=',
old.phone, ', email=', old.email, ', profession=', old.profession));
end;
3. 锁
3.1 概述
在计算机系统中,锁是一种用于协调多个进程或线程并发访问共享资源的机制。数据库作为一种供众多用户共享的数据资源,其并发访问的一致性和有效性保障是数据库设计与运行的关键问题之一。锁冲突更是直接影响数据库并发访问性能的重要因素,因此,锁机制在数据库中显得尤为重要且复杂。
MySQL数据库中的锁,按照锁的粒度划分,主要有以下三类:
-
全局锁:锁定数据库中的所有表,对整个数据库实例进行加锁,适用于全库范围内的维护操作或数据备份等场景。
-
表级锁:每次操作锁定整张表,适用于对整表进行批量操作或维护时,但并发性能相对较低,因为同一时间只能有一个操作对表进行写入。
-
行级锁:每次操作仅锁定对应的行数据,能够实现更细粒度的并发控制,允许多个事务同时对表中的不同行进行操作,从而提高并发性能,但行级锁的管理和维护成本相对较高。
不同粒度的锁适用于不同的操作场景,合理选择锁的类型对于提升数据库性能和保障数据一致性至关重要。
3.2 全局锁
3.2.1 介绍
全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞。 其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。为什么全库逻辑备份,就需要加全就锁呢?
A. 我们一起先来分析一下不加全局锁,可能存在的问题。

在进行数据备份时,如果按照以下顺序操作,可能会导致备份数据不一致的问题:
首先备份了
tb_stock
库存表。然后在业务系统中执行了下单操作,扣减库存并生成订单(更新
tb_stock
表,插入tb_order
表)。接着备份
tb_order
表。执行插入订单日志操作。
最后备份
tb_orderlog
表。此时备份的数据存在一致性问题:
tb_stock
表与tb_order
表的数据不匹配(tb_order
表中有最新的订单信息,但tb_stock
表的库存尚未扣减)。为了避免这种问题,可以借助 MySQL 的 全局锁 来解决。通过在备份前对整个数据库加全局锁,确保在备份期间所有表的数据状态保持一致,从而避免因并发操作导致的数据不一致问题。
B. 再来分析一下加了全局锁后的情况

对数据库进行进行逻辑备份之前,先对整个数据库加上全局锁,一旦加了全局锁之后,其他的DDL、 DML全部都处于阻塞状态,但是可以执行DQL语句,也就是处于只读状态,而数据备份就是查询操作。 那么数据在进行逻辑备份的过程中,数据库中的数据就是不会发生变化的,这样就保证了数据的一致性 和完整性。
3.2.2 语法
1). 加全局锁
sql
#全局锁(只读锁)
flush tables with read lock ;
2). 数据备份
sql
mysqldump -uroot --p1234 itcast > itcast.sql
3). 释放锁
sql
unlock tables ;
4). 演示:

3.2.3 特点
数据库中加全局锁,是一个比较重的操作,存在以下问题: 如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。 如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟。
在InnoDB引擎中,我们可以在备份时加上参数 --single-transaction 参数来完成不加锁的一致 性数据备份。
sql
mysqldump --single-transaction -uroot --p123456 itcast > itcast.sql
3.3 表级锁
3.3.1 介绍
表级锁,每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在MyISAM、 InnoDB、BDB等存储引擎中。 对于表级锁,主要分为以下三类:
- 表锁
- 元数据锁
- 意向锁
3.3.2 表锁
对于表锁,分为两类: 表共享读锁(read lock) 表独占写锁(write lock)
语法:
加锁:lock tables 表名... read/write。
释放锁:unlock tables / 客户端断开连接 。
A. 表共享-读锁(read lock)

如果一个事务对表加了共享读锁,其他事务(包括当前事务自己)对该表的写操作(如
INSERT
、UPDATE
、DELETE
)会被阻塞,直到共享读锁被释放。自己的写操作也会被锁住
- 在大多数数据库系统中,表共享读锁会阻止当前事务自己对该表的写操作。如果当前事务已经对表加了共享读锁,它自己也无法对该表进行写操作,除非先释放共享读锁。
B. 表独占-写锁(write lock)

阻塞其他操作
阻止读操作 :其他事务对该表的读操作(如
SELECT
)会被阻塞,直到独占写锁被释放。阻止写操作 :其他事务对该表的写操作(如
INSERT
、UPDATE
、DELETE
)也会被阻塞,直到独占写锁被释放。
3.3.3 元数据锁
meta data lock , 元数据锁,简写MDL。 MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。MDL锁主要作用是维 护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。
++核心作用:为了避免DML与 DDL冲突,保证读写的正确性。++
这里的元数据,大家可以简单理解为就是一张表的表结构。 也就是说,某一张表涉及到未提交的事务 时,是不能够修改这张表的表结构的。
在MySQL5.5中引入了MDL,当对一张表进行增删改查的时候,加MDL读锁(共享);当对表结构进行变 更操作的时候,加MDL写锁(排他)。 常见的SQL操作时,所添加的元数据锁:

SHARE_READ和SHARE_WRITE兼容,两者与EXLCLUSIVE互斥

我们可以通过下面的SQL,来查看数据库中的元数据锁的情况:
sql
select object_type,object_schema,object_name,lock_type,lock_duration from
performance_schema.metadata_locks ;
3.3.4 意向锁
1). 介绍
为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。
有了意向锁之后前:
客户端一,开启一个事务,然后执行DML操作,在执行DML语句时,会对涉及到的行加行锁。
当客户端二,想对这张表加表锁时,会检查当前表是否有对应的行锁,如果没有,则添加表锁,此时就 会从第一行数据,检查到最后一行数据,效率较低。

有了意向锁之后 :
客户端一,在执行DML操作时,会对涉及的行加行锁,同时也会对该表加上意向锁。

而其他客户端,在对这张表加表锁的时候,会根据该表上所加的意向锁来判定是否可以成功加表锁,而不用逐行判断行锁情况了。

2). 分类
- 意向共享锁(IS): 由语句select ... lock in share mode添加 。
- 与表锁共享锁 (read)兼容,与表锁排他锁(write)互斥。
- 意向排他锁(IX): 由insert、update、delete、select...for update添加 。
- 与表锁共 享锁(read)及排他锁(write)都互斥,意向锁之间不会互斥。
一旦事务提交了,意向共享锁、意向排他锁,都会自动释放。
可以通过以下SQL,查看意向锁及行锁的加锁情况:
sql
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from
performance_schema.data_locks;
3.4 行级锁
3.4.1 介绍
行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在 InnoDB存储引擎中。
InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的 锁。对于行级锁,主要分为以下三类:
- 行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此行进行update和delete。在 RC、RR隔离级别下都支持。

- 间隙锁(Gap Lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事 务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持。

- 临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。 在RR隔离级别下支持。

3.4.2 行锁
1). 介绍
InnoDB实现了以下两种类型的行锁:
- 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。
- 排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。
两种行锁的兼容情况如下:

常见的SQL语句,在执行时,所加的行锁如下:

2). 演示
默认情况下,InnoDB在 REPEATABLE READ事务隔离级别运行,InnoDB使用 next-key 锁进行搜 索和索引扫描,以防止幻读。
- 针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
- InnoDB的行锁是针对于索引加的锁,不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,此时就会升级为表锁。
可以通过以下SQL,查看意向锁及行锁的加锁情况:
sql
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from
performance_schema.data_locks;
无索引行锁升级为表锁

3.4.3 间隙锁&临键锁
默认情况下,InnoDB在 REPEATABLE READ事务隔离级别运行,InnoDB使用 next-key 锁进行搜 索和索引扫描,以防止幻读。
- 索引上的等值查询(唯一索引),给不存在的记录加锁时, 优化为间隙锁 。
- 索引上的等值查询(非唯一普通索引),向右遍历时最后一个值不满足查询需求时,next-key lock 退化为间隙锁。
- 索引上的范围查询(唯一索引)--会访问到不满足条件的第一个值为止。
索引上的等值查询(唯一索引),给不存在的记录加锁时, 优化为间隙锁

注意:间隙锁唯一目的是防止其他事务插入间隙。间隙锁可以共存,一个事务采用的间隙锁不会 阻止另一个事务在同一间隙上采用间隙锁。
4. InnoDB引擎
4.1 逻辑存储结构
InnoDB的逻辑存储结构如下图所示:


Tablespace(表空间):
表空间是InnoDB中最大的存储单位,用于存储数据库对象(如表、索引等)。
表空间可以分为系统表空间、文件表空间、通用表空间和临时表空间等。
系统表空间包含InnoDB的系统数据,如数据字典和撤销日志。
图中显示了表空间包含多个段(Segment)。
Segment(段):
段是表空间中的一个逻辑存储单元,用于存储特定类型的数据。
每个表在InnoDB中通常有三个段:数据段、索引段和回滚段。
数据段用于存储表的数据,索引段用于存储索引,回滚段用于存储回滚信息。
图中显示了表空间包含多个段,每个段包含多个区(Extent)。
Extent(区):
区是段中的一个存储单元,由多个连续的页(Page)组成。
InnoDB中的区大小通常是1MB(由64个16KB的页组成)。
区是InnoDB分配和管理空间的基本单位。
图中显示了每个段包含多个区,每个区包含多个页。
Page(页):
页是InnoDB中最小的存储单元,通常大小为16KB。
页是InnoDB进行I/O操作的基本单位。
不同类型的页用于存储不同类型的数据,如数据页、索引页、undo页等。
图中显示了每个区包含多个页,每个页用于存储行数据。
Row(行):
行是数据库表中的一行数据,包含多个列(Column)。
每个行数据存储在页中,页中可以存储多行数据。
图中显示了页中包含多行数据,每行数据包含事务ID(Trx id)、回滚指针(Roll pointer)和列数据(col1, col2, col3等)。
4.2 架构
4.2.1 概述
MySQL5.5 版本开始,默认使用InnoDB存储引擎,它擅长事务处理,具有崩溃恢复特性,在日常开发 中使用非常广泛。下面是InnoDB架构图,左侧为内存结构,右侧为磁盘结构。

4.2.2 内存结构
1.Buffer Pool:

2.ChangBuffer:

3.Adaptive Hash Index:

4.Log Buffer:

4.2.3 磁盘结构
1.System Tablespace & File-Per-Table Tablespaces

2.General Tablespaces & Undo Tabelspaces & Temporary Tablespaces

3.Doublewrite Buffer Files & Redo Log

4.2.4 后台线程

4.3 事务原理
4.3.1 事务基础
1). 事务
事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系 统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
2). 特性
- 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
- 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
- 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
- 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
那实际上,我们研究事务原理,就是研究MySQL的InnoDB引擎是如何保证事务的这四大特性的。

4.3.2 redo log

有了redolog之后,当对缓冲区的数据进行增删改之后,会首先将操作的数据页的变化,记录在redo log buffer中。在事务提交时,会将redo log buffer中的数据刷新到redo log磁盘文件中。 过一段时间之后,如果刷新缓冲区的脏页到磁盘时,发生错误 ,此时就可以借助于redo log进行数据恢复 ,这样就保证了事务的持久性。 而如果脏页成功刷新到磁盘 或者涉及到的数据已经落盘 ,此 时redolog就没有作用了,就可以删除了,所以存在的两个redolog文件是循环写的。
那为什么每一次提交事务,要刷新redo log 到磁盘中呢,而不是直接将buffer pool中的脏页刷新到磁盘呢 ?
因为在业务操作中,我们操作数据一般都是随机读写磁盘 的,而不是顺序读写磁盘。 而redo log在 往磁盘文件中写入数据,由于是日志文件,所以都是顺序写的(追加写入)。顺序写的效率,要远大于随机写。 这 种先写日志的方式,称之为 WAL(Write-Ahead Logging)。
4.3.3 undo log

4.4 MVCC
4.4.1 基本概念
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
全称 Multi-Version Concurrency Control,多版本并发控制 。指维护一个数据的多个版本, 使得读写操作没有冲突,快照读为MySQL实现MVCC提供了一个非阻塞读功能。MVCC的具体实现,还需 要依赖于数据库记录中的三个隐式字段 、undo log日志 、readView。 接下来,我们再来介绍一下InnoDB引擎的表中涉及到的隐藏字段 、undolog 以及 readview,从 而来介绍一下MVCC的原理。
4.4.2 隐藏字段
4.4.2.1 介绍

当我们创建了上面的这张表,我们在查看表结构的时候,就可以显式的看到这三个字段。 实际上除了这三个字段以外,InnoDB还会自动的给我们添加三个隐藏字段及其含义分别是:

4.4.3 undolog
4.4.3.1 介绍
回滚日志,在insert、update、delete的时候产生的便于数据回滚的日志。 当insert的时候,产生的undo log日志只在回滚时需要,在事务提交后,可被立即删除。 而update、delete的时候,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会立即被删除。
4.4.3.2 版本链
有一张表原始数据为:

隐藏字段派上用场了
DB_TRX_ID : 代表最近修改事务ID,记录插入记录或最后一次修改该记录的事务ID,自增。
DB_ROLL_PTR : 由于这条数据是才插入的,没有被更新过,所以该字段值为null。

最终我们发现,不同事务或相同事务对同一条记录进行修改,会导致该记录的undolog生成一条 记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。
4.4.4 readview
定义

版本链数据的访问规则

4.4.5 原理分析
4.4.5.1 RC隔离级别
RC隔离级别下,在事务中每一次执行快照读时生成ReadView。

4.4.5.2 RR隔离级别
RR隔离级别下,仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。 而RR 是可重复读,在一个事务中,执行两次相同的select语句,查询到的结果是一样的。

所以呢,MVCC的实现原理就是通过 InnoDB表的隐藏字段、UndoLog版本链、ReadView来实现的。 而MVCC + 锁,则实现了事务的隔离性。 而一致性则是由redolog 与 undolog保证。

5. MySQL管理
5.1 系统数据库
Mysql数据库安装完成后,自带了一下四个数据库,具体作用如下:

5.2 常用工具
5.2.1 mysql
该mysql不是指mysql服务,而是指mysql的客户端工具。
sql
语法 :
mysql [options] [database]
选项 :
-u, --user=name #指定用户名
-p, --password[=name] #指定密码
-h, --host=name #指定服务器IP或域名
-P, --port=port #指定连接端口
-e, --execute=name #执行SQL语句并退出
-e选项可以在Mysql客户端执行SQL语句,而不用连接到MySQL数据库再执行,对于一些批处理脚本, 这种方式尤其方便。
sql
mysql -uroot --p123456 db01 -e "select * from stu";

5.2.2 mysqladmin
mysqladmin 是一个执行管理操作的客户端程序。可以用它来检查服务器的配置和当前状态、创建并删除数据库等。
sql
语法:
mysqladmin [options] command ...
选项:
-u --user=name #指定用户名
-p --password[=name] #指定密码
-h --host=name #指定服务器IP或域名
-P --port=port #指定连接端口
示例
sql
mysqladmin -uroot --p1234 drop 'test01';
mysqladmin -uroot --p1234 version;
5.2.3 mysqlbinlog
由于服务器生成的二进制日志文件以二进制格式保存,所以如果想要检查这些文本的文本格式,就会使 用到mysqlbinlog 日志管理工具。

5.2.4 mysqlshow
mysqlshow 客户端对象查找工具,用来很快地查找存在哪些数据库、数据库中的表、表中的列或者索引。
sql
语法 :
mysqlshow [options] [db_name [table_name [col_name]]]
选项 :
--count 显示数据库及表的统计信息(数据库,表 均可以不指定)
-i 显示指定数据库或者指定表的状态信息
示例:
#查询test库中每个表中的字段书,及行数
mysqlshow -uroot -p2143 test --count
#查询test库中book表的详细情况
mysqlshow -uroot -p2143 test book --count
5.2.5 mysqldump
mysqldump 客户端工具用来备份数据库或在不同数据库之间进行数据迁移。备份内容包含创建表,及 插入表的SQL语句。
sql
语法 :
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文件,数据文件
5.2.6 mysqlimport/source
1). mysqlimport
mysqlimport 是客户端数据导入工具,用来导入mysqldump 加 -T 参数后导出的文本文件。
sql
语法 :
mysqlimport [options] db_name textfile1 [textfile2...]
示例 :
mysqlimport -uroot -p2143 test /tmp/city.txt
2). source
如果需要导入sql文件,可以使用mysql中的source 指令 :
sql
source /root/xxxxx.sql
大功告成!