【MySQL原理系列】- MySQL基础

【MySQL原理系列】- MySQL基础


MySQL最重要、最与众不同的特性是其存储引擎架构,它将查询处理及其他系统任务和数据的存储/提取相分离。这种处理和存储分离的设计可以在使用时根据性能、特性,以及其他需求来选择数据存储的方式。

本章概要地描述了MySQL的服务器架构、各种存储引擎之间的主要区别

一、MySQL逻辑架构

第一层:连接/线程服务,该服务并不是MySQL所独有的,大多数基于网络的客户端/服务器都有类似的架构,比如连接处理、授权认证、安全等

第二层:查询、缓存、解析、优化,MySQL的核心服务功能,包括所有的内置函数(如日期、时间、数学和加密函数),以及跨存储引擎的功能(如存储过程、触发器、视图等)

第三层:存储引擎,负责MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信,不同存储引擎之间不会相互通信,只能响应上层服务器的请求

在这里插入图片描述

1. 连接管理与安全性

每个客户端与服务器的连接都会在服务器进程中拥有一个线程

  • 该连接的查询只会在这个线程中执行,该线程只能轮流在某个CPU或者CPU中运行
  • 服务器会负责缓存线程,因此不需要为每一个新建的连接创建或者销毁线程

当客户端连接到MySQL服务器时,服务器需要对其进行认证

  • 认证基于用户名、原始主机信息和密码
  • 如果使用了安全套接字SSL的方式连接,还可以使用X.509证书认证
  • 一旦连接成功,服务器会验证该客户端是否有执行特定查询的权限

2. 优化与执行

MySQL会解析查询,并创建解析树,对其进行优化,包括重写查询、决定表的读取顺序,以及选择合适的索引等

  • 用户可以通过特殊的关键字hint,提示优化器,影响它的决策过程
  • 也可以请求优化器explain优化过程的各个因素,使用户可以知道服务器是如何进行优化决策的,并提供一个参考基准,便于用户重构查询和schema、修改相关配置,使应用尽可能高效运行

优化器并不关心表使用的是什么存储引擎,但存储引擎对于优化查询是有影响的

  • 优化器会请求存储引擎提供容量或某个具体操作的开销信息,以及表数据的统计信息等
  • 某些存储引擎的某种索引,可能对一些特定的查询有优化

对于SELECT,在解析查询前,服务器会先检查查询缓存,如果能在其中找到对应的查询,服务器就不必再执行查询解析、优化和执行的整个过程,而是直接返回查询缓存中的结果集

二、并发控制

并发读写问题

1. 读写锁

处理并发读或写时,通过实现两种类型的锁来解决问题

  • 共享锁和排他锁,或叫读锁和写锁

  • 读锁是共享的,相互不阻塞,同一时刻多个用户并发读不会有问题,所以多个客户在同一时刻可以同时读取同一个资源,而互不干扰

  • 写锁则是排他的,写锁会阻塞其他的写锁和读锁,确保在给定的时间里,只有一个用户能执行写入,并防止其他用户读取正在被改的资源

2. 锁粒度

在这里插入图片描述

锁策略,就是在锁的开销和数据安全性之间寻求平衡

  • 更精确的加锁可以提高共享资源并发性

  • 但加锁需消耗资源,锁的各种操作,包括获得锁、检查是否解锁、释放锁等,都会增加系统开销,如果系统花费大量的时间来管理锁,而不是存取数据,系统的性能会受到影响

  • 将锁粒度固定在某个级别,可以为某些特定的应用场景提供更好的性能,但同时会失去对另外一些应用场景的良好支持

  • 每种MySQL存储引擎都可以实现自己的锁策略和锁粒度

表锁(table lock)

表锁是MySQL中最基本,开销最小的锁策略,会锁定整张表

  • 在特定的场景中,表锁也可能有良好的性能。例如,READ LOCAL表锁支持某些类型的并发写操作

  • 写锁也比读锁有更高的优先级,一个写锁请求可能会被插入到读锁队列的前面

  • 尽管存储引擎可以管理自己的锁,MySQL本身还是会使用各种有效的表锁来实现不同的目的。例如,服务器会为诸如ALTER TABLE 之类的语句使用表锁,而忽略存储引擎的锁机制

行级锁(row lock)

行级锁可以最大程度地支持并发处理,同时也带来了最大的锁开销

行级锁只在存储引擎层实现,而MySQL服务器层没有实现

三、事务

事务是一组原子性的SQL查询,或者说一个独立的工作单元

  • 如果数据库引擎能够成功地对数据库应用该组查询的全部语句,那么就执行该组查询;如果其中有任何一条语句因为崩溃或其他原因无法执行,那么所有的语句都不会执行

  • 事务内的语句,要么全部执行成功,要么全部执行失败

  • 可以用START TRANSACTION语句开始一个事务,然后要么使用COMMIT 提交事务将修改的数据持久保留,要么使用ROLLBACK 撤销所有的修改

事务处理过程中可能出现问题 :服务器崩溃,另一进程修改数据

事务的ACID特性在应用逻辑中很难,一个兼容ACID的数据库系统,需要做很多复杂但可能用户并没有觉察到的工作,才能确保ACID的实现,就像锁粒度的升级会增加系统开销一样,事务处理过程中额外的安全性,需要数据库系统额外工作、额外资源,这也正是MySQL的存储引擎架构可以发挥优势的地方

  • 用户可以根据业务是否需要事务处理,来选择合适的存储引擎
  • 对于一些不需要事务的查询类应用,选择一个非事务型的存储引擎,可以获得更高的性能
  • 即使存储引擎不支持事务,也可以通过LOCK TABLES 语句为应用提供一定程度的保护

一个运行良好的事务处理系统,必须具备ACID特性

  • 原子性(atomicity)

    一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部成功,要么全部失败回滚。不可能只执行其中一部分操作

  • 一致性(consistency)

    数据库总是从一个一致性的状态转换到另外一个一致性的状态

    • 一致性指事务在开始和结束时,数据库中的数据必须符合所有的约束条件(如主键约束、外键约束、数据完整性约束、业务逻辑约束等),不违反定义的规则和约束条件
  • 隔离性(isolation)

    一个事务所做的修改在最终提交以前,对其他事务是不可见的

  • 持久性(durability)

    一旦事务提交,则其所做的修改就会永久保存到数据库中。即使系统崩溃,修改的数据也不会丢失

    • 实际上持久性也分很多不同的级别,有些持久性策略能够提供非常强的安全保障,有些则未必
    • 且不可能有100%持久性的策略

1. 隔离级别

在SQL标准中定义了四种隔离级别

在这里插入图片描述

较低级别的隔离通常可以执行更高的并发,系统的开销也更低

  • READ UNCOMMITTED(未提交读)

    • 是最低的隔离级别

    • 事务中的修改,即使没提交,对其他事务也都可见

    • 事务可以读取未提交的数据,这也被称为脏读

    • 会导致很多问题,性能上不会比其他的级别好太多,但却缺乏其他级别的很多好处,实际很少用

  • READ COMMITTED(提交读)

    • 满足隔离性,一个事务从开始直到提交之前,所做的任何修改对其他事务都不可见
    • 是大多数数据库系统的默认隔离级别
    • 也叫做不可重复读,因为两次执行同样的查询,可能因为中途数据被修改而得到不一样的结果
  • REPEATABLE READ(可重复读)

    • 解决了脏读的问题,该级别保证了在同一个事务中多次读取同样记录的结果是一致的
    • 是MySQL的默认事务隔离级别
    • 仍无法解决幻读问题,即当某个事务在读取某个范围内的记录时,另外一个事务在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行
      • InnoDB和XtraDB存储引擎通过多版本并发控制MVCC解决了幻读的问题
  • SERIALIZABLE(可串行化)

    • 是最高的隔离级别
    • 强制事务串行执行,在读取的每一行数据上都加锁,避免了幻读的问题
    • 可能导致大量的超时和锁争用的问题,实际很少用,只有在非常需要确保数据的一致性且能接受没有并发的情况下用该级别

2. 死锁

死锁的产生有双重原因:

  • 有些是因为真正的数据冲突,这种情况通常很难避免。如果两个事务都等待对方释放锁,同时又持有对方需要的锁,则陷入死循环,多发生在多个事务试图以不同的顺序锁定资源时
  • 有些则完全是由于存储引擎的实现方式导致的。锁的行为和顺序是和存储引擎相关的,以同样的顺序执行语句,有些存储引擎会产生死锁,有些不会

为了解决死锁问题,数据库系统实现了各种死锁检测和死锁超时机制:

  • 越复杂的系统,比如InnoDB存储引擎,越能检测到死锁的循环依赖,并立即返回一个错误
    • 这种解决方式很有效,否则死锁会导致出现非常慢的查询
  • 还有一种解决方式,就是当查询的时间达到锁等待超时的设定后放弃锁请求,这种方式通常不太好
  • 死锁发生以后,只有部分或者完全回滚其中一个事务,才能打破死锁
    • InnoDB处理死锁的方法是,将持有行级排他锁最少的事务回滚

3. 事务日志

事务日志可以帮助提高事务的效率

使用事务日志,存储引擎在修改表的数据时只需要修改其内存中的拷贝,再把该修改行为记录到持久在硬盘上的事务日志中,而不用每次都将修改的数据本身持久到磁盘,事务日志持久以后,内存中被修改的数据在后台可以慢慢地刷回到磁盘

  • 目前大多数存储引擎都是这样实现的,称为预写式日志,修改数据需要写两次磁盘

  • 如果数据的修改已经记录到事务日志并持久化,但数据本身还没有写回磁盘,此时系统崩溃,存储引擎在重启时能自动恢复这部分数据

  • 事务日志采用追加的方式,写日志的操作是磁盘上一小块区域内的顺序I/O,而不像随机I/O需要在磁盘的多个地方移动磁头,所以采用事务日志的方式相对来说要快得多

4. MySQL中的事务

MySQL提供了两种事务型的存储引擎:InnoDB、NDB Cluster

还有一些第三方存储引擎也支持事务,如XtraDB、PBXT

4.1 自动提交(AUTOCOMMIT)

MySQL默认采用自动提交模式

  • 可以通过设置AUTOCOMMIT变量来启用或禁用自动提交,1 或ON 表示启用,0 或OFF 表示禁用
  • 默认是如果不是显式地(START TRANSACTION;)开始一个事务,则每个查询都会被当作一个事务执行提交
  • AUTOCOMMIT=0 时,所有的查询都是在一个事务中,直到显式地执行提交或回滚,该事务结束,同时又开始了另一个新事务
  • 修改AUTOCOMMIT 对非事务型的表,如MyISAM或内存表,不会有任何影响,这类表没有COMMIT 或者ROLLBACK 的概念,相当于一直处于AUTOCOMMIT 启用的模式
  • 还有一些命令,在执行前会强制执行COMMIT 提交当前的活动事务,如ALTER TABLE、LOCK TABLES
4.2 设置隔离级别

MySQL可以通过执行SET TRANSACTION ISOLATION LEVEL 命令来设置隔离级别。新的隔离级别会在下一个事务开始时生效:

  • mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

  • MySQL能够识别所有的4个ANSI隔离级别,InnoDB引擎也支持所有的隔离级别

4.3 在事务中混合使用存储引擎

MySQL服务器层不管理事务,事务由下层的存储引擎实现

所以在同一个事务中,使用多种存储引擎是不可靠的

如果在事务中混合使用了事务型和非事务型的表(如InnoDB和MyISAM表)

  • 正常提交的情况下不会有什么问题

  • 但如果该事务需要回滚,非事务型的表上的变更就无法撤销,这会导致数据库处于不一致的状态,这种情况很难修复,事务的最终结果将无法确定

4.4 隐式和显式锁定

隐式锁定:

  • InnoDB采用的是两阶段锁定协议,InnoDB会根据隔离级别在需要的时候自动加锁

  • 数据库的事务机制和锁机制通常由数据库引擎自动管理

  • 事务执行过程中,随时都可以执行锁定,只有在执行COMMIT或ROLLBACK 时才能释放锁,并且所有锁是在同一时刻被释放

显式锁定:

  • InnoDB也支持通过特定的语句显式锁定

    • SELECT … LOCK IN SHARE MODE
  • MySQL也支持LOCK TABLES 和UNLOCK TABLES 语句,这是在服务器层实现的,和存储引擎无关

  • 建议不管使用的什么存储引擎,除非事务中禁用了AUTOCOMMIT,此时可以使用LOCK TABLES,除此之外,其他任何时候都别显式地执行LOCK TABLES

四、多版本并发控制

基于提升并发性能的考虑,大多数事务型存储引擎一般都实现了多版本并发控制MVCC

  • MVCC在很多情况下避免了加锁操作,开销更低

  • MVCC实现了非阻塞的读操作,写操作也只锁定必要的行

  • MVCC是通过保存数据在某个时间点的快照来实现的

  • 不同存储引擎的MVCC实现是不同的,典型的有乐观并发控制和悲观并发控制

  • InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的

    • 一个列保存了行的创建时间,一个列保存行的过期时间(或删除时间)
    • 时间是指系统版本号,每开始一个新的事务,系统版本号都会自动递增
    • 事务开始时刻的系统版本号会作为事务的版本号

在REPEATABLE READ 隔离级别下,MVCC的具体操作:

  • SELECT

    • InnoDB会根据以下两个条件检查每行记录:
      • InnoDB只查找版本早于当前事务版本的数据行(行的系统版本号小于等于事务版本号)
      • 行的删除版本要么未定义,要么大于当前事务版本号
      • 只有符合上述两个条件的记录,才能返回作为查询结果
  • INSERT

    • InnoDB为新插入的每一行保存当前系统版本号作为行版本号
  • DELETE

    • InnoDB为删除的每一行保存当前系统版本号作为行删除版本号
  • UPDATE

    • InnoDB为插入一行新记录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识

保存这两个额外系统版本号,使大多数读操作都可以不用加锁,不足之处是额外存储,需做更多的行检查,以及额外的维护工作

MVCC只在REPEATABLE READ 和READ COMMITTED 两个隔离级别下工作,其他两个隔离级别都和MVCC不兼容

  • 因为READ UNCOMMITTED总是读取最新的数据行,而不是符合当前事务版本的数据行
  • 而SERIALIZABLE 则会对所有读取的行都加锁
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值