1.1 MySQL逻辑架构
最上层的服务大多数是基于网络的客户端、服务端工具,比如连接处理、授权认证、安全等。
第二层是MySQL的核心功能,包括查询解析、分析、优化、缓存以及所有内置函数,所有跨存储引擎的功能都在这一层实现:存储过程、触发器、视图等。
第三层包括存储引擎,负责MySQL中数据的存储和提取,不会解析SQL。
MySQL处理请求主要分为:
- 连接管理与安全性: 每个客户端连接都在服务器进程中拥有一个线程,当客户端连接到MySQL服务器时,对其进行认证。
- 优化与执行: MySQL查询前会先检查查询缓存,否则会解析查询,并创建内部数据结构(解析树),然后对其进行各种优化,包括重新查询、决定表读取顺序、选择合适索引等,用户可以通过特殊关键字提示优化器,影响其决策过程。
1.2 并发控制
1.2.1 读写锁
处理并发读或写操作时,通过实现一个由两种类型的锁组成的锁系统来解决问题,分别时共享锁(读锁)和排他锁(写锁)。
隐示加锁
- InnoDB会根据隔离级别在需要的时候自动加锁
- 锁只有在执行commit或者rollback的时候才会释放,并且所有的锁都是在同一时刻被释放
显示加锁
- 共享锁:select...lock in share mode
- 排他锁:select...for update
1.2.2 锁粒度
提高共享资源并发性需要在锁的开销和数据安全性之间寻求平衡。每个存储引擎可以实现自己的锁策略和锁粒度。
表锁
是开销最小的策略,会锁定整张表,服务器会为alter table之类的语句使用表锁,而忽略存储引擎的锁机制。意向锁是一种特殊的表级锁,取得行的共享锁和排他锁之前,必须先获取意向共享锁和意向排他锁,用于判断表锁和行锁冲突。
行级锁
最大程度的支持并发处理,同时会带来最大的开销。
1.3 事务
事务是一组原子性的SQL,事务内的语句,要么全部执行成功,要么全部执行失败。事务的四大特性:
- 原子性(atomicity): 整个事务中的所有操作要么全部提交成功,要么全部失败回滚,不可能只执行其中的一部分操作,避免脏读。
- 一致性(consistency): 数据库总是从一个一致性状态转换到另外一个一致性状态,可以解决不可重复读问题。
- 隔离性(isolation): 一个事务所做的修改在最终提交之前,对其他事务是不可见的。
- 持久性(durability): 一旦事务提交,则其所做的修改会永久保存到数据库中,实际情况中不可能做到100%的持久性保证策略。
1.3.1 隔离级别
- 未提交读(READ UNCOMMITTED): 事务可以读取未提交的数据,造成脏读。实际应用中很少使用。
- 提交读(READ COMMITTED): 大多数数据库系统的默认隔离级别是提交读,一个事务开始和提交前,所做的修改对其他事务都是不可见的,可能会造成不可重复读。两个事务A和B,
- 可重复读(REPEATABLE READ): 保证在同一个事务中多次读取同样记录的结果是一致的,但是会造成幻读,幻读指当某个事务在读物某个范围内的记录时,另外一个事务又在该范围内插入或删除记录,当之前的事务再次读取该范围时,会产生幻行。可重复读是MySQL默认的隔离级别。
- 串行化(SERIALIZABLE): 最高的隔离级别,通过强制事务串行执行,会在读取的每一行数据上加锁,可能会导致大量的超时和锁争用问题。
1.3.2 死锁
死锁指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶行循环。为了解决死锁,数据库系统实现了各种死锁检测和死锁超时机制,InnoDB目前处理死锁,将持有最少行级排他锁的事务进行回滚。锁的行为和顺序是和存储引擎相关的。
1.3.3 事务日志
使用事务日志,存储引擎在修改表的数据时只需要修改其内存拷贝,再把修改行为记录到持久在硬盘上的事务日志中,而不用每次都将修改的数据本身持久到磁盘。事务日志采用的是追加的方式,顺序I/O。
1.3.4 MySQL中的事务
MySQL提供了两种事务型的存储引擎:InnoDB和NDB Cluster
- 自动提交(AUTOCOMMIT)
- 在事务中混合使用存储引擎:事务是由下层的存储引擎实现的,在同一个事务中,使用多种存储引擎是不可靠的。
- 隐式和显示锁定:InnoDB采用两阶段锁定协议,在事务执行过程中,随时可以锁定,只有在commit或rollback时才会释放,且所有锁在同一时刻被释放,根据隔离级别自动加锁。InnoDB也支持显示锁定,见1.2.1。
1.4 多版本并发控制(MVCC)
MySQL的大多数事务型存储引擎实现的都不是简单的行级锁,基于提升并发性能的考虑,一般同时实现了多版本并发控制(MVCC)。可以认为MVCC是行级锁的一个变种,实现了非阻塞的读操作,写操作也只锁定必要的行。
MVCC是通过保存数据在某个时间点的快照来实现的。根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。
不同的存储引擎的MVCC实现是不同的,典型的有乐观并发控制和悲观并发控制。InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的,这两个列,一个保存了行的创建时间,一个保存行的过期时间(或删除时间),存储的是系统版本号,没开始一个新的事务,系统版本号会自动递增。
MVCC只在可重复读和提交读两个隔离级别下工作。因为未提交读总是读取最新的数据行,而不是符合当前事务版本的数据行,而串行读则会对所有读取的行都加锁。
下面看一下可重复读隔离级别下,MVCC是如何操作的?
select
InnoDB会根据一下两个条件检查每行记录:
a.InnoDB只查找版本早于当前事务版本的数据行(即行的系统版本号小于或等于事务的系统版本号),保证事务读取的行,要么在事务开始前已经存在,要么是事务自身插入或修改过的。
b.行的删除版本要么未定义,要么大于当前事务版本号,确保事务读取到的行,在事务开始之前未被删除。
只有符合上述两个条件的记录,才能返回作为查询结果。
insert
InnoDB为新插入的每一行保存当前系统版本号作为行版本号。
delete
InnoDB为删除的每一行保存当前系统版本号作为行删除标识。
update
InnoDB为插入一行新纪录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。
保存这两个额外系统版本号,使得大多数读操作都可以不加锁,提高性能。不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作以及一些额外的维护工作。