连接层
最上层是一些客户端和连接服务,主要完成一些类似于连接处理、授权认证、及相关的安全方案。服务器也会为安全连接的每个客户端验证它所具有的操作权限。
服务层
第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化,部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。
引擎层
存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。
存储层
主要是将数据存储在文件系统上,并完成与存储引擎的交互。
存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。存储引擎是基于表的,而不是基于库的,所有存储引擎也可被称为表类型。
MySQL 5.5之后默认的存储引擎是InnoDB。
-- 在创建表时,指定存储引擎
CREATE TABLE 表名(
字段1 字段1类型 [COMMENT 字段1注释],
...
字段n 字段n类型 [COMMENT 字段n注释]
)ENGINE = INNODB [COMMENT 表注释];
-- 查看当前数据库支持的存储引擎类型
SHOW ENGINES;
介绍
InnoDB是一种兼顾高可靠性和高性能的通用存储引擎,在MySQL 5.5 之后,InnoDB是默认的MySQL存储引擎。
特点
DML操作遵循ACID模型,支持事务;
行级锁,提高并发访问性能;
支持外键FOREIGN KEY约束,保证数据的完成性和正确性。
文件
SHOW VARIABLES LIKE 'innodb_file_per_table';
-- 在命令行使用命令:ibd2sdi account.ibd可以打开.ibd文件
介绍
MySQL早期的默认存储引擎
特点
文件
介绍
Memory引擎的表数据是存储在内存中的,由于受到硬件问题、或断点问题的影响,只能将这些表作为临时表或缓存作用。
特点
文件
在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用程序,还可以根据实际情况选择多种存储引擎进行组合。
索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算,这种数据结构就是索引。
MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的结构,主要包含:
我们平时所说的索引,如果没有特别说明,都是指B+树结构组织的索引。
二叉树的缺点:顺序插入时,会形成一个链表,查询性能大大降低,大数据量的情况下,层级较深,检索速度慢。
红黑树:大数据量的情况下,层级较深,检索速度慢。
相对于B-Tree的区别:
MySQL索引数据结构对经典的B+Tree进行了优化。在原来的基础上,增加了一个指向相邻叶子结点的链表指针,就形成了带有顺序指针的B+Tree,提高区间访问的可能性。
哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中。
如果两个或多个键值,映射到一个相同的槽位上,他们就产生了哈希冲突,可以通过链表来解决。
相对于二叉树,层级更少,搜索效率更高
对于B-Tree,无论是叶子结点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低。
相对于Hash索引,B+Tree支持范围匹配和排序操作。
在InnoDB存储引擎中,根据索引的存储形式,又可分为以下两种:
回表查询的过程:
思考:
select * from user where id = 10;
select * from user where name = 'zhangsan';
答案是第一条,因为第一条是按照主键(聚集索引)查询,不需要回表查询。
假设:一行数据大小为1k,一页可以存储16行这样的数据。InnoDB的指针占用6个字节的空间,key占用空间大小取决于主键的数据类型,即主键为int,占用4个字节;主键为bigint,占用字节数为8.
高度为2:n*8+(n+1)*6=16*1024,算出n约为1170,1170*16=18736(其中n表示当前节点存储的key的数量,则n+1表示指针的数量)
高度为3:1171*1171*16=21939856
-- UNIQUE表示唯一索引,FULLTEXT表示群文索引,如果不加这两个字段表示创建的是一个常规索引
CREATE [UNIQUE | FULLTEXT] INDEX index_name ON table_name(index_col_name,...);
SHOW INDEX FROM table_name;
DROP INDEX index_name ON table_anem;
MySQL客户端连接成功后,通过show [session|global] status命令可以提供服务器状态信息。通过如下指令,可以查看当前数据库的INSERT、UPDATE、DELETE、SELECT的访问频次:
SHOW GLOBAL STATUS LIKE 'Com ______';
慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志。
MySQL的慢查询日志默认没有开启,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:
-- 开启MySQL慢日志查询开关
slow_query_log=1
-- 设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2
配置完毕后,通过以下指令重新启动MySQL服务器进行测试,查看慢日志文件中记录的信息:
/var/lib/mysql/localhost-slow.log
SELECT @@have_profiling;
默认profiling是关闭的,可以通过set语句在session/global级别开启profiling:
SET profiling = 1;
执行一系列的业务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或者DESC命令获取MySQL如何执行SELECT语句的信息,包括语句执行过程中表如何连接和连接的顺序。
语法:
-- 直接在select语句之前加上关键字explain/desc
EXPLAIN SELECT 字段列表 FROM 表名 WHERE 条件;
explain执行计划各字段含义:
id
select_type
type
possible_key
Key
key_len
rows
filtered
单列索引:一个索引只包含单个列。
联合索引:一个索引包含了多个列。
在业务场景中,如果存在多个查询条件,考虑针对查询字段建立索引时,建议建立联合索引,而非单列索引。
多条件联合查询时,MySQL优化器会评估哪个字段的索引效率更高,会自动选择该索引完成本次查询。
联合索引数据结构
注意:创建联合索引时,要考虑字段的顺序,因为要遵循最左前缀法则。
联合索引中,出现范围查询(>,<),范围查询右侧的列索引失效
注意:使用 >= 就不会导致索引失效
-- user index:告诉数据库你用哪个索引
explain select * from tb_user use index(idx_user_pro) where pro="数学";
-- ignore index:告诉数据库你不要用哪个索引
explain select * from tb_user ignore index(idx_user_pro) where pro="数学";
-- force index:告诉数据库必须用哪个索引
explain select * from tb_user force index(idx_user_pro) where pro="数学";
在使用索引过程中,我们尽量使用覆盖索引(查询使用了索引,并且需要返回的列在该索引中已经全部能够找到),减少使用select *。
一张表,有四个字段(id,username,password,status),由于数据量过大,需要对以下SQL语句进行优化,该如何进行才是最优方案:
select id,username,password from tb_user where username='itcast';
方案:针对username和password两个字段建立联合索引。
当字段类型为字符串(varchar,text等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时浪费大量的磁盘IO,影响查询效率。此时可以只将字符串的一部分前缀建立索引,这样可以大大节约索引空间,从而提高索引效率。
语法
create index idx_xxx on table_name(column_name(n)); -- n表示提取该字符串前面的几个字符构建索引
前缀长度
-- 计算选择性,截取email的前5个字符构建前缀索引
select count(distinct substring(email,1,5))/count(*) from tb_user;
-- 创建索引
create index idx_email_5 on tb_user(email(5));
批量插入
insert into table_name values(1,'tom'),(2,'jerry');
注意:一次插入的数据也不建议超过1000条。
手动事务提交
start transaction;
insert into table_name values(1,'tom'),(2,'jerry');
insert into table_name values(3,'tom'),(4,'jerry');
insert into table_name values(5,'tom'),(6,'jerry');
commit;
MySQL默认是自动提交事务,这样就会导致频繁开启和提交事务,降低性能
主键顺序插入
主键顺序插入的性能要高于乱序插入
-- 客户端连接服务器时,加上参数 --local-infile
mysql--local-infile -u root -p
-- 设置全局参数local_infile为1,开启从本地加载文件导入数据的开关
set global local_infile = 1;
-- 执行load指令将准备好的数据加载到表结构中
load data local infile '/root/sql.sql' into table tb_user fields terminated by ',' lines terminated by '\n';
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mH3kN6Hs-1647939072339)(https://s2.loli.net/2022/03/19/kg5wOHn2rRUzq9X.png)]
主键乱序插入的情况下,为保证叶子结点有序,在插入一个新的数据时,如果要插入的页面已经放不下了,则会在该页面的中间位置(不一定是非常绝对的中间)将其后面的节点分裂出来;
把分出来的节点放到新创建的页中,再将要插入的数据按顺序放在后面;
页面之间重新调节指针,从而使各节点有序存放。这整个现象就叫做页分裂。
满足业务需求的情况下,尽量降低主键的长度。
二级索引的叶子结点中存储着主键,如果主键长度过长,会导致二级索引占用大量磁盘空间,在搜索时也会耗费大量的磁盘IO。
插入数据时,尽量选择顺序插入,选择使用AUTO_INCREMENT自增主键。、
乱序插入会导致页分裂现象的发生。
尽量不使用UUID做主键或者是其他自然主键,如身份证号。
每次生成的UUID是无序的,在插入时会导致乱序插入,发生页分裂现象;身份证号太长,造成空间浪费
业务操作时,避免对主键的修改
修改主键的话就要把二级索引叶子结点内存储的主键也一并修改,代价比较大
-- 创建age和phone联合索引
create index idx_user_age_phone on tb_user(age,phone);
-- 正常,using index
select id, age, phone from tb_user order by age, phone;
-- 反向扫描索引,backward index scan,using index
select id, age, phone from tb_user order by age desc, phone desc;
-- using index,using filesort
-- 违背最左前缀法则?
select id, age, phone from tb_user order by phone, age;
-- using index,using filesort
-- 创建索引时默认是按照升序排序,这里要单独按照phone进行倒序排序就要进行额外的排序
select id, age, phone from tb_user order by age asc, phone desc;
-- 创建索引时指定字段的排序方式
create index idx_user_age_phone_ad on tb_user(age asc,phone desc);
-- 正常,using index
select id, age, phone from tb_user order by age asc, phone desc;
一个常见又非常头疼的问题就是limit 2000000,10,此时需要MySQL排序前2000010记录,仅仅返回2000000 -> 2000010的记录,其他记录丢弃,查询排序的代价非常大。
优化思路:一般分页查询时,通过创建覆盖索引能够比较好地提高性能,可以通过覆盖索引加子查询形式进行优化。
selext s.* from tb_sku s,(select id from tb_sku order by id limit 2000000, 10) a where s.id = a.id;
MyISAM引擎把一个表的总行数存放在了磁盘上,因此执行count(*)的时候会直接返回这个数,效率很高。
InnoDB引擎就比较麻烦,他在执行count(*)的时候,需要把数据一行一行从引擎里读出来,然后累计计数。
**优化思路:**自己计数
count的几种用法
count()是一个聚合函数,对于返回的结果集一行行地判断,如果count函数的参数不是NULL,累计值就加1,否则不加,最后返回累计值。
用法:count(*)、count(主键)、count(字段)、count(1)
count(主键)
InnoDB引擎会遍历整张表,把每一行的主键id都取出来,返回给服务层,服务层拿到主键后,直接按行进行累加(主键不可能为null)
count(字段)
没有not null约束:InnoDB遍历整张表,把每行字段值都取出来返回给服务层,判断是否为null,不为null则计数累加。
有not null约束:InnoDB遍历整张表,把每行字段值都取出来,返回给服务层直接按行累加。
count(1)
InnoDB引擎遍历整张表,但不取值。服务层对于返回的每一行,放一个数字”1“进去,直接按行进行累加。
count(*)
InnoDB引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加。
按照效率排序:count(字段) < count(主键) < count(1) ≈ count(*),所以尽量使用count(*)
视图(View)是一种虚拟存在的表,视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。
通俗来讲,视图只保存了查询的SQL逻辑,不保存查询结果,所以我们在创建视图时,主要的工作就落在创建这条SQL查询语句上。
CREATE [OR REPLACE] VIEW view_name[(column_name)] AS SELECT语句 [WITH [CASCADED | LOCAL] CHECK OPTION];
-- 查看创建视图语句
SHOW CREATE VIEW view_name;
-- 查看视图数据
SELECT * FROM view_name;
-- 方法一
CREATE [OR REPLACE] VIEW view_name AS SELECT语句 [WITH [CASCADED | LOCAL] CHECK OPTION];
-- 方法二
ALTER VIEW view_name[(column_name)] AS SELECT语句 [WITH [CASCADED | LOCAL] CHECK OPTION];
DROP VIEW [IF EXISTS] view_name,...;
当使用WITH CASCADED CHECK OPTION子句创建视图时,MySQL会通过视图检查正在更改的每个行,例如插入、更新、删除,以使其符合视图的定义。MySQL允许基于另一个视图创建视图,他还会检查当前视图所依赖的视图中的规则,以保持一致性。为了确定检查的范围,MySQL提供了两个选项:CASCADED和LOCAL,默认值为CASCADED。
CASCADED
LOCAL
当我们在操作视图时,会递归地去找当前视图所依赖的视图,会根据所依赖的视图是否定义了WITH [CASCADED | LOCAL] CHECK OPTION这样的检查选项来决定个是否对操作做出检查。
要使视图可更新,视图中的行与基础表中的行之间必须存在一对一的关系。如果视图包含以下任何一项,则该视图不可更新:
简单
视图不仅可以简化用户对数据的理解,也可以简化他们的操作。那些被经常使用的查询可以被定义为视图,从而使得用户不必为以后的操作每次指定全部的条件。
安全
数据库可以授权,但不能授权到数据库特定和特定的列上。通过视图用户只能查询和修改他们所能见到的数据。
数据独立
视图可帮助用户屏蔽真实表结构变化带来的影响。
存储过程是事先经过编译并存储在数据库中的一段SQL语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。
存储过程思想上很简单,就是数据库SQL语言层面的代码封装和重用(有点像Java的方法?)。
创建
CREATE PROCEDURE 存储过程名称([参数列表])
BEGIN
-- SQL语句
END;
调用
CALL 名称([参数]);
查看
-- 查询指定数据库的存储过程及状态信息
SELECT * FROM INFOMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'xxx';
-- 查询某个存储过程的定义
SHOW CREATE PROCEDURE 存储过程名称;
删除
DROP PROCEDURE [IF EXISTS] 存储过程名称;
注意:在命令行中执行创建存储过程的SQL时,需要通过关键字delimiter指定SQL语句的结束符。
系统变量是MySQL服务器提供,不是用户定义的,属于服务器层面,分为全局变量(GLOBAL)、会话变量(SESSION)。
查看系统变量
-- 查看所有系统变量
SHOW [SESSION | GLOBAL] VARIABLES;
-- 可以通过LIKE模糊匹配方式查找变量
SHOW [SESSION | GLOBAL] VARIABLES LIKE '...';
-- 查看指定变量的值
SELECT @@[SESSION | GLOBAL] 系统变量名;
设置系统变量
SET [SESSION | GLOBAL] 系统变量名 = 值;
SET @@[SESSION | GLOBAL] 系统变量名 = 值;
注意:
用户自定义变量是用户根据需求自己定义的变量,用户变量不需要提前声明,在用的时候直接使用“@变量名”使用就可以,只不过获取不到的值为NULL。其作用域为当前连接。
赋值
-- @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 表名;
使用
SELECT @var_name;
局部变量是根据需要定义的在局部生效的变量,访问之前需要DECLARE声明。可用作存储过程内的局部变量和输入参数,局部变量的范围是在其在声明的BEGIN…END块。
声明
DECLARE 变量名 变量类型[DEFAULT...];
变量类型就是数据库字段类型:INT、BEGIN、CHAR、VARCHAR、DATE、TIME等。
赋值
SET 变量名 = 值;
SET 变量名 := 值;
SELECT 字段名 INTO 变量名 FROM 表名...;
-- 例如
create procedure p2()
begin
declare stu_count int default 0;
select count(*) into stu_count from student;
select stu_count;
end;
语法
IF 条件1 THEN
...
ELSEIF 条件2 THEN -- 可选
...
ELSE -- 可选
...
END IF;
-- 例
create procedure p3()
begin
declare score int default 58;
declare result varchar(10);
if score >= 85 then
set result := '优秀';
elseif score >= 60 then
set result := '及格';
else
set result := '不及格';
end if;
select result;
end;
call p3();
用法
CREATE PROCEDURE 存储过程名称([IN/OUT/INOUT 参数名 参数类型])
BEGIN
-- SQL语句
END;
-- 根据传入的参数score,判定当前分数对应的分数等级,并返回。
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;
call p3(69, @result); -- 使用功能自定义变量@result接收p4的结果
select @result;
-- 将传入的200分制的分数进行换算,返回百分制的分数
create procedure p5(inout score double)
begin
set score := score * 0.5;
end;
set @score := 178
call p5(@score);
select @score;
语法一
CASE case_value
WHEN when_value1 THEN statement_list1
[WHEN when_value2 THEN statement_list2]...
[ELSE statement_list]
END CASE;
语法二
CASE
WHEN search_condition1 THEN statement_list1
[WHEN search_condition2 THEN statement_list2]...
[ELSE statement_list]
END CASE;
-- 案例
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(7);
while语句是有条件的循环控制语句,满足条件后,再执行循环体中的SQL语句。具体语法为:
-- 先判定条件,如果为true,执行循环,否则不执行
WHILE 条件 DO
-- SQL语句
END WHILE;
-- 案例
create procedure p7(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 p7(100);
repeat是有条件的控制循环,当满足条件时退出循环,具体语法为:
-- 先执行一次逻辑,然后判断逻辑是否满足,如果满足则退出,不满足则进行下一次循环
REPEAT
-- SQL语句
UNTIL 条件
END REPEAT;
-- 案例
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);
loop实现简单的循环,如果不在SQL逻辑中增加退出循环的条件,可以用来实现简单的死循环。loop可以配合以下两个语句使用:
[begin_label:] LOOP
-- SQL逻辑
END LOOP [end_label];
LEAVE label; -- 退出指定标记的循环体
ITERATE label; -- 直接进入下一次循环
-- 计算从1累加到n的值,n为传入的参数
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(10);
-- 计算从1到n的偶数之和,n为传入的参数
create procedure p10(in n int)
begin
declare total int default 0;
sum:loop
if n <= 0 then
leave sum; -- 跳出循环
end if;
if n%2 = 1 then
n := n - 1;
iterate sum; -- 跳出本次循环,开始下一次
end if;
set total := total + n;
set n := n - 1;
end loop sum;
select total;
end;
call p10(10);
游标(CURSOR)是用来存储查询结果集的数据类型,在存储过程和函数中可以使用游标对结果集进行循环的处理。游标的使用包括游标的声明、OPEN、FETCH和CLOSE,其语法如下:
-- 声明游标
DECLARE 游标名称 CURSOR FOR 查询语句;
-- 打开游标
OPEN 游标名称;
-- 获取游标记录
FETCH 游标名称 INTO 变量[,变量...];
-- 关闭游标
CLOSE 游标名称;
条件处理程序
条件处理程序(Handler)可以用来定义在流程控制结构执行过程中的遇到问题时相应的处理步骤。具体语法为:
DECLARE handler_action HANDLER FOR condition_value[,condition_value]... SQL语句;
/*
handler_action取值:
CONTINUE:继续执行当前程序
EXIT:终止执行当前程序
condition_value取值:
SQLSTATE sqlstate_value:状态码,如02000
SQLWARNING:所有01开头的SQLSTATE代码的简写
NOT FOUND:所有02开头的SQLSTATE代码的简写
SQLEXCEPTION:所有没有被SQLWARNING 和 NOT FOUND捕获的SQLSTATE代码的简写
*/
-- 案例:根据传入的参数uage来查询用户表tb_user中所有的用户年龄小于等于uage的用户姓名name和专业profession,并将用户的姓名和专业插入到所创建的一张新表(id,name,profession)中。
create procedure p11(in uage int)
begin
-- 声明变量要在声明游标之前
declare uname varchar(10);
declare upro varchar(20);
-- 声明游标
declare u_cursor cursor for select name, profession from tb_user where age <= uage;
-- 声明条件处理程序handler
-- declare exit handler for SQLSTATE '02000' close u_cursor;
declare exit handler for not found close u_cursor;
-- 创建新表tb_user_pro
drop table if exists tb_user_pro;
create table if not exists tb_user_pro(
id int primary key auto_increment,
name varchar(10),
profession varchar(20)
);
-- 打开游标
open u_cursor;
-- 循环遍历游标
while true do
fetch u_cursor into uname,upro;
insert into tb_user_pro values(null,uname,upro);
end while;
-- 关闭游标
close cursor;
end;
call p11(40);
存储函数就是有返回值的存储过程,存储过程的参数只能是IN类型的,具体语法如下:
CREATE FUNCTION 存储函数名称([参数列表])
RETURN type[characteristic...]
BEGIN
-- SQL语句
RETURN ...;
END;
/*
characteristic取值说明:
- DETERMINISTIC:相同的输入参数总是产生相同的结果
- NO SQL:不包含SQL语句
- READS SQL DATA:包含读取数据的语句,但不包含写入数据的语句
*/
-- 案例:完成从1到n的累加
create function fun1(n int)
return 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(100);
注意:存储函数必须有返回值。
触发器是与表有关的数据库对象,指在insert、update、delete等操作之前或之后触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性、日志记录、数据校验等操作。
使用别名 OLD 和 NEW 来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。现在触发器还支持行级触发,不支持句级触发。
创建
CREATE TRIGGER trigger_name
BEFORE/AFTER INSERT/UPDATE/DELETE
ON tb_name FOR EACH ROW -- 行级触发器
BEGIN
trigger_stmt;
END;
查看
SHOW TRIGGERS;
-- 查看当前数据库的所有触发器
删除
DROP TRIGGER [schema_name] trigger_name;
-- 如果没有指定schema_name,默认为当前数据库
-- 案例:通过触发器记录tb_user表的数据变更日志,将变更日志插入到日志表user_logs中,包含在增加、修改、删除;
-- 创建user_logs表结构
create table user_logs(
in int(11) not null auto_increment,
operation varchar(20) not null comment '操作类型',
operate_time datetime not null comment '操作时间',
operate_id int(11) not null comment '操纵ID',
operate_params varchar(500) comment '操作参数',
primary key('id')
);
-- 插入数据触发器
create trigger tb_user_insert_trigger after insert on tb_user 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,',phone=',new.phone,',email=',new.email,',profession=',new.profession));
end;
-- 查看触发器
show triggers;
-- 往tb_user表中插入数据
insert into tb_user(id, name, phone, email, profession, age, gender, status, createtime) values(25,'张三','111','[email protected]','软件工程',23,'1','1',now());
-- 修改数据触发器
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;
update tb_user set age = 20 where id = 23;
-- 删除数据触发器
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,'update',now(),new.id,concat('删除的数据:id=',old.id,',name=',old.name,',phone=',old.phone,',email=',old.email,',profession=',old.profession);
end;
delete from tb_user where id = 10;
介绍
锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除了传统的计算资源(CPU、RAM、I/O)的争用之外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
分类
MySQL中的锁,按照锁的粒度可分为三类:
介绍
全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,以及更新操作的事务提交语句都将被阻塞。
其典型的使用场景:做全库的逻辑备份。对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。
演示
特点
数据库中加全局锁是一个比较重的操作,存在以下问题:
在InnoDB引擎中,我们可以在备份时加上参数**–single-transaction参数来完成不加锁**的一致性数据备份。
mysqldump --single-transaction -uroot -p123456 itcast>itcast.sql
介绍
表级锁,每次操作锁住整张表。锁的粒度大,发生锁冲突的概率最高,并发度最低。应用在MyISAM、InnoDB、BDB等引擎中。
分类
语法:
-- 加锁
lock tables 表1,... read/write;
-- 释放锁
unlock tables; -- 或者让客户端断开连接
读锁:自己和别人都能读,但是都不能写;
写锁:自己既能读也能写,别人不能读也不能写。
MDL加锁过程是系统自动控制的,无需显式使用,在访问一张表的时候就会自动加上。MDL锁主要作用是维护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。
简单来说,就是在一张表上存在未提交的事务时,我们不能对表结构做出修改。
作用:为了避免DML和DDL冲突,保证读写的正确性。
在MySQL 5.5 中引入了MDL,当对一张表进行增删改的时候,加MDL读锁(共享);当对表结构进行变更操作的时候,加MDL写锁(排他)。
查看元数据锁:
select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks;
为了避免DML在执行时加的行锁和表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。
可以通过以下SQL查看意向锁及行锁的加锁情况:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
介绍
行级锁,每次操作锁住对应的行数据。锁的粒度最小,发生锁冲突的概率最低,并发度最高。应用在InnoDB引擎中。
InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。对于行级锁主要分为以下三类:
InnoDB实现了以下两种类型的行锁:
默认情况下,InnoDB在REPEATABLE READ事务隔离级别运行,InnoDB使用next-key锁进行搜索和索引扫描,以防止幻读。
默认情况下,InnoDB在REPEATABLE READ(RR)事务隔离级别运行,InnoDB使用next-key锁进行搜索和索引扫描,以防止幻读。
注意:间隙锁唯一目的是防止其他事务插入间隙,间隙锁可以共存,即一个事务采用的间隙锁不会阻止另一个事务在同一个间隙上采用间隙锁。