不积跬步,无以至千里
和其他数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥好的作用,但同时会带来一定选择上的困难。MySQL并不完美,却足够灵活,能够适应高要求的环境。
本章概括的描述了MySQL的服务器架构、各种存储引擎之间的主要区别,以及这些区别的重要性。
1、MySQL逻辑架构
最上层的服务并不是MySQL独有的,大多数基于网络的客户端/服务端的工具或者服务都有类似的架构。比如连接处理、授权认证、安全等等。
第二层架构是MySQL是大多数核心服务功能,包括查询解析、分析、优化、以及所有的内置函数,所有跨存储引擎的功能都在这一层实现:存储过程、触发器、视图等。
第三层包含了存储引擎。存储引擎负责MySQL中数据的存储和提取。
1.1 连接管理与安全性
每个客户端连接都会在服务器进程中拥有一个线程,这个连接的查询只会在这个单独的线程中执行,该线程执行轮流在某个CPU核心或者CPU中运行。服务器会负责缓存线程,因此不需要为每一个创建的连接创建或者销毁线程。
当客户端连接到MySQL服务器时,服务器需要对其认证。认证基于用户名、原始主机信息和密码。
1.2 优化与执行
MySQL会解析查询,并创建内部数据结构,然后对其进行各种优化,包括重写查询、决定表的读写顺序、以及选择合适的索引等。用户可以通过特殊的关键提示优化器,影响他的决策过程。优化器并不关心使用的是什么存储引擎,但存储引擎对于优化查询是有影响的。优化器会请求存储引擎提供容器或某个具体操作的的开销信息,以及表数据的统计信息等。
2、并发控制
无论何时,只要有多个数据查询需要在同一时刻修改数据,都会有并发的问题。
2.1 读写锁
在处理并发读或者写时,可以通过一个由两种类型的锁组成的锁系统来解决这个问题。这两种类型的锁通常被称为共享锁(shared lock)或者排他锁(exclusive lock),也叫读锁(read lock)或者写锁(write lock)。
读锁是共享的,是互不阻塞的。多个用户在同一时刻读取相同的资源,而互不干扰。写锁是排他的,也就是说写锁会阻塞其他的写锁和读锁,这是出于安全策略的考虑,只有这样才能确保其他用户不会读取正在写入的同一资源。
2.2 锁粒度
每种MySQL存储引擎都可以实现自己的锁策略和锁粒度。在存储引擎中,锁管理是一个非常重要的决定。将锁粒度固定的某个级别,可以为某些特定场景提供更好的性能,但同时却会失去对另一场景的良好支持,好在MySQL支持多个存储引擎的架构,下面将介绍两种最重要的所策略。
- 表锁(table lock)
- 表锁是MySQL中最基本的锁策略,并且是开销最小的策略。它会锁定整张表。一个用户对表进行写操作前(插入,删除,更新等),需要先获得写锁,这会阻塞其他用户对表进行读写操作。只有在没有写锁时,其他用户才能获得读锁。读锁之间是互不阻塞的。
- 写锁也比读锁有更高的优先级,写锁请求可能会被插到读锁前面,而反之则不可。尽管存储引擎可以管理自己的锁,MySQL本身还是会使用各种有效的表锁来实现不同的目标。例如:服务器会为诸如alter table 之类的语句使用表锁,而忽略存储引擎的表机制。
- 行级锁(row lock)>
- 行级锁最大程度上支持并发操作(也带来了最大的锁开销),在innodb和xtradb,以及其他一些存储引擎中使用了行级锁。行级锁只在存储引擎中实现,为没有在MySQL服务层中实现。
3、事务
事务就是一组原子性的SQL查询,或者说一个独立的工作单元。如果数据库引擎能够成功的对数据库应用改组查询的全部语法,那么就执行该组查询。如果其中有一条语句因为崩溃或者其他原因无法执行,那么所有语句都不会执行。换句话说就是,事务内的语句要么全部执行成功,要么全部执行失败。
一个良好的事务处理系统必须具备ACID这些标准特性。ACID表示原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。
- 原子性(atomicity)
1
一个事物必须视作一个不可分割的工作单元,整个事务中的所有操作要么全部提交成功要么全部失败回滚。
- 一致性(consistency)
1
数据库总是从一个一致性状态转换到另一个一致性状态。中途语句执行失败,因为事务最终没有提交,所以事务中所作的修改也不会修改并保存到数据库中,确保了数据的一致性。
- 隔离性(isolation)
1
通常来说,一个事物所作的修改在最终提交之前,对其他的事务是不可见的。
- 持久性(durability)
1
一旦事务提交,则其所作的修改就会永久的保存在数据库中。此时即使系统崩溃,修改的数据也不会丢失。持久性是个模糊的概念,实际持久性也分很多不同的级别。
一个实现了ACID的数据库,相比没有实现的通常需要更大的CPU处理能力、更大的内存和更多的磁盘空间。用户可以根据业务是否需要事务处理,来选择合适的存储引擎。对于一些不需要的事务的查询类业务,选择一个非事务型的存储引擎,可以获得更高的性能。即使存储引擎不支持事务,也可以选择 lock tables 语句为应用提供一定的保护。
3.1 隔离级别
隔离性比想象中更为复杂,在SQL定义了四种隔离级别:
- READ UNCOMMITTED(未提交读)
- 在该级别中,事务的修改,即使没有提交,对其他事务也是可见的。事务可以读取未提交的数据,这就是脏读(Drity Read)。从性能上来看缺乏其他级别的许多好处,除非有必要的理由,一般不会使用。
- READ COMMITTED(提交读)
- 大多数数据库默认的都是该级别,但MySQL不是。一个事务从开始直到提交之前,所作的任何修改对其他事务都是不可见的。这个级别有时也叫做不可重复读,因为两次执行同样的查询,可能会查询到不一样的结果。
- REPEATABLE READ(可重复读)
- 此级别解决了脏读的问题。该级别保证了同一个事务中多次读取同样记录的结果是一致的。但是理论上,可重复读还是无法解决另外一个幻读(Phantom Read)的问题。所谓幻读指的是当某个事务在读取某个范围内的值时,另外一个事物又在该范围内修改了记录。这是读取的还是之前查询的记录,会出现幻行(Phantom Row)。InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决了这个问题。
- 可重复读是MySQL的默认事务隔离级别。
- SERIALIZABLE(可串行化)
- 此级别是最高的隔离级别。它通过强制事务串行执行,避免了幻读的问题。简单来说,该级别会在读取的每一行数据上加锁,所以可能导致大量的超时和锁争用的的问题。实际应用中很少用到,除非非常需要确保数据的一致性并且接受没有并发的情况,才考虑该级别。
隔离级别 | 脏读可能性 | 不可重复读可能性 | 幻读可能性 | 加锁读 |
---|---|---|---|---|
READ UNCOMMITTED | Yes | Yes | Yes | No |
READ COMMITTED | No | Yes | Yes | No |
REPEATABLE READ | No | No | Yes | No |
SERIALIZABLE | No | No | No | Yes |
3.2 死锁
死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。当多个事务试图以不同的顺序锁定资源时,就有可能会产生死锁。
为了解决这种问题,数据库系统实现了各种死锁检测和死锁超时机制。越复杂的系统,比如innodb存储引擎,越能检测到死锁的循环依赖,并立即返回一个错误,这种解决方式很有效,否则会导致出现非常慢的查询。
3.3 事务日志
事务日志可以帮助提高事务的效率。使用事务日志,存储引擎在修改表数据时只需要修改其内存拷贝,再把该修改行为记录到持久在次哦按上的事务日志中,而不是每次都将数据本身持久化到磁盘。如果修改的数据已经记录到事务日志并持久化,但数据本身还没有写回磁盘,此时系统崩溃,存储引擎在重启时能够自动恢复这部分修改的数据。具体的恢复方式则视存储引擎而定。
3.4 MYSQL中的事务
MySQL提供了两种事务型的存储引擎:InnoDB和Cluster,另外还有一些第三方的存储引擎也支持事务,比较知名的有XtraDB和PBXT。
MySQL默认采用自动提交模式。也就是说如果不是显式的开始一个事物,则每个查询都被当做一个事务执行提交操作。可以通过设置AUTOCOMMIT变量来启用或者禁用自动提交模式:
1 | mysql> SHOW VARIABLES LIKE 'AUTOCOMMIT'; |
1或者ON表示启用,0或者OFF表示禁用。
MySQL可以通过执行SET TRANSACTION ISOLATION LEVEL命令来设置隔离级别。新的隔离级别会在下一个事务开始的时候生效。可以在配置文件中设置整个数据库的隔离级别,也可以只改变当前会话的隔离级别:
1 | mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; |
MySQL能识别所有的四个ANSI隔离级别,InnoDB引擎也支持所有的隔离级别。
参考资料
- 本文作者: llc
- 本文链接: http://llc-dukes.top/2020/09/28/MYSQL-mysql架构/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!