高性能MySQL第三版-第一章MySQL架构与历史
MySQL架构最与众不同的是将查询处理及其他系统任务和数据的存取/提取相分离。
这种处理和存储分离的设计可以在使用时根据性能、特性,以及其他需求来选择数据存储的方式。
1.1逻辑架构
第一层:连接/线程处理。用于连接处理、授权认证、安全等。
第二层:查询缓存、解析器、优化器。大多数核心功能都在这一层,包括查询解析、分析、优化、缓存以及所有内内置函数,所有跨存储引擎的功能(存储过程、触发器、视图)。
第三层:存储引擎。负责数据存储和提取。服务器通过 API 与存储引擎进行通信, 这些 API 屏蔽了存储引擎之间的差异,使得这些差异对上层的查询过程透明。 存储引擎不会去解析SQL,那是第二层干的事,不同存储引擎也不会相互通信,只是简单地响应上层服务器的请求。
问题:到底什么是存储引擎?
假设你也想自己写一个存储引擎,你需要定义好一个表的数据用什么格式来存储,用什么样的索引来访问这些数据, 然后你需要写一个类,这个类需要实现一些约定好的接口函数(创建表、打开表、写入数据等)。
在这些方法的实现当中,你需要处理好如何创建表,如何插入数据,如何开始事务,提交事务这些功能。
你写的这个类会被MySQL 所装载使用,然后在合适的时候调用相关方法,于是你这个类就变成了存储引擎!
1.1.1 连接管理与安全性
每个客户端连接在服务器进程中都有自己的线程。连接的查询只会在该单独的线程中执行,该线程只能轮流在某个CPU核心或CPU上运行。服务器会缓存线程,因此无需为每个新连接创建和销毁线程。(MySQL 5.5 and newer versions support an API that can accept thread-pooling plugins, so a small pool of threads can service many connections.)
客户端(应用)连接到 MySQL 服务器时,服务器需要对其进行认证(用户名、主机信息、密码或认证证书等)。
客户端连接成功,也就是认证成功,服务器会继续验证客户端的相关权限。
问题:每个客户端连接都会占用一个线程,那客户端没有查询发出的时候,这个线程能不能被别的客户端连接使用呢?
读书群里提出问题讨论。问题重点在客户端没有查询发出,说的不明确,下面分几种情况来说。
- 是连接阶段顺利,用户线程进入命令阶段,而命令阶段又没有SQL指令。
这时候就要看wait_timeout
和interactive_timeout
参数是多少了,默认28800s,也就是8h。(The number of seconds the server waits for activity on an interactive connection before closing it)
在连接没有被关闭之前,这个线程不能被其他客户端连接使用。(连接关闭有两种,一种超过MySQL的wait_timeout,一种是客户端发送一个 COM_QUIT命令断开连接)
-
连接阶段不顺利
那就是和这个参数有关了
connect_timeout
,默认10s,超过了那这个线程就可以被别的客户端连接使用。(The number of seconds that the mysqld server waits for a connect packet before responding withBad handshake
. The default value is 10 seconds.)
1.1.2 优化与执行
优化器并不关心表使用的事什么存储引擎,但存储引擎对于优化查询是有影响的。优化器请求存储引擎提供容量或者某个具体操作的开销信息。例如,某些存储引擎的索引,可能对一些特定的查询有优化。
1.2 并发控制
只要有多个查询需要在同一时刻修改数据,都会产生并发控制的问题。本章目的讨论 MySQL 在两个层面的并发控制:服务器层与存储引擎层。
服务器层:锁定表(lock tables)和解锁表(unlock tables)的语句。服务器层会为诸如ALTER TABLE
之类的语句使用表锁,而忽略存储引擎的锁机制。
存储引擎层:存储引擎都可以实现自己的锁策略和锁粒度,不同存储引擎实现锁机制方式不同。
并发控制一般都会涉及到事务。MySQL服务层不管理事务,事务是有存储引擎实现的。同个事务中避免使用多种存储引擎。
InnoDB的隐式锁定会根据隔离级别在需要的时候自动加锁。显式锁定,这些语句应该尽量避免使用:
SELECT ... LOCK IN SHARE MODE
、 SELECT ... FOR UPDATE
不属于SQL规范,是在存储引擎层做的并发控制。
锁粒度
MySQL 中提供了两种锁粒度:表级锁、行级锁。
尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。
但是加锁需要消耗资源,锁的各种操作(包括获取锁、释放锁、以及检查锁状态)都会增加系统开销。因此锁粒度越小,系统开销就越大。
锁策略,就是在锁的开销和数据安全性之前寻求平衡,这种平衡当然也会影响性能。
锁类型
详细参考别人笔记:数据库系统原理-封锁
1.读写锁
- 互斥锁(Exclusive),简写为 X 锁,又称写锁。
- 共享锁(Shared),简写为 S 锁,又称读锁。
2.意向锁
意向锁在原来的 X/S 锁之上引入了 IX/IS,IX/IS 都是表锁,用来表示一个事务想要在表中的某个数据行上加 X 锁或 S 锁。
1.3 事务
ACID概念、ACID之间的关系、不可重复读、幻读图解见数据库系统原理分享
1.3.1隔离级别
隔离性(Isolation)的细分
-
Read uncommited(未提交读)
事务可读取未提交的数据,称为脏读。性能上不会比其他级别好太多,实际一般很少用。
-
Read commited(提交读)
事务从开始到提交前的操作,对其他事务是不可见的。大多数据库默认是这个隔离级别。
-
Repeatable read(可重复读)
在同一个事务中多次读取同样的数据记录结果是一致的。MySQL默认的事务隔离级别。
-
Serializable(可串行化)
强制事务串行执行,在读取的每一行数据上都加了锁。
不可重复读。两次执行同样的查询,可能会得到不同的结果。
幻读。A事务读取某个范围内的记录(比如x表的count),B事务又向x表新增一条数据,A事务再次读取结果和第一次不同。
1.3.2 死锁
两个或者多个事务在同一资源上相互作用,并请求锁定对方占用的资源,从而导致恶性循环的现象。
死锁的产生有双重原因:1、真正的数据冲突,比较难避免。2、存储引擎的实现方式造成的。(不同存储引擎,锁的执行顺序不同。所以相同语句执行,有些存储引擎会死锁,有的不会。)
部分或者完全回滚其中一个事务,才能打破死锁。
1.3.3 事务日志
预写式日志(Write-Ahead Logging),修改数据需要些两次磁盘。
存储引擎在修改表数据时只需修改其内存拷贝,再把修改的行为持久到硬盘的事务日志中,不用每次修改数据本身持久化到硬盘上。
事务日志持久后,内存中被修改的数据在后头可以慢慢刷回磁盘。
1.3.4 MySQL 中的事务
MySQL 中默认自动提交(AUTOCOMMIT)模式。如果不是显示的开启一个事务,则每个查询都被当做一个事务执行提交操作。
修改 AUTOCOMMIT 变量对非事务型的表不会有任何影响。
1.4 多版本并发控制
读写不互相等待,能极大地提高数据库的并发能力。
并发情况下数据一致性问题,刘大文章数据库村的旺财和小强 有关mvcc的例子
产生并发不一致性问题的主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。并发控制可以通过封锁来实现,但是封锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。
这里面也有多版本并发控制的例子数据库系统原理