MySQL 事务和锁

1. 事务

2. 锁

 

 

1. 事务

什么是事务?

事务是指一组业务操作,要么全部成功,要么全部失败。

比如银行转账业务,步骤一:从 A 账户减少 300 元;步骤二:向 B 账户增加 300 元。为了确保总的金额不变,就要维持数据的一致性,那么步骤一和步骤二两个操作必须全确认或者全取消。这里的每个步骤就可以理解为每个 SQL 语句。

事务的特性:ACID

  • 原子性(Atomicity):一个事务要么全部成功(提交),要么全部失败(回滚),不能只完成其中的一部分操作。
  • 一致性(Consistency):事务的执行不能破坏数据的完整性和一致性,一个事务在执行之前和执行之后,所有数据都必须处于一致的状态。
  • 隔离性(Isolation):在并发环境中,事务是相互隔离的,一个事务的执行不能被其他事务干扰。
  • 持久性(Durability):一旦事务提交,那么它对于系统或者数据的修改是永久性的。

事务语句

  • begin(start transaction):显式开启一个事务。
  • commit:提交事务。
  • rollback:回滚事务。
  • set autocommit:设置自动提交模式。

autocommit 默认为 on(打开),即我们每执行一条 SQL 都相当于一个事务并自动提交。

示例场景

场景 1:commit

Session 1 Session 2
start transaction;  -- 显式开启一个事务
 
delete from emp where ename=wang;
 
select * from emp;  -- 数据"wang"已删除
 
 
select * from emp;  -- 数据"wang"未删除
commit;  -- 提交事务
 
select * from emp;  -- 数据"wang"已删除
 
 
select * from emp;  -- 数据"wang"已删除

场景 2:rollback

Session 1 Session 2
begin;  -- 显式开启一个事务
 
insert into emp values(wang, now(), 4000, 2); 
 
select * from emp;  -- 新增了"wang"数据
 
insert into emp values(liu, now(), 90000, 3);
 
select * from emp;  -- 新增了"liu"数据
 
   select from emp;  -- 未新增两条数据
   rollback;   -- 回滚事务  
select * from emp;  -- 未新增两条数据
 
 
select * from emp;  -- 未新增两条数据

事务的隔离级别

隔离问题:

  1. 脏读:一个事务读到另一个事务没有提交的数据。
  2. 不可重复读:一个事务读到另一个事务已提交的数据(update)。
  3. 虚读(幻读):一个事务读到另一个事务已提交的数据(insert)。

隔离级别:

  • Read Uncommitted:读未提交。允许事务督导其他事务未提交的数据变更。
    • 存在 3 个问题:脏读、不可重复读、虚读
  • Read Committed:读已提交。允许事务读到其他事务已提交的数据变更。
    • 解决 1 个问题:脏读
    • 存在 2 个问题:不可重复读、虚读
  • Repeatable Read:可重复读。在同一个事务中保证读到的结果的一致性,是 InnoDB 默认的隔离级别。
    • 解决 2 个问题:脏读、不可重复读
    • 存在 1 个问题:虚读
  • Serializable :串行化。完全隔离了事务之间的影响,一个事务正在读的数据,另一个事务不允许修改。
    • 解决 3 个问题:脏读、不可重复读、虚读

 

2. 锁

锁是一种使各种共享资源在被并发访问变得有序的机制,目的是为了保证数据的一致性。

锁的类型

根据加锁范围大致划分:

  • 表锁:表锁会锁定整张表,其他事务无法操作或者无法变更操作。(Myisam)
  • 行锁:只会锁定需要的行,并发性会更好,并且行锁一定是作用在索引上的。(InnoDB)

根据加锁功能大致划分:

  • 共享锁(Shared Lock):一个事务并发读取某一行记录所需要持有的锁。
  • 排他锁/独占锁(Exclusive Locks):一个事务并发更新或删除某一行记录所需要持有的锁。

共享锁/排他锁都只是行锁。

死锁

死锁发生在当两个事务均尝试获取对方已经持有的排他锁时。

在 innodb 中,select 不会对数据加锁,而 update/delete 会加行级别的独占锁。

当数据库的隔离级别为 Repeatable Read 或 Serializable 时,我们来看以下会发生死锁的并发事务场景。

表结构示例:

mysql> desc user;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int         | YES  | MUL | NULL    |       |
| age   | int         | YES  | MUL | NULL    |       |
| name  | varchar(30) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.12 sec)

场景 1:表锁

当 update 数据未作用于索引时,会发生表锁:

Session 1 Session 2
begin; begin;
select * from user; select * from user;
update user set name=‘test1‘ where name=‘xiaoming‘;  
(表)锁等待解除 update user set name=‘test1‘ where name=‘xiaodan‘;
  死锁,事务被回滚

场景 2:行锁

当 update 数据作用于索引时,会发生行锁:

Session 1 Session 2
begin; begin;
select * from user; select * from user;
update user set name=‘test1‘ where id=1;  
(行)锁等待解除 update user set name=‘test1‘ where id=2;
  死锁,事务被回滚

场景 3:死锁回滚

当 InnoDB 检测到死锁时,会回滚其中一个事务,让另一个事务得以完成。

Session 1 Session 2
begin; begin;
select * from user; select * from user;
update user set name=‘test2‘ where id=1;  
(行)锁等待解除 update user set name=‘test2‘ where id=1;
  死锁,事务被回滚
mysql>  update user set name=test2 where id=1;  -- Session 2
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

死锁的应对方案

Session 1 Session 2
begin; begin;
select * from user; select * from user;
update user set name=‘test3‘ where id=1;  
commit; update user set name=‘test4‘ where id=1;
  commit;

在这个并发场景下,两个事务均能成功提交,而不会有死锁。

如何减少死锁发生

  1. 使用合适的索引。
  2. 使用更小的事务。
  3. 经常性的提交事务,避免事务被挂起。

 

select * from emp;  -- 未新增两条数据

MySQL 事务和锁

上一篇:shell 处理文档信息导入数据库-自动化运维linux


下一篇:oracle数据库查询表空间使用情况