MySQL 的锁和事务隔离级别(中)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 我们的数据库一般会并发执行多个事务,多个事务可能会并发的对相同的一批数据进行增删改查操作,可能出现我们常说的脏写、脏读、不可重复读、幻读这些问题。 这些问题的本质都是数据库的多事务并发问题,为了解决多事务并发问题,数据库设计了事务隔离机制、锁机制、MVCC多版本并发控制隔离机制。用了一整套机制来解决多事务并发问题,接下来,我们会深入讲解这些机制,让大家彻底理解数据库的内部执行原理。 说明:本文中提到的概念和案例是基于 mysql-5.7.x 展开

行锁场景


一个 session 开启事务更新不提交,另外一个session 更新同一行记录会阻塞,更新不同记录不会阻塞。


下图是我两个客户端开启事务,另外事务等待的一个过程。


image.png


总结:


MyISAM 在执行查询语句 SELECT 之前,会自动给涉及到的所有表加读锁,在执行 update 、insert 、delete 操作会自动给涉及到的表加写锁。


InnoDB 在执行查询语句 SELECT 时,因为有 MVCC 机制不会加锁。但是 update、insert 、delete 操作会加行锁。


总之,读锁阻塞写,但是不会阻塞读, 而写锁则会吧读和写都阻塞。


行锁和事务隔离级别分析


-- 创建表 SQL
create table `account` (
  `id` int(11) not null auto_increment,
  `name` varchar(20) default null,
  `balance` int(11) default null,
  primary key(`id`)
) engine = InnoDB default charset = utf8;
-- 插入数据
insert into `account` (`id`, `name`, `balance`) values ('1' , 'zhangsan', 100);
insert into `account` (`id`, `name`, `balance`) values ('2' , 'lisi', 200);
insert into `account` (`id`, `name`, `balance`) values ('3' , 'wangwu', 300);
insert into `account` (`id`, `name`, `balance`) values ('4' , 'zhaoliu', 400);


读未提交


  1. 打开一个客户端 A, 并且设置当前事务模式为 read uncommitted (未提交读), 查询表 account 的初始值:


--客户端 A
mysql> set tx_isolation ='read-uncommitted';
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account;
+----+----------+---------+
| id | name     | balance |
+----+----------+---------+
|  1 | zhangsan |     120 |
|  2 | lisi     |     200 |
|  3 | wangwu   |     300 |
|  4 | zhaoliu  |     400 |
+----+----------+---------+
4 rows in set (0.00 sec)


  1. 在客户端 A 的事务提交以前,打开另外一个客户端 B ,  对张三的余额进行修改。


-- 客户端 B
mysql> set tx_isolation ='read-uncommitted';
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> update account set balance = 100 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> select * from account;
+----+----------+---------+
| id | name     | balance |
+----+----------+---------+
|  1 | zhangsan |     100 |
|  2 | lisi     |     200 |
|  3 | wangwu   |     300 |
|  4 | zhaoliu  |     400 |
+----+----------+---------+
4 rows in set (0.00 sec)


  1. 这时候, 虽然客户端 B 没有提交,但是客户端 A 已经可以查询到 B 未提交的数据此现象就是发生了 脏读


-- 客户端 A
mysql> set tx_isolation ='read-uncommitted';
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account;
+----+----------+---------+
| id | name     | balance |
+----+----------+---------+
|  1 | zhangsan |     120 |
|  2 | lisi     |     200 |
|  3 | wangwu   |     300 |
|  4 | zhaoliu  |     400 |
+----+----------+---------+
4 rows in set (0.00 sec)
mysql> select * from account;
+----+----------+---------+
| id | name     | balance |
+----+----------+---------+
|  1 | zhangsan |     100 |
|  2 | lisi     |     200 |
|  3 | wangwu   |     300 |
|  4 | zhaoliu  |     400 |
+----+----------+---------+
4 rows in set (0.00 sec)


  1. 一但,客户端的事务因为某种原因可能会滚,所有的操作都会被测小,那么客户端 A查询到的数据就是脏数据


-- 客户端 B
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account;
+----+----------+---------+
| id | name     | balance |
+----+----------+---------+
|  1 | zhangsan |     120 |
|  2 | lisi     |     200 |
|  3 | wangwu   |     300 |
|  4 | zhaoliu  |     400 |
+----+----------+---------+
4 rows in set (0.00 sec)


  1. 在客户端 A执行更新语句 update account set balance = balance - 50 where id =1;  id = 1 的数据变成了,70 , 不是。50, 之数据不一致, 在应用程序中如果我们用。100 - 50  = 50。 如果不知道其他的程序回滚,想要解决这个问题可以采用读已提交的隔离级别


-- 客户端A
mysql> update account set balance = balance - 50 where id =1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> select * from account;
+----+----------+---------+
| id | name     | balance |
+----+----------+---------+
|  1 | zhangsan |      70 |
|  2 | lisi     |     200 |
|  3 | wangwu   |     300 |
|  4 | zhaoliu  |     400 |
+----+----------+---------+
4 rows in set (0.00 sec)


读已提交


  1. 客户端 A 查询 account 表,然后进行数据修改提交。


set tx_isolation ='read-committed';


-- 客户端 A
mysql> set tx_isolation ='read-committed';
Query OK, 0 rows affected, 1 warning (0.00 sec)
-- 开启事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
-- 更新 id = 1 的账户余额为 700
mysql> update account set balance = 700 where id = 1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0
-- 事务提交
mysql> commit;
Query OK, 0 rows affected (0.00 sec)


  1. 客户端 B 先查询 A 客户端提交前后的数据,事务内部相同查询语句在不同的时刻读取出来的结果不一致,不符合隔离性。出现了不可重复读的问题


set tx_isolation ='read-committed';


-- 客户端 B
-- 开启事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
-- 客户端 A 提交之前查询
mysql> select * from account;
+----+-----------+---------+
| id | name      | balance |
+----+-----------+---------+
|  1 | zhangsan  |      70 |
|  2 | lisi      |     200 |
|  3 | wangwu    |     300 |
|  4 | huayi     |     400 |
|  6 | wangmazi  |     600 |
|  8 | zhaoer    |     800 |
|  9 | wangmazi9 |     600 |
+----+-----------+---------+
7 rows in set (0.00 sec)
-- 客户端 A 提交后查询
mysql> select * from account;
+----+-----------+---------+
| id | name      | balance |
+----+-----------+---------+
|  1 | zhangsan  |     700 |
|  2 | lisi      |     200 |
|  3 | wangwu    |     300 |
|  4 | huayi     |     400 |
|  6 | wangmazi  |     600 |
|  8 | zhaoer    |     800 |
|  9 | wangmazi9 |     600 |
+----+-----------+---------+
7 rows in set (0.00 sec)


可重复读


  1. 客户端 A 开启事务,客户端 B 也是开启事务,并且插入一条数据


-- 客户端 B
set tx_isolation = 'REPEATABLE-READ';
mysql>  select * from account;
+----+----------+---------+
| id | name     | balance |
+----+----------+---------+
|  1 | zhangsan |      70 |
|  2 | lisi     |     200 |
|  3 | wangwu   |     300 |
|  4 | huayi    |     400 |
|  6 | wangmazi |     600 |
|  8 | zhaoer   |     800 |
+----+----------+---------+
6 rows in set (0.00 sec)


  1. 客户端 A 对 B 新增的数据进行修改,然后在查询就可以查询到了。所以存在 幻读, 违背了隔离性。


-- 客户端 A
-- 客户端提交后查询
mysql>  select * from account;
+----+----------+---------+
| id | name     | balance |
+----+----------+---------+
|  1 | zhangsan |      70 |
|  2 | lisi     |     200 |
|  3 | wangwu   |     300 |
|  4 | huayi    |     400 |
|  6 | wangmazi |     600 |
|  8 | zhaoer   |     800 |
+----+----------+---------+
-- 更新数据 9
mysql> update account set balance =   9000 where id = 9;
Query OK, 1 row affected (7.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
-- 重新查询可见, 违背隔离性
mysql>  select * from account;
+----+-----------+---------+
| id | name      | balance |
+----+-----------+---------+
|  1 | zhangsan  |      70 |
|  2 | lisi      |     200 |
|  3 | wangwu    |     300 |
|  4 | huayi     |     400 |
|  6 | wangmazi  |     600 |
|  8 | zhaoer    |     800 |
|  9 | wangmazi9 |    9000 |
+----+-----------+---------+
7 rows in set (0.00 sec)


可重复读是 MySQL 的默认隔离级别


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2月前
|
SQL 关系型数据库 MySQL
MySQL 锁
MySQL里常见的几种锁
56 3
|
2月前
|
存储 SQL 关系型数据库
MySQL的事务隔离级别
【10月更文挑战第17天】MySQL的事务隔离级别
118 43
|
16天前
|
关系型数据库 MySQL
mysql事务特性
原子性:一个事务内的操作统一成功或失败 一致性:事务前后的数据总量不变 隔离性:事务与事务之间相互不影响 持久性:事务一旦提交发生的改变不可逆
|
14天前
|
关系型数据库 MySQL 数据库
MySQL事务隔离级别及默认隔离级别的设置
在数据库系统中,事务隔离级别是一个关键的概念,它决定了事务在并发执行时如何相互隔离。MySQL提供了四种事务隔离级别,每种级别都解决了不同的并发问题。本文将详细介绍这些隔离级别以及MySQL的默认隔离级别。
|
2月前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1674 14
|
2月前
|
存储 关系型数据库 MySQL
优化 MySQL 的锁机制以提高并发性能
【10月更文挑战第16天】优化 MySQL 锁机制需要综合考虑多个因素,根据具体的应用场景和需求进行针对性的调整。通过不断地优化和改进,可以提高数据库的并发性能,提升系统的整体效率。
103 1
|
2月前
|
SQL 关系型数据库 MySQL
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
尼恩,一位40岁的资深架构师,通过其丰富的经验和深厚的技術功底,为众多读者提供了宝贵的面试指导和技术分享。在他的读者交流群中,许多小伙伴获得了来自一线互联网企业的面试机会,并成功应对了诸如事务ACID特性实现、MVCC等相关面试题。尼恩特别整理了这些常见面试题的系统化解答,形成了《MVCC 学习圣经:一次穿透MYSQL MVCC》PDF文档,旨在帮助大家在面试中展示出扎实的技术功底,提高面试成功率。此外,他还编写了《尼恩Java面试宝典》等资料,涵盖了大量面试题和答案,帮助读者全面提升技术面试的表现。这些资料不仅内容详实,而且持续更新,是求职者备战技术面试的宝贵资源。
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
|
2月前
|
存储 关系型数据库 MySQL
RR隔离级别在MySQL中的实现与幻读问题探讨
【10月更文挑战第3天】在数据库管理系统中,事务隔离级别是确保数据一致性和并发性能的关键要素。MySQL作为广泛使用的关系型数据库管理系统,支持多种事务隔离级别,其中可重复读(Repeatable Read,简称RR)是其默认隔离级别。本文将深入探讨RR隔离级别在MySQL中的实现原理,以及RR隔离级别下幻读问题的产生与解决方案。
103 2
|
2月前
|
关系型数据库 MySQL Java
MySQL数据锁:Record Lock,Gap Lock 和 Next-Key Lock
本文基于 MySQL 8.0.30 版本及 InnoDB 引擎,深入解析三种行锁机制:记录锁(Record Lock)、间隙锁(Gap Lock)和临键锁(Next-key Lock)。记录锁锁定索引记录,确保事务唯一修改;间隙锁锁定索引间的间隙,防止新记录插入;临键锁结合两者,锁定范围并记录自身,有效避免幻读现象。通过具体示例展示了不同锁的作用机制及其在并发控制中的应用。
194 2
|
2月前
|
存储 关系型数据库 MySQL
MySQL数据库锁:共享锁和独占锁
本文详细介绍了`InnoDB`存储引擎中的两种行级别锁:共享锁(S锁)与排他锁(X锁)。通过具体示例展示了这两种锁的工作机制及其在`InnoDB`与`MyISAM`引擎中的表现差异。文章还提供了锁的兼容性矩阵,帮助读者更好地理解锁之间的互斥关系。最后总结了两种锁的特点及适用场景。适合希望深入了解`MySQL`并发控制机制的读者阅读。
80 1