Py学习  »  DATABASE

全面分析 MySQL并发控制

看,未来 • 3 年前 • 356 次点击  

在这里插入图片描述

并发控制

为什么会提出这个话题?不言而喻。

无论何时,只要有多个查询需要在同一时刻查询数据,都会产生并发问题。
我也不多废话,如果是进来找代码实现的,请移步: 不是你记忆中的单例模式,但适用的程度,更胜一筹
当然,建议还是打开看一下,说不定就涨了些奇奇怪怪的知识。

本篇虽然题目说:全面分析,但是谁都知道,并发控制是一个多么庞大的概念是吧,本篇主要讲的是:MySQL的锁、存储引擎、事务处理机制。如果不是你期待的,可以省点时间啦;如果是的话,点赞收藏错不了!


MySQL逻辑架构

在这里插入图片描述

每个连接都会在mysql服务端产生一个线程(内部通过线程池管理线程),比如一个select语句进入,mysql首先会在查询缓存中查找是否缓存了这个select的结果集,如果没有则继续执行 解析、优化、执行的过程;反之则会直接从缓存中获取结果集。

我们常规认识的锁是这样的:对于临界资源A,有进程B和进程C需要对其进行访问,为了防止冲突,当某个进程比如说A先到达,它会取得互斥锁,那么在A使用这个资源的时候,B是无法使用这个资源的,它必须等待,直到A释放了锁为止。
在学校里面老师就是在这么教我们的。
当然,并不满足与此。如果想多了解锁一点,请移步: 面试常问 乐观锁、悲观锁,互斥锁、自旋锁

上面这种互斥锁的方案在实际应用环境中,但并不支持大并发处理,我们来看看一些解决方案:

读写锁

读锁:读锁是共享的,多个客户在同一时刻可以同时读取同一个资源,而互不干扰(只要你有读的权限,你就可以当它是透明的)。
写锁:写锁是排他的,也就是说一个写锁会阻塞其他的写锁和读锁,这是出于安全策略的考虑。

具体参见:悲观锁和乐观锁(资源在上面)

在实际的数据库系统中,每时每刻都在发生锁定。大多数时候,MySQL的内部管理都是透明的。

锁粒度

在这里插入图片描述

在这个问题上,我看到了一个非常接地气的比喻:( 出处

为什么要加锁?加锁是为了防止不同的线程访问同一共享资源造成混乱。
打个比方:人是不同的线程,卫生间是共享资源
你在上洗手间的时候肯定要把门锁上吧,这就是加锁,只要你在里面,这个卫生间就被锁了,只有你出来之后别人才能用。想象一下如果卫生间的门没有锁会是什么样?

什么是加锁粒度呢?所谓加锁粒度就是你要锁住的范围是多大。
比如你在家上卫生间,你只要锁住卫生间就可以了吧,不需要将整个家都锁起来不让家人进门吧,卫生间就是你的加锁粒度。

怎样才算合理的加锁粒度呢?
其实卫生间并不只是用来上厕所的,还可以洗澡,洗手。这里就涉及到优化加锁粒度的问题。
你在卫生间里洗澡,其实别人也可以同时去里面洗手,只要做到隔离起来就可以,如果马桶,浴缸,洗漱台都是隔开相对独立的,实际上卫生间可以同时给三个人使用,
当然三个人做的事儿不能一样。这样就细化了加锁粒度,你在洗澡的时候只要关上浴室的门,别人还是可以进去洗手的。如果当初设计卫生间的时候没有将不同的功能区域划分
隔离开,就不能实现卫生间资源的最大化使用。这就是设计架构的重要性。

表锁(table lock)

表锁是MySQL中最基本的锁策略,并且是开销最小的策略。它会锁定整张表,这是什么意思我就不多说了啊。
但是吧,放对了位置,表锁也可以有不错的体现,比方说服务器会为诸如ALTER TABLE之类的语句使用表锁而忽略存储引擎的锁机制。

行级锁

行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。

InnoDB等比较常用的存储引擎中都实现了行级锁。

页级锁

页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。


事务

MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你既需要删除人员的基本资料,也要删除和该人员相关的信息,这样,这些数据库操作语句就构成一个事务!

在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。
事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。
事务用来管理 insert,update,delete 语句
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

事务的四个特性(ACID)

原子性:一个事务是不可再分割的整体,要么都执行要么都不执行
一致性:一个事务的执行不能破坏数据库数据的完整性和一致性
隔离性:一个事务不受其它事务的干扰,多个事务是互相隔离的
持久性:一个事务一旦提交了,则永久的持久化到本地
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

就像锁粒度的升级会增加系统开销一样,这种事务处理过程中额外的安全性也会需要数据库系统做更多的额外工作。一个实现了ACID的数据库,相比没有实现ACID的数据库,通常会需要更多的CPU处理能力、更大的内存和更多的磁盘空间,这也正是MySQL的存储引擎可以发挥优势的地方,用户可以根据自身需要来选定存储引擎。

隔离级别

数据库为了压制丢失更新,提出了4类隔离级别[在application配置文件中声明]。
数据库现在的技术完全有办法避免丢失更新,但是这样做的代价是要付出锁的代价。一旦用了过多的锁,出现商品抢购这类功能的时候,很多线程都会被挂起和恢复,因为使用了锁之后,一个时刻只能有一个线程访问数据,这样当多个线程访问时,就会很慢,而且过多的锁会引发宕机,大部分线程被挂起,等待持有锁事务的完成。

隔离性其实比想象的要复杂,我们来看看:
(简单介绍几种)

READ UNCOMMITTED(读取未提交内容)

在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。

Read Committed(读取提交内容)

这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。

Repeatable Read(可重读)

这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。

Serializable(可串行化)

这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。


在这里插入图片描述

要是再深挖下去,那得是专门的研究员们做的事情了吧,比方说MySQL是如何保证可重复读的实现,比方说幻读是怎么被咔嚓掉的之类的。

后面我看看能不能查到些好的资料贴上来。


死锁

为什么这个死锁不放在上面“锁”的模块里面讲呢?木有事务,谈什么死锁。

死锁的基本概念我也不啰嗦了,为了解决死锁的问题,数据库系统实现了各种死锁检测和超时机制。InnoDB检测死锁的本事就不错,它会抓出死锁的循环依赖,并且抛出一个错误。
InnoDB目前处理死锁的方法:将持有最少行级排他锁的事务进行回滚。
锁的行为和存储引擎是密不可分的,同样的事务执行顺序,有的存储引擎就会死锁,有的就不会。。


事务日志

我觉得可以参考一下redis的日志: 全面分析redis持久化机制

吃出不过多赘述,其他篇关于MySQL的博客里已经讲得够多了。
如果觉得确实想要了解一下,这里倒是有一篇写得很全面的: 详细分析MySQL事务日志

想赶紧进入存储引擎的模块。


存储引擎

我先不说话,我先放张图,你品,你细品
在这里插入图片描述

InnoDB

我还是不说话,我放图:
在这里插入图片描述

InnoDB后台有多个不同的线程,用来负责不同的任务。

InnoDB 存储引擎是基于磁盘存储的,也就是说数据都是存储在磁盘上的,由于 CPU 速度和磁盘速度之间的鸿沟, InnoDB 引擎使用缓冲池技术来提高数据库的整体性能。缓冲池简单来说就是一块内存区域.在数据库中进行读取页的操作,首先将从磁盘读到的页存放在缓冲池中,下一次读取相同的页时,首先判断该页是不是在缓冲池中,若在,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。对于数据库中页的修改操作,首先修改在缓冲池中页,然后再以一定的频率刷新到磁盘,并不是每次页发生改变就刷新回磁盘。

在InnoDB中,缓冲池中的页大小默认为16KB。


InnoDB 给 MySQL 提供了具有事务(transaction)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)、多版本并发控制(multi-versioned concurrency control)的事务安全(transaction-safe (ACID compliant))型表。InnoDB 提供了行级锁(locking on row level),提供与 Oracle 类似的不加锁读取(non-locking read in SELECTs)。InnoDB锁定在行级并且也在SELECT语句提供一个Oracle风格一致的非锁定读。这些特色增加了多用户部署和性能。没有在InnoDB中扩大锁定的需要,因为在InnoDB中行级锁定适合非常小的空间。InnoDB也支持FOREIGN KEY强制。在SQL查询中,你可以自由地将InnoDB类型的表与其它MySQL的表的类型混合起来,甚至在同一个查询中也可以混合。这些特性均提高了多用户并发操作的性能表现。在InnoDB表中不需要扩大锁定(lock escalation),因为 InnoDB 的行级锁定(row level locks)适宜非常小的空间。InnoDB 是 MySQL 上第一个提供外键约束(FOREIGN KEY constraints)的表引擎。


InnoDB采用MVCC来支持高并发,并且实现了四个标准隔离级别。其默认隔离级别是REPEATABLE READ(可重复读),并且通过 间隙锁 策略防止幻读的出现,间隙锁使得InnoDB不仅仅对锁定查询涉及的行,还会对索引中的间隙进行锁定,以防止幻影行的插入。


不好搞,建议:

推荐:官方文档: InnoDB事务模型和锁
翻译版:https://www.cnblogs.com/EmptyRabbit/p/13688580.html

建议:更深入的了解一下InnoDB的MVCC架构。


其他的引擎嘛,其实我只喜欢InnoDB。。。
叶公好龙哈哈哈

其他存储引擎

MyISAM

特性:

  1. 加锁与并发:表锁,读写锁
  2. 修复:可手动或自动执行检查和修复工作,就是慢了点。
  3. 索引特性:支持全文索引
  4. 性能:设计简单,在某些情况下性能很好,嗯,某些情况下。

CSV引擎

CSV引擎可以将普通的CSV文件作为MySQL的表来处理,但这种表并不支持索引。CSV引擎可以在数据库运行的时候拷入或拷出文件,因此CSV作为一种数据交换的机制,非常有用。

Memory引擎

如果需要快速的访问数据,并且这些数据不会被修改,重启后丢失也没有关系,那么可以使用Memory表,因为所有数据都保存在内存中,不需要进行磁盘IO。
Memory表的结构在重启以后还会保留,但数据会丢失。


先到这里啦,我努力了,接下来就看各位的努力了,三连走起!!!

在这里插入图片描述

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/73991
 
356 次点击