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

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介: 我们的数据库一般会并发执行多个事务,多个事务可能会并发的对相同的一批数据进行增删改查操作,可能出现我们常说的脏写、脏读、不可重复读、幻读这些问题。 这些问题的本质都是数据库的多事务并发问题,为了解决多事务并发问题,数据库设计了事务隔离机制、锁机制、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 的默认隔离级别


相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
27天前
|
SQL 关系型数据库 MySQL
轻松入门MySQL:保障数据完整性,MySQL事务在进销存管理系统中的应用(12)
轻松入门MySQL:保障数据完整性,MySQL事务在进销存管理系统中的应用(12)
|
3天前
|
缓存 关系型数据库 MySQL
【专栏】MySQL高可用与性能优化——从索引到事务
【4月更文挑战第27天】本文探讨了提升MySQL性能和高可用性的策略,包括索引优化、查询优化和事务管理。通过合理使用B-Tree和哈希索引,避免过度索引,以及优化查询语句和利用查询缓存,可以改善性能。事务管理中,应减小事务大小并及时提交,以保持系统效率。主从或双主复制可增强高可用性。综合运用这些方法,并根据实际需求调整,是优化MySQL的关键。
|
4天前
|
SQL 存储 关系型数据库
MySQL锁详解
MySQL锁详解
10 0
|
4天前
|
算法 关系型数据库 MySQL
|
5天前
|
SQL 关系型数据库 MySQL
MySQL 锁
MySQL 锁
|
6天前
|
存储 SQL 关系型数据库
MySQL 事务
MySQL 事务
|
11天前
|
关系型数据库 MySQL 索引
MySQL 锁机制
MySQL 锁机制
7 0
|
19天前
|
关系型数据库 MySQL 数据库
MySQL锁三部曲:临键、间隙与记录的奇妙旅程
MySQL锁三部曲:临键、间隙与记录的奇妙旅程
18 0
|
19天前
|
存储 SQL 关系型数据库
【MySQL实战笔记】03.事务隔离:为什么你改了我还看不见?-02
【4月更文挑战第7天】数据库通过视图实现事务隔离,不同隔离级别如读未提交、读已提交、可重复读和串行化采用不同策略。以可重复读为例,MySQL使用多版本并发控制(MVCC),每个事务有其独立的视图。回滚日志在无更早视图时被删除。长事务可能导致大量存储占用,应避免。事务启动可显式用`begin`或设置`autocommit=0`,但后者可能意外开启长事务。建议使用`autocommit=1`并显式管理事务,若需减少交互,可使用`commit work and chain`。
31 5
|
1月前
|
关系型数据库 MySQL 测试技术
面试-MySQL的四种事务隔离级别
面试-MySQL的四种事务隔离级别
21 0