MySQL:(十)MySQL锁和事务
(一)关系型数据库基础
(二)MySQL安装
(三)管理数据库和表
(四)用户和权限管理
(五)函数,存储过程和触发器
(六)MySQL架构
(七)存储引擎
(八)MySQL服务器选项,系统和状态变量
(九)优化查询和索引管理
(十)锁和事务管理
(十一)日志管理
(十二)备份还原
(十三)MySQL集群
MySQL 锁
MySQL锁的概述
1)MySQL锁
**锁是计算机协调多个进程或纯线程并发访问某一资源的机制。**即锁的目前是为了实现并发控制。相对其他数据库而言,MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。MySQL大致可归纳为以下3种锁:
- 开销、加锁速度、死锁、粒度、并发性能
- 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
- 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
- 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
2)上述关于锁的概念
- 锁粒度:
即通常所说的锁级别。数据库引擎具有多粒度锁定,允许一个事务锁定不同类型的资源。 为了尽量减少锁定的开销,数据库引擎自动将资源锁定在适合任务的级别。
①表级锁
②行级锁
如果两个用户同时对数据库中的某个表的某一个记录修改时:
==>存储引擎自动加锁的锁粒度
myisam:表级锁 别的用户不能对表中其他记录进行修改,影响并发
innodb:行级锁 别的用户只是不能对当前记录进行修改
-
锁:
读锁:共享锁,只读不可写(包括当前事务),多个读互不阻塞
写锁:独占锁,排它锁,写锁会阻塞其他事务(不包括当前事务) -
锁的分类:
隐式锁:由存储引擎自动施加的锁
显示锁:用户手动请求 -
锁的实现:
存储引擎:自行实现其锁策略和锁粒度
服务器级:实现了锁,表级锁,用户可显示请求 -
锁策略:在锁粒度及数据安全性寻求的平衡机制
3)下面主要实现显示加锁(存储引擎加锁是mysql自动实现的功能不能干预)
- LOCK TABLES 加锁
tbl_name [[AS] alias] lock_type
[, tbl_name [[AS] alias] lock_type] …
lock_type: READ , WRITE - UNLOCK TABLES 解锁
- FLUSH TABLES [tb_name[,…]] [WITH READ LOCK]
关闭正在打开的表(清除查询缓存),通常在备份前加全局读锁 - SELECT clause [FOR UPDATE | LOCK IN SHARE MODE]
查询时加写或读锁
由上述命令可知:人为加锁默认只能加表级锁
lock tables
其中加锁类型有:read 和 write
示例:终端:/dev/pts/0
> lock tables students read; 对studens加锁
> select stuid,name,age from students where stuid<3; == ok
+-------+-------------+-----+
| stuid | name | age |
+-------+-------------+-----+
| 1 | Shi Zhongyu | 22 |
| 2 | Shi Potian | 22 |
+-------+-------------+-----+
> update students set age=25 where stuid=1; == 直接报错
ERROR 1099 (HY000): Table 'students' was locked with a READ lock and can't be updated
终端:/dev/pts/1
> select stuid,name,age from students where stuid<3; == ok
+-------+-------------+-----+
| stuid | name | age |
+-------+-------------+-----+
| 1 | Shi Zhongyu | 22 |
| 2 | Shi Potian | 22 |
+-------+-------------+-----+
> update students set age=25 where stuid=1; == 阻塞
此时会被阻塞在此处,直至表级锁被释放
总结:人为添加read的表级锁时,从表中读取数据是影响的,但是对表进行修改时会被阻塞.
> unlock tables;
==>unlock tables;只能解锁当前终端加锁的所有表
==>实验出现这样了的现象:
在终端:/dev/pts/0
对其中一张表:>lock tables teachers read;
在终端:/dev/pts/1
对其中一张表:>lock tables students read;
然后在各自的终端访问对方加锁的表就不能访问啦?直接报错?
示例:添加write锁:
终端:/dev/pts/0
> lock tables teachers write;
> select * from teachers; == ok
> select * from teachers; == ok
终端:/dev/pts/1
> select * from teachers; == 阻塞
此时将会被阻塞
总结:write锁是独占锁,仅能自己查询或者修改,其他用户不能的操作均会别阻塞
示例:FLUSH TABLES [tb_name[,...]] [WITH READ LOCK]
> flush tables; 全局读锁
整个实例的全局读锁!
> flush tables students with read lock;
注: <即使这样明确写明对students表加读锁也是全局读锁>
解锁:> unlock tables;
事务
事务
-
事务Transactions:一组原子性的SQL语句,或一个独立工作单元
-
事务日志:记录事务信息,实现undo,redo等故障恢复功能
-
事务的特性:
A:atomicity原子性;整个事务中的所有操作要么全部成功执行,
要么全部失败后回滚C:consistency一致性;数据库总是从一个一致性状态转换为另
一个一致性状态I:isolation隔离性;一个事务所做出的操作在提交之前,是不
能为其它事务所见;隔离有多种隔离级别,实现并发D:durability持久性;一旦事务提交,其所做的修改会永久保
存于数据库中
事务的生命周期
- 启动事务:
BEGIN
BEGIN WORK
START TRANSACTION - 结束事务:
COMMIT:提交
ROLLBACK: 回滚
注意: 只有事务型存储引擎中的DML语句方能支持此类操作,myisam不支持事务 - 自动提交:set autocommit={1|0} 默认为1,为0时设为非自动提交
建议:显式请求和提交事务,而不要使用“自动提交”功能 - 事务支持保存点:savepoint
SAVEPOINT identifier
ROLLBACK [WORK] TO [SAVEPOINT] identifier
RELEASE SAVEPOINT identifier
启动一个事务:begin
增删改....
结束事务:commit和rollback
> select @@autocommit 自动提交;
+---------+
| 自动提交 | @@autocommit
+---------+
| 1 |
+---------+
==>MySQL系统中默认启动的是自动提交机制
即对表的增删改在SQL语句最后的分号就当做事务提交了。
> set autocommit=0;会话将autocommit改为0
以students表为例
session1 | session2 |
---|---|
> set autocommit=0;将事务改为显示,但是临时修改 | |
>delete from students where stuid=31; | |
> select * from hellodb.students;可查询到stuid=31的记录 | |
> commit;显示提交事务 | |
> select * from hellodb.students;不能查询stuid=31的记录 | |
同时执行多个增删改操作,如果rollback将全部撤销 | |
显示提交事务时,如果掉电系统故障均视为rollback | |
只有显示提交即>commit;才算事务的提交 |
注意: 事务的rollback只能针对DDL(insert,delete,update)即对表的增删改可以在事务未提交时撤销,DDL(create,drop,alter)不能rollback即删库删表不能使用rollback撤销。
事务的意义
- 事务可以保证数据的完整性和安全性。eg:转账必须以事务方式进行
- 多个DDL语句作为整体执行,比单个DDL语句独立执行速度更快。
示例:
定义如下存储过程,create test表,insert 100000 条记录
快速清空表中记录:truncate table testlog
create table test (id int auto_increment primary key,name char(10));
delimiter $$
create procedure proc_test()
begin
declare i int;
set i = 1;
while i < 100000
do insert into test(name) values (concat('bob',i));
set i = i +1;
end while;
end$$
delimiter ;
session1 以事务整体执行存储过程 | session2 独立执行insert |
---|---|
> begin;> call proc_test;> commit; | > call proc_test; |
耗时如下: | 耗时如下: |
Query OK, 1 row affected (2.99 sec) | Query OK, 1 row affected (2 min 18.05 sec) |
事务的隔离级别
- READ UNCOMMITTED 可读取到未提交数据,产生脏读
- READ COMMITTED 可读取到提交数据,但未提交数据不可读,产生不可重复读,即可读取到多个提交数据,导致每次读取数据不一致
- REPEATABLE READ 可重复读,多次读取数据都一致,产生幻读,即读取过程中,即使有其它提交的事务修改数据,仍只能读取到未修改前的旧数据。此为MySQL默认设置
- SERIALIZABILE 可串行化,未提交的读事务阻塞修改事务,或者未提交的修改事务阻塞读事务。导致并发性能差
- MVCC: 多版本并发控制,和事务级别相关
- 说明: REPEATABLE READ最适合数据库的备份。备份是希望备份某个点的数据库状态。
事务隔离级别 | 脏读可能性 | 不可重复读可能性 | 幻读可能性 | 加锁读 |
---|---|---|---|---|
读未提交(read-uncommitted) | 是 | 是 | 是 | 否 |
不可重复读(read-committed) | 否 | 是 | 是 | 否 |
可重复读(repeatable-read) | 否 | 否 | 是 | 否 |
串行化(serializable) | 否 | 否 | 否 | 是 |
脏数据:脏数据在临时更新(脏读)中产生。即一个事务读取了另一个事务未提交的数据。
READ-UNCOMMITTED:脏读
脏数据可读
READ-COMMITTED:不可重复读
即可能不同时间读取的记录不相同,出现幻读
REPEATABLE READ:可重复读
读取事务开始时刻的数据库状态,在整个事务过程中记录不变化
SERIALIZABILE 可串行化
未提交的读事务阻塞修改事务以及未提交的修改事务阻塞读事务
事务隔离级别相关的设置
- 事务隔离级别系统变量:tx_isolation
- 事务隔离级别服务器选项:
transaction-isolation=[READ-UNCOMMITTED|READ-COMMITTED|REPEATABLE-READ|SERIALIZABLE] - 系统默认的事务隔离级别是REPEATABLE-READ
实验:READ-UNCOMMITTED
- [mysqld]
transaction-isolation=READ-UNCOMMITTED
session1 | session2 |
---|---|
>begin; | >begin; |
> insert students(stuid,name,age)values(32,‘Wu Ming’,35); | > select * from students where name like ‘W%’; |
事务未提交 | 记录可查询 |
>rollback; | 记录不不可查询 |
commit; |
实验:READ-UNCOMMITTED
- ransaction-isolation=READ-COMMITTED
session1 | session2 |
---|---|
>begin; | >begin; |
> insert students(stuid,name,age)values(35,‘Duan lang’,35); | |
还未提交的事务 | |
> select * from students where name like ‘duan%’; | > select * from students where name like ‘duan%’; |
可查询 | 不可查询 |
> commit;提交事务 | |
> select * from students where name like ‘duan%’; | > select * from students where name like ‘duan%’; |
可查询 | 可查询 |
>begin; 再次开启一个事务 | |
> insert students(stuid,name,age)values(36,‘Duan Yu’,30); | |
> commit;提交事务 | |
> select * from students where name like ‘duan%’; | |
可查询的记录增加 | |
此session中同样的查询,结果不同,即不可重复读 | |
> commit; |
实验:REPEATABLE-READ
- ransaction-isolation=REPEATABLE-READ
session1 | session2 |
---|---|
>begin; | >begin; |
>insert students(stuid,name,age)values(36,‘Qiao Feng’,40); | |
可查询 | > select * from students; |
不可查询 | |
> commit; 提交事务 | |
不可查询 | |
可重复读,但实际上数据已经改变,幻读。 | |
> commit; |
实验:SERIALIZABLE
session1 | session2 |
---|---|
>begin; | >begin; |
> select * from students; | > select * from students; |
可同时读 | 可同时读 |
> select * from students; | |
>insert students(stuid,name,age)values(38,‘Liu Bei’,50); | |
读阻塞改,阻塞一定时间,若其他人还不提交事务,报错 | |
> commit; | > commit; |
> begin; | > begin; |
>insert students(stuid,name,age)values(38,‘Liu Bei’,50); | |
> select * from students; | |
更改阻塞读,阻塞一定时间,若其他人还不提交事务,报错 | |
阻塞时间内提交事务 | 可执行,包含立即更改的数据均可查询 |
死锁
- 两个或多个事务在同一资源相互占用,并请求锁定对方占用的资源的状态
session1 | session2 |
---|---|
> begin; | > begin; |
> update teachers set age=50 where tid=4; | > update students set age=10 where stuid=39; |
修改成功,innodb会加行级锁 | 修改成功,innodb会加行级锁 |
此时session1对session2事务中修改的行stuid=39再修改时 | 此时session2对session1事务中修改的行tid=4再修改时 |
死锁 | 死锁 |
> update students set age=100 where stuid=39; | > update teachers set age=10 where tid=4; |
阻塞 | 阻塞 |
系统会发现死锁,然后将其中一个事务进行回滚 | 系统会发现死锁,然后将其中一个事务进行回滚 |
— | — |
Query OK, 1 row affected (7.46 sec) | Lock wait timeout exceeded; try restarting transaction |
可能出现这种场景:当一个事务执行改操作时,innodb默认添加行锁,但是事务一直未提交
show processlist and kill #
session1 | session2 | session3 |
---|---|---|
> begin; | - | - |
> update teachers set age=50 where tid=4; | - | - |
session1一直不提交事务,行锁一直存在 | - | - |
- | session2想对同一行进行修改时 | - |
- | > update teachers set age=30 where tid=4; | - |
- | 因为行级锁的原因会阻塞 | - |
- | - | > show processlist; |
- | - | 查询哪些线程正在运行 |
- | - | 需要干预,将session1的事务关闭 |
- | - | > kill id(kill session1对应的id) |
- | - | 释放行级锁 |
- | - | 不能允许这种事务一直不提交,将行锁一直开启,影响其他session访问 |
事务日志简介
-
事务日志文件: ib_logfile0, ib_logfile1
-
事务日志的写入类型为“追加”,因此其操作为“顺序IO”;通常也被称为:预写式日志 write ahead logging
-
注:事务即使没有提交,事务日志仍会记录
-
默认 ib_logfile0, ib_logfile1 两个文件固定大小为5M,文件被写满,前面的日志将会被覆盖
-
查询不会记录事务日志,事务日志只记录增删改操作即DDL(insert,update,delete)
-
事务日志记录的是对应具体修改的哪个磁盘块,并不是记录SQL语句
-
关于事务日志详情见mysql日志