一、触发器
触发器是与表有关的数据库对象,指在insert/update/delete
之前或之后,触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性,日志记录,数据校验等操作。
使用别名OLD或者NEW来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。现在触发器还支持行级触发(触发几次就执行几次),不支持语句级触发(针对这一个语句只触发一次)。
1. 语法
创建触发器
create trigger 触发器名称
before/after insert/update/delete
on 表名 for each row --行级触发器
begin
trigger_stmt;
end;
查看触发器
show triggers;
删除触发器
drop trigger 表名.触发器名; --如果不指定表名,默认为当前数据库
2. 案例
2.1 insert类型
需求:通过触发器记录user表的数据变更日志(user_logs表),包含增加,修改,删除
准备工作:创建好日志表(user_logs)
--创建触发器
create trigger tb_user_insert_trigger
after update on tb_user for each row
begin
insert into user_logs(id, operation, operation_time, operation_id, operate_params) values
(null, 'insert', now(), new.id, concat('插入的数据内容为:id=', new.id,',name=',new.name,',phone=',new.phone,',email=',new.eamil,',profession=',new.profession))
end;
--查看触发器
show trigger;
--删除触发器
delete trigger tb_user_insert_trigger;
--验证tb_user_insert_trigger触发器效果
--1.先插入数据到tb_user
insert into tb_user(id,name,phone,email,profession,age,gender,status,createtime)
values(25,'二皇子','18809091212','erhuangzi@163.com','软件工程',23,'1','1',now());
--2.查看日志表是否含有新插入的数据
select * from user_logs;
--3.再插入一条数据到tb_user
insert into tb_user(id,name,phone,email,profession,age,gender,status,createtime)
values(26,'三皇子','18809091212','erhuangzi@163.com','软件工程',23,'1','1',now());
--4.再次查看日志表是否含有新插入的数据
select * from user_logs;
2.2 update类型
--1.创建update类型的触发器
create trigger tb_user_update_trigger
after update on tb_user for each row
begin
insert into user_logs(id, operation, operation_time, operation_id, operation_param) values
(null,'update',now(),new.id,
concat('更新之前的数据:id=',old.id,',name=',',name=',old.name,',phone=',old.phone,',email=',old.eamil,',profession=',old.profession,
'| 更新之后的数据:id=',new.id,',name=',new.name,',phone=',new.phone,',email=',new.eamil,',profession=',new.profession)
);
end;
--2.更新tb_user表中的数据
update tb_user set profession ='会计' where id =23;
--3.验证tb_user_update_trigger触发器是否有作用,查看user_logs表数据
select * from user_logs;
2.3 delete类型
--1,创建删除触发器
create trigger tb_user_delete_trigger
after delete on tb_user for each row
begin
insert into user_logs(id, operation, operation_time, operation_id, operation_param) values
(null,'update',now(),new.id,
concat('删除之前的数据:id=',old.id,',name=',',name=',old.name,',phone=',old.phone,',email=',old.eamil,',profession=',old.profession));
end;
--2,删除tb_user表中的数据
delete from tb_user where id=25;
--3,验证删除触发器是否执行,查询user_logs表中是否有删除的记录数据
select * from user_logs;
二、锁
锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种供多个用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,针对数据库而言显得尤为重要,也更加复杂。
MySQL中的锁,按照锁的粒度分,分为以下三类:
- 全局锁:锁定数据库中的所有表
- 表级锁:每次操作锁住整张表
- 行级锁:每次操作锁住对应的行数据
1. 全局锁
全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞。
其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性的视图,保证数据的完整性。
添加全局锁
--1.添加全局锁
mysql> flush tables with read lock;
--2.备份表,直接在cmd命令中执行
CMD: mysqldump -h ip -u用户名 -p密码 数据库名 > 磁盘路径/文件名.sql
CMD: mysqldump -h 192.168.200.202 -uroot -p1234 db01 > D:/db01.sql
--3.释放锁
mysql> unlook tables;
数据库中加全局锁,是一个比较中的操作,存在以下问题:
- 如果在主库中备份,那么在备份期间都不能执行更新,业务基本上就得停摆
- 如果在从库中备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟
在InnoDB引擎中,我们可以在备份时加上参数--single-transaction
参数来完成不加锁的一致性数据备份
mysqldump --single-transaction -uroot -p1234 itcast > itcast.sql
2. 表级锁
表级锁,每次操作锁住整张锁。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在MyISAM、InnoDB、BDB等存储引擎中
对于表级锁,主要分为以下三类:
- 表锁
- 元数据锁(meta data lock, MDL)
- 意向锁
2.1 表锁
对于表锁,分为两类:
- 表共享读锁(read lock)
- 表独占写锁(write lock)
语法
- 加锁:
lock tables 表名 ...... read/write;
- 释放锁:
unlock tables/ 客户端断开连接
表共享读锁
Lock tables 表名 read;
某表上加读锁,只允许客户端读操作(不限制哪一个客户端读),不能写操作
表独占写锁
lock tables 表名 wirte;
在某表上加写锁。对于当前客户端来说,可以读写表。但是对于其他客户端来说,不能读也不能写。
读锁不会阻塞其他客户端的读,但是会阻塞写。写锁既会阻塞其他客户端的读,又会阻塞其他客户端的写。
2.2 元数据锁
MDL(meta data lock)加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。为了避免DML与DDL冲突(增删改查和修改表结构语句的冲突),保证读写的正确性。
在MySQL5.5中引入了MDL,当对一张表进行增删改查的时候,加MDL读锁(共享);当对表结构进行变更操作的时候,加MDL写锁(排他)
查看元数据锁
select object_type, object_schema, object_name, lock_type, lock_duration from performance_schema.metadata_locks;
2.3 意向锁
为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查
- 意向共享锁(IS):与表锁共享锁(read)兼容,与表锁排它锁(write)互斥。由语句
select ..... Lock in share mode
添加- 意向排它锁(IX):与表锁共享锁(read)及排他锁(write)都互斥。意向锁之间不会互斥。由
insert、update、delete、select.....for update
添加
- 线程A执行:首先begin开启事务,执行update的时候会将指定行的加上行锁,紧接着会对整张表加上一个意向锁
- 线程B执行的之前,会检查这张表得到意向锁情况,能否加上线程B的锁取决于对意向锁的判断。如果线程B添加的表锁和线程A的意向锁兼容,则可以加锁。如果不兼容,则线程B处于阻塞状态,阻塞状态随着线程A事务的commit提交,释放意向锁和行锁之后,线程B才能加表锁。
可以通过以下SQL,查看意向锁及行锁的加锁情况:
select object_schema, object_name, index_name, lock_type, lock_mode, lock_data from performance_schema.data_locks;
测试意向共享锁
客户端一
begin; -- 开启事务
--lock in share mode 会将id=1的这一行数据锁住(行锁),同时添加了意向共享锁(表锁)
select * from score where id = 1 lock in share mode;
客户端二
--查询表的锁情况, lock_type: table表锁,record行锁; lock_mode:IS意向共享锁(读锁兼容,写锁互斥)
select object_schema, object_name, index_name, lock_type, lock_mode, lock_data
from performance_schema.data_locks;
--添加读锁
lock tables score read;
--释放读锁
unlock tables;
--添加写锁(会被客户端①的意向共享锁所阻塞)
lock tables score write;
测试意向排它锁
客户端一
--此时数据库会自动添加一个行锁和一个意向排它锁
update score set math=66 where id=1;
commit;
客户端二
--查询表的锁情况, lock_type: table表锁,record行锁; lock_mode:IX意向排它锁
select object_schema, object_name, index_name, lock_type, lock_mode, lock_data
from performance_schema.data_locks;
--添加读锁(因为客户端①此时存在意向排他锁,且update语句的事务没有提交,会阻塞ing 直到客户端①的事务commit提交)
lock tables score read;
--释放锁
unlock tables;
3. 行级锁
行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在InnoDB存储引擎中。
InnoDB的数据是基于索引组织的,行锁的通过对索引上的索引项来加锁实现的,而不是对记录加的锁。对于行级锁,主要分为以下三类:
- 行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此进行update和delete。在Read Commit、Read Repeatable隔离级别下都支持
- 间隙锁(Gap Lock ):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RP隔离级别下都支持
- 临键锁(Next-key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。在RP隔离级别下支持
3.1 行锁
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;
3.2 间隙锁、临键锁
默认情况下,InnoDB在RepeatableRead事务隔离级别运行,InnoDB使用next-key锁进行搜索和索引扫描,以防止幻读
- 索引上的等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁
- 索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询需求时,next-key lock退化为间隙锁
- 索引上的范围查询(唯一索引),会访问到不满足条件的第一个值为止
注意:间隙锁唯一目的是防止其他事务插入间隙。间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一个间隙上采用间隙锁。