浅谈MySQL中的锁
根据加锁范围MySQL中的锁可以分为全局锁,表级锁以及行级锁。
全局锁
全局锁是对整个数据库进行加锁的,执行Flush table with read lock对整个数据库加锁,执行之后会使得整个库处于只读状态,数据更新语句,数据定义语句以及更新类事务的提交语句都会被阻塞。使用 unlock tables解锁。
例如对数据库先执行Flush table with read lock,另一进程进行查询插入操作的测试,
学生表如下:
CREATE TABLE `t_student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(128) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
执行Flush table with read lock之后,
执行查询语句结果如下:
mysql> select * from t_student;
+----+--------+------+
| id | name | age |
+----+--------+------+
| 1 | 张三 | 18 |
+----+--------+------+
1 row in set (0.00 sec)
发现查询时不受影响的。
执行插入语句结果如下:
mysql> insert into t_student(name,age) values('李四',19);
该线程就被阻塞住了直到我们执行解锁语句unlock tables
mysql> insert into t_student(name,age) values('李四',19);
Query OK, 1 row affected (1 min 14.16 sec)
mysql>
全局锁一个典型的使用场景为做全库备份。
但是对于支持事务的引擎(例如Innodb)时,可以在可重复读的隔离级别下开启一个事务,当 mysqldump 使用参数–single-transaction 的时候,导数据之前就会启动一个事务,来确保拿到一致性视图,后续的备份只需对拿到的视图备份即可。
此外使全库只读还有set global readonly=true的方式,但是不建议使用:
1)、某些系统中会把readonly用于其他逻辑判断,例如判断主库还是备库。
2)、FTWRL加锁后,若客户端发生异常,该锁会自动释放,而set global readonly=true的方式客户端发生异常后,整个库还处于只读状态。
表级锁
表级的锁有两种,一种是表锁一种是元数据锁(meta data lock,MDL)。
表锁的语法:lock tables table_name read/write, 其也可以使用unlock tables进行解锁。
需要注意的是lock tables加锁后除了会限制其他线程的读写外还会限制本线程的读写。
例如:
线程1执行如下sql
lock tables t_student read, t_book write;
之后其他线程只能读t_student,对t_book既不能读又不能写。
当前线程对t_student也只允许读。
另一线程执行查询结果如下,对t_book的查询对t_student的写入都被阻塞住了
mysql> select * from t_student;
+----+--------+------+
| id | name | age |
+----+--------+------+
| 1 | 张三 | 18 |
| 2 | 李四 | 19 |
+----+--------+------+
2 rows in set (0.00 sec)
mysql> select * from t_book;
mysql> insert t_student(name,author) values('三国演义','罗贯中');
当前线程对t_student执行写操作结果如下:
mysql> insert into t_student(name,age) value('王五',22);
ERROR 1099 (HY000): Table 't_student' was locked with a READ lock and can't be updated
另一种表级锁为meta data lock(MDL),MDL不需要显式使用,访问表时会被自动加上。对表做增删改查时加MDL读锁,对表做结构变更时加MDL写锁。
读锁之间不互斥:因此可以多个线程并发对一张表进行增啥改查;
读写锁互斥,写锁之间互斥,用来保证变更表结构的安全性。
行级锁
MySQL 的行锁是在引擎层由各个引擎自己实现的。但并不是所有的引擎都支持行锁,比如 MyISAM 引擎就不支持行锁,不支持行锁使得MyISAM的并发控制只能使用表锁,这也是InnoDB替代MyISAM的一个重要原因。
行锁顾名思义,针对数据表的行记录建立的锁,如果线程1更新这一行,线程二也要更新这一行,如此只有等线程一的事务提交后,线程二才能更新
两阶段锁协议:
在InnoDB中,行锁是在需要时添加的,并不是不需要时就立即释放,只有当事务提交时才进行释放。
如上图所示事务A执行update t set k = k + 1 where id = 1;时就会对id=1这行记录加行锁,就算这条语句执行完了还不能将行锁释放,此时事务B的更新操作会被阻塞,直到事务A提交了事务。
知道了两阶段锁的这个特性后,对于一条事务中多条跟新语句其会锁多行,我们可以通过改变其顺序(令竞争激烈并发度高的那些可能造成锁冲突的语句往后放)达到提升并发度的效果,例如:
电影院卖票这种场景
开启事务
用户余额减少一张电影票价
影院余额增加一张电影票价
添加一条购买记录
提交事务
经过分析我们得知给影院余额增加一张票价这条语句的跟容易造成锁冲突,例如多个用户同时购票,因此将其放到最后即
开启事务
添加一条购买记录
用户余额减少一张电影票价
影院余额增加一张电影票价
提交事务
保证拿到锁后很快释放。
死锁与死锁检测
例如如下场景
事务1 事务2
update ...where id = 1 update ...where id = 2
update ...where id = 2 update ...where id = 1
提交事务 提交事务
事务1先拿到id=1这一行的行锁,事务2先拿到id=2这一行的行锁,此时事务1由于拿不到id = 2这一行的锁,他就阻塞住自己直到事务2释放了id=2的行锁,阻塞阶段其并不会释放自己手里的id=1这一行的锁。事务2同理等事务1释放id=1的行锁,因此就进入了死锁状态。
出现死锁的两种解决方式;
1)、直接进入等待,直到超时,超时参数innodb_lock_wait_timeout默认为50s。
2)、发起死锁检测,发现死锁后立即回滚循环等待链中某一个事务,让其他事务先执行。将参数 innodb_deadlock_detect 设置为 on即可,该参数默认值亦为on。
方式二会导致每个新来阻塞住的线程都会判断其是否出现死锁,对于正常的阻塞亦会执行该过程,该过程是一个O(N)时间复杂度的操作,如此会极大得消耗cpu资源。
对于热点行更新的性能问题的可以对于相同行的更新,在进入引擎之前排队。这样在 InnoDB 内部就不会有大量的死锁检测工作了。
- node-inspector调试工具应用
- karma的基础应用之与fis结合
- 区块链学堂——区块链有前途吗?
- 程序猿的日常——Java基础之抽象类与接口、枚举、泛型
- addSubView需要注意的几个点为什么要在addsubview:一个view对象后,release它?
- nodejs 命令行、自定义
- GeForce驱动EULA更新惹争议,NVIDIA的理由是这样的
- 关于HTML面试题汇总之visibility
- 加密狗进场暴富开始?请别侮辱区块链和游戏
- 程序猿的日常——Java基础之clone、序列化、字符串、数组
- [你必须知道的.NET] 第四回:后来居上:class和struct
- oc 中随机数的用法(arc4random() 、random()、CCRANDOM_0_1()
- 央行出台条码支付新规;美团打车将在7大城市上线;国产人工智能平台型芯片首发;苹果就降速门致歉
- 阿三又让全球看笑话,无人驾驶的地铁名不副实,还直接跑到大街上
- MySQL 教程
- MySQL 安装
- MySQL 管理与配置
- MySQL PHP 语法
- MySQL 连接
- MySQL 创建数据库
- MySQL 删除数据库
- MySQL 选择数据库
- MySQL 数据类型
- MySQL 创建数据表
- MySQL 删除数据表
- MySQL 插入数据
- MySQL 查询数据
- MySQL where 子句
- MySQL UPDATE 查询
- MySQL DELETE 语句
- MySQL LIKE 子句
- mysql order by
- Mysql Join的使用
- MySQL NULL 值处理
- MySQL 正则表达式
- MySQL 事务
- MySQL ALTER命令
- MySQL 索引
- MySQL 临时表
- MySQL 复制表
- 查看MySQL 元数据
- MySQL 序列 AUTO_INCREMENT
- MySQL 处理重复数据
- MySQL 及 SQL 注入
- MySQL 导出数据
- MySQL 导入数据
- MYSQL 函数大全
- MySQL Group By 实例讲解
- MySQL Max()函数实例讲解
- mysql count函数实例
- MYSQL UNION和UNION ALL实例
- MySQL IN 用法
- MySQL between and 实例讲解
- 从安全切面到Security Mesh
- SpringBoot源码学习(十一) - bean的实例化过程
- 每天一杯力扣快乐水
- Typescript的tsconfig.json
- python和R语言计算蛋白质内部氨基酸相互作用
- 超文本传输协议 - 白话篇
- 为什么你的简历没人看?7份案例分析
- 浅析动态切换数据源的原理(接上篇)
- SpringBoot源码解析(十二)- Autowired是如何注入的
- 项目要实现多数据源动态切换,咋搞?
- 这一次,带你全面了解锁机制!
- GitHub标星1w+超牛的微服务项目,开发脚手架
- Redis中hash、set、zset的底层数据结构原理
- Redis中string、list的底层数据结构原理
- Redis中字符串的表示