一、逻辑架构
最上层的服务不是MySQL独有,多数基于网络的C/S的工具或者服务都有类似架构。
多数MySQL的核心服务功能都在第二层,包括查询解析、分析、优化、缓存以及所有的内置函数,所有跨存储引擎的功能都在这层实现:存储过程、触发器、视图等。
第三层包含了存储引擎。存储引擎负责MySQL中数据的存储和提取。服务器通过API与存储引擎进行通信。这些API屏蔽了不同存储引擎的差异。存储引擎不会去解析SQL,不同存储引擎之间也不会会相互通信,只是简单地响应上层服务器的请求。存储引擎是基于表,而不是数据库。
MySQL由以下几部分组成:
- 连接池组件
- 管理服务和工具组件
- SQL接口组件
- 查询分析器组件
- 优化器组件
- 缓存组件
- 插件式存储引擎
- 物理文件
1.连接管理与安全性
每个客户端连接都会在服务器进程中拥有一个线程,这个连接的查询只会在这个单独的线程中执行。服务器负责缓存线程,因此不需要为每个新建连接创建或销毁线程。
当客户端连接到MySQL服务器时,服务器需要对其进行认证。认证基于用户名、原始主机信息和密码。若使用了安全套接字(SSL)方式连接,还可以使用X.509证书认证。连接成功后,服务器会继续验证该客户端是否具有执行某个特定查询的权限。
2.优化与执行
MySQL会解析查询,并创建内部数据结构(解析树),然后对其进行各种优化,包括重写查询、决定表的读取顺序,以及选择合适的索引等。用户可以通过关键字提示(hint)优化器,影响其决策过程。也可以请求优化器解释(explain)优化过程的各个因素。
优化器不关心使用的存储引擎类型,但存储引擎对于优化查询有影响。优化器会请求存储引擎提供容量或者某个具体操作的开销信息等。
二.并发控制
1.读写锁
读锁(也叫共享锁,shared lock),互不阻塞。多个用户在同一时候刻读取同一资源互不干扰。
写锁(也叫排他锁,exclusive lock),一个写锁会阻塞其他写锁与读锁,这样才能确保在同一时刻,只有一个用户能执行写入并防止其他用户读取正在写入的同一资源。
写锁比读锁有更高的优先级,因此一个写锁请求可能会插入到读锁队列前面。
2.锁粒度
锁策略,就是在锁的开销与数据的安全性之间寻求平衡,这种平衡会影响性能。每种MySQL存储引擎可以实现自己的锁策略和锁粒度。
表锁
开销小,会锁定整张表。一个用户在对表进行写操作前,需要先获得写锁,因此会阻塞其他用户对该表的所有读写操作。
行级锁
可以最大程度的支持并发处理,锁开销也最大。行级锁只在存储引擎层实现,服务器完全不了解存储引擎中的锁实现。InnoDB,XtraDB以及其他一些存储引擎实现了行级锁。
三、事务
事务就是一组原子性的SQL查询。该组操作要么全部执行成功,要么全部执行失败。
ACID表示原子性(atomicity)、一致性(consistency)、隔离性(isolation)】持久性(durability)。一个运行良好的事务处理系统必须具备执行标准特征。
原子性(atomicity)
一个事务的所有操作要么全部提交成功,要么全部失败回滚,不可能只执行其中一部分操作。
一致性(consistency)
数据库总是从一个一致性状态转换到另一个一致性状态。
隔离性(isolation)
通常一个事务所做的修改在提交前,对其他事务不可见。
持久性(durability)
一旦事务提交,其所做的修改会永久保存到数据库中。持久性也分多个不同级别。
1.隔离级别
较低级别的隔离通常可以执行更高的并发,系统开销也低。
四种隔离级别:
READ UNCOMMITTED(未提交读)
事务中的修改,即使没有提交,对其他事务可见。事务读取未提交的数据,称为脏读(Dirty Read)。一般很少使用。
READ COMMITTED(提交读)
提交读满足隔离性的简单定义:一个事务开始到提交之前,所做修改对其他事务不可见。这个级别有时候也叫不可重复读(Nonrepeatable Read),因为两次执行相同查询,可能得到不同结果。
REPEATABLE READ(可重复读)
可重复读解决了脏读的问题。保证了同一事务中多次读取同样记录的结果一致。但是无法解决幻读(Phantom Read)问题。幻读,是指当某个事务在读取某个范围内记录时,另一个事务在该范围内插入了新纪录,当之前的事务再次读取该范围的记录时,产生幻行(Phantom Row)。
InnoDB和XtraDB通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决幻读问题。
SERIALIZEABLE(可串行化)
最高隔离级别。通过强制事务串行化执行,避免幻读问题。简单来说,可串行化会在读取的每行数据加锁,所以可能导致大量的超时和锁争用问题。实际中很少应用这个级别。
2.死锁
死锁是指两个或多个事务在同一资源上的相互占用,并请求锁定对方占用的资源,从而导致恶性循环。当多个事务视图以不同顺序锁定资源时,可能出现死锁。多个事务同时锁定同一资源时,也会产生死锁。
例如,两个事务都对同一表执行了第一条update语句,同时锁定了该行数据,两个事务尝试去执行第二条update时,发现该行被对方锁定,然后两个事务都等待对方释放锁,同时又持有对方需要的锁,陷入死循环。
为了解决这种问题,数据库系统实现了各种死锁检查和死锁超时机制。InnoDB目前处理死锁的方法是,将持有最少行级排他锁的事务进行回滚。
锁的行为和顺序和存储引擎有关。以同样顺序执行语句,有点存储引擎会死锁,有点不会。原因是有些是因为真正的数据冲突,有些是由于存储引擎实现方式导致。
多数情况下只需要重新执行因死锁回滚的事务即可。
3.事务日志
事务日志帮助提高事务效率,使用事务日志,存储引擎在修改表的数据时只需要修改其内存拷贝,再把该修改行为记录到持久在硬盘上的事务日志中,不用每次都将修改的数据本身持久到硬盘。
事务日志采用追加方式,因此写日志操作是磁盘上的一小块区域内的顺序I/O,不需要多个地方移动磁头,所以相对来说快得多。
事务日志持久以后,内存中被修改的数据在后台慢慢刷回磁盘。通常称之为预写式日志(Write-Ahead Logging),修改数据需要写两次磁盘)。
4.MySQL中的事务
MySQL提供两种事务型存储引擎:InnoDB和NDB Cluster。还有其他一些第三方存储引擎。
自动提交(AUTOCOMMIT)
MySQL默认自动提交。即若不显示开始一个事务,则每个查询都被当作一个事务执行提交操作。设置AUTOCOMMIT变量启用或禁用自动提交模式:
show variables like 'AUTOCOMMIT';
....
set AUTOCOMMIT =1;
禁用时,所有查询都在一个事务中,直到执行commit或者rollback。有一些命令在执行之前会强制commit提交当前事务,具体检查对于官方文档。
MySQL可以通过执行SET TRANSACTION LEVEL设置隔离级别。新的隔离级别会在下一个事务开始时生效。可以在配置文件中设置整个数据库的隔离级别,也可以只改变当前会话的隔离级别:
set session transaction isolation level read committed;
在事务中混合使用存储引擎
服务器层不管理事务,事务是由存储引擎实现。所以,在同一事务中,使用多种存储引擎是不可靠的。
如果在事务中混合使用了事务型和非事务型表,正常提交不会有问题,回滚操作会失败,因为非事务型表的变更无法撤销,会导致数据库处于不一致状态。
隐式和显式锁定
InnoDB采用两阶段锁定协议(two-phase locking protocol)。在事务执行过程中,随时可以锁定。锁只有在执行commit或rollback时才释放,并且所有锁在同一时刻释放。
前面描述的是隐式锁定,InnoDB会根据隔离级别在需要的时候自动加锁。
InnoDB支持通过语句显式锁定:
select ... lock in share mode
select ... for update
四、多版本并发控制
mvcc的实现,是通过保存数据在某个时间点的快照来实现的。典型的有乐观并发控制与悲观并发控制。
InnoDB的MVCC,是通过在每行记录后面保存两个隐藏列实现。一列保存行的创建时间,一列保存行的删除时间。列存储的值是系统版本号(system version number)。每开始一个新事务,系统版本号自动递增。事务开始时的系统版本号会作为事务的版本号。
在REPEATABLE READ级别下,MVCC具体操作:
SELECT:
1.InnoDB只查找行的系统版本号小于或等于事务的系统版本号的数据行,这样确保事务所读取的行,在事务开始时前已存在或事务自身插入或修改过。
2.行的删除版本要么未定义,要么大于当前事务版本号。确保事务读取到的行,在事务开始前未被删除。
INSERT:
为新插入的每行保存当前系统版本号为行版本号。
DELETE:
为删除的每行保存当前系统版本号为行删除版本号。
UPDATE:
插入一行新纪录,保存当前系统版本号为行版本号,保存当前系统版本号到原来的行作为行的删除版本号。(可以理解为先delete,后insert。)
这种方式,使大多数读操作不用加锁。缺点是需要额外空间。
MVCC只在可重复读与提交读两个隔离级别下工作。SERIALIZABLE会对所有读取行加锁。