第一章 Mysql架构与历史

MySQL的关键特性是: 它的存储引擎架构, 这种架构的设计将查询处理(Query Processing)及其他系统任务(Server Task)和数据的存储/提取相分离, 即处理和存储分离.

1.1 MySQL逻辑架构

image.png

最上层: 连接处理, 授权认证, 安全等.

第二层: 查询解析, 分析, 优化, 缓存及其内置函数; 所有跨存储引擎的功能: 存储过程, 触发器, 视图.

第三层: 存储引擎, 负责数据的存储和提取. 服务器通过API和存储引擎进行通信.

连接管理和安全性

每个客户端连接都会在服务器进程中用拥有一个线程, 这个连接的查询只会在这个单独的线程中执行, 服务器负责线程池管理.

连接时会做认证, 一旦连接成功, 服务器会继续验证该客户端是否有执行某个特点查询的权限.

优化与执行

MySQL 会将一条查询语句解析成内部数据结构——解析树,然后对其进行优化:重写查询、决定表的读取顺序、选择合适的索引等。用户可通过hint可以指导优化.

优化器不关心存储引擎的类型, 但存储引擎对优化查询有影响, 优化器会请求存储引擎提供容量或某个具体操作的开销信息或表数据的统计信息.

对于 Select 语句,会用整个查询语句作为 Key,去缓存中查询处理结果.

1.2 并发控制

只要有多个查询需要在同一时刻修改数据, 就会产生并发控制的问题, 本章讨论2个层面的并发控制: 服务器层与存储引擎层.

读写锁

共享锁S(读锁)
即读取操作时创建的锁。其他用户可以并发的读取数据。

若事务T对数据A加上共享锁,则事务T只能读A; 其他事务也只能对数据A加共享锁,而不能加排它锁,直到事务T释放A上的S锁。
这就保证了其他事务可以读A,但是在事务T释放A上的共享锁之前,不能对A做任何修改。

排它锁E(写锁)

若事务T对数据对象A加上排它锁,则只允许T读取和修改数据A,其他任何事务都不能再对A加任何类型的锁,直到事务T释放X锁.

MySQL中时时刻刻都在加锁.

锁粒度

锁的粒度会决定系统的并发度。一种提高共享资源的并发性的方法就是让锁更具有选择性,即只锁定需要修改的部分数据,而不是所有. 但粒度太小加锁过于频繁也消耗资源, 因此需要一种平衡.

表锁

锁定整张表, 是开销最小的策略. 用户写操作时获得写锁, 会阻塞其他用户对该表的所有操作. 没有写锁时, 其他读取的用户才能获得读锁, 读锁间互不阻塞.

尽管存储引擎可以管理自己的锁,但在执行诸如 Alter table 之类的语句时,服务器会忽略存储引擎的锁,而直接用表锁.

行级锁

行级锁可以最大程度地支持并发处理,同时也带来了最大的锁开销. 行级锁只在存储引擎中实现,服务器层没有实现, 服务器层完全不了解存储引擎中的行级锁实现.

事务

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

事务要满足ACID测试:
原子性:一个事务中的所有操作,要么全部提交成功,要么全部失败回滚。
一致性:数据库总是从一个一致性的状态转换到另一个一致性的状态。
隔离性:通常来说,一个事务所做的修改在提交之前,对其他事务是不可见的。之所以说通常,因为这和事务的隔离级别是相关的。
持久性:一旦事务提交,则其所做的修改会永久保存到数据库中。

事务是在存储引擎层做的, 一个实现了ACID的数据库, 开销也更大. 因此用户可以选择不同的存储引擎来决定是否需要使用事务, 即使是存储引擎不支持事务, 也可以通过LOCK TABLES(Mysql层)语句来为应用提供一定的保护.

隔离级别

SQL标准中定义了4种隔离级别, 每种都规定了一个事务中所做的修改, 哪些在事务内和事务间是可见的, 哪些是不可见的. 较低级别的隔离通常开销低, 并发高.

未提交读(READ UNCOMMITTED):一个事务可以读取其他事务未提交的修改,这也被称为“脏读”。实际中一般很少使用。

提交读(READ COMMITTED):一个事务从开始到提交之前,所做的任何修改对其他事务是不可见的。这是大多数数据库系统的默认的事务隔离级别。这个级别也被称为不可重复读,因为在一个事务中,执行两次相同的查询,可能会得到不一样的结果。例如,第二次执行时,可以读到其他事务的提交。

可重复读(REPEATABLE READ):同一个事务中,多次读取同样记录的结果是一致的。这是 MySQL 默认的事务隔离级别。另外,InnoDB 通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决了幻读的问题。

可串行化(SERIALIZABLE):强制事务串行执行,解决了幻读的问题。由于需要在读取的每一行数据上都加锁,因此存在大量的超时和锁争用的问题。在实际中很少使用,只有在非常需要确保数据的一致性且可以接受没有并发的情况下,才考虑使用。

image.png
  • 注: 上图中有不准确的地方, MySQL可以保证可重复读时不会发生幻读.

死锁

死锁是指两个或多个事务在同一个资源上相互占用,并请求锁定对方占用的资源。

死锁发生后,只有部分或完全回滚其中一个事务,才能打破死锁。InnoDB能检测到死锁的循环依赖, 将持有最少行级排他锁的事务进行回滚. 应用应该考虑如何处理死锁: 如重新执行因死锁回滚的事务.

事务日志

存储引擎在修改数据表时,只需要先修改表的内存拷贝,再通过事务日志将所做的修改顺序追加到磁盘上的事务日志中,而对数据表的修改可以在后台慢慢地刷回磁盘,这样提高了事务的执行效率。这种实现方式称为预写式日志(Write-Ahead Logging),修改数据需要写两次磁盘(一次是事务日志, 一次是表的数据)。

若事务日志已经持久化, 而数据还未写回磁盘, 此时系统崩溃, 存储引擎重启是就能自动回复这部分修改的数据.

MySQL中的事务

Mysql提供了两种事务性的存储引擎:InnoDB 和 NDB Cluster

  1. 自动提交(AUTOCOMMIT)
    Mysql默认采用自动提交(AUTOCOMMIT)的模式,如果不明确开始一个事务,则每个查询都被当做一个事务执行提交操作.
show variables like 'AUTOCOMMIT';

MySQL的默认隔离级别是: REPEATABLE READ
修改隔离级别:

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
  1. 在事务中混合使用存储引擎
    Mysql服务器不管理事务,事务是由下层存储引擎实现的。在同一事务中, 使用多种存储引擎是不可靠的.

  2. 隐式和显示锁定
    InnoDB 采用的是两阶段锁定协议(two-phase locking protocol),随时可执行锁定,在执行commit 后者rollback时会释放. MySQL的4种隔离级别在需要时会自动加锁, 这叫隐式加锁.
    显示加锁(经常被滥用):

SELECT ... LOCK IN SHARE MODE

SELECT ... FOR UPDATE
  1. MVCC
    基于提升并发性能的考虑,它是行级锁的一个变种,但是它在很多的情况避免了加锁的操作,因此开销更低. 虽然不同数据库实现机制有所不同,但大都实现了非阻塞的读操作,写操作也只锁定必要的行.
    MVCC 是通过保存数据在某个时间点的快照来实现的。不管事务执行多长时间,在一个事务中,它看到的数据都是一致的。而事务开始的时间不同,不同事务对同一张表,同一时刻看到的数据可能是不一样的。
    InnoDB 实现的 MVCC 是通过在每行记录后面保存两个隐藏的列来实现。一个保存了行的创建版本号(系统版本号,system version number),一个保存了行的过期(或删除)版本号。每开始一个事务,系统版本号会自动递增。事务开始时的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号作比较。在REPEATABLE READ隔离级别下,MVCC 的实现描述如下:
语句 方法
select a. 只查找版本早于(小于或等于)当前事务版本的数据行, b. 行的删除版本未定义或者大于当前事务版本号. a,b条件同时满足的记录, 才作为查询结果返回.
insert 为新插入的每一行保存当前版本号作为行版本号.
delete 为删除的每一行保存当前版本号作为删除标识.
update 插入新的一行,保存当前系统版本号作为版本号;同时将当前系统版本号保存到原来的行作为删除标识.

MVCC 只在可重复读(REPEATABLE READ)、提交读(READ COMMITTED)两个隔离级别下工作。因为 未提交读(READ UNCOMMITTED)总是读取最新的数据行,而不是符合当前事务版本的数据行;可串行化(SERIALIZABLE)则会对所有读取的行都加锁.

MVCC 多出的两列使得很多数据操作不用上锁,提升了性能;不足之处是需要额外的存储空间.

MySQL的存储引擎

MySQL将每个数据库(schema)保存为数据目录下一个子目录, 然后数据库子目录下创建一个和表同名的.frm文件保存表的定义(MySQL8.0后不再有.frm, 放到系统表空间了). 不同存储引擎保存数据和索引的方式不同(如InnoDB只有索引), 但表的定义是在MySQL服务层统一处理的.
显示数据目录位置:

show global variables like "%datadir%";

展示表的信息

SELECT * FROM information_schema.tables WHERE table_schema = DATABASE() and TABLE_NAME='runoob_tbl'\G

InnoDB存储引擎

  • InnoDB 是 MySQL 的默认事务型存储引擎,也是最重要、使用最广泛的存储引擎。通过间隙锁(next-key locking)使得 InnoDB 不仅仅锁定查询涉及的行,还会对索引中的间隙进行锁定,以防止幻影行的插入,从而防止幻读.
  • InnoDB主键应当尽量小, 后续讨论聚簇索引, 主键查询, 二级索引时会仔细分析.
  • InnoDB做了诸多优化: 采用可预测性预读,自动在内存中创建hash索引以加速读操作的自适应哈希索引, 能够加速插入操作的插入缓冲区(insert buffer)等.
  • 官方手册的"InnoDB事务模型和锁"介绍了它的MVCC架构.
  • InnoDB支持真正的在线热备份. 而其他引擎则不支持, 要获取一致性视图需要停止对所有表的写入.

MyISAM存储引擎

  • MyISAM不支持事务和行级锁, 且崩溃后无法安全恢复.
  • 优点是对于只读的数据,或者表比较小,可以忍受修复操作,可以继续使用.
  • MyISAM会将表存储在两个文件中: 数据文件和索引文件,分别以.MYD和.MYI为扩展名.
  • MyISAM表可以包含动态或者静态(长度固定)行。MySQL会根据表的定义来决定采用何种行格式。MyISAM表可以存储的行记录数,一般受限于可用的磁盘空间,或者操作系统中单个文件的最大尺寸.
  • MyISAM对整张表加锁,而不是针对行。读取时会对需要读到的所有表加共享锁,写入时则对表加排他锁。但是在表有读取查询的同时,也可以往表中插入新的记录(这被称为并发插入).

Archive引擎

Archive引擎只支持INSERT, SELECT. 会缓存所有的写并利用zlib对插人的行进行压缩,所以比MyISAM表的磁盘I/O更少。但是每次SELECT查询都需要执行全表扫描。所以Archive表适合日志和数据采集类应用,这类应用做数据分析时往往需要全表扫描。或者在一些需要更快速的INSERT操作的场合下也可以使用。

Archive引擎支持行级锁和专用的缓冲区,所可以实现高并发的插人。在一个查询开始直到返回表中存在的所有行数之前,Archive引擎会阻止其他的SELECT执行,以实现一致性读。另外,也实现了批量插入在完成之前对读操作是不可见的。这种机制模仿了事务和MVCC的一些特性,但Archive引擎不是一个事务型的引擎,而是-一个针对高速插人和压缩做了优化的简单引擎。

Blackhole引擎

Blackhole引擎没有实现任何的存储机制,它会丟弃所有插入的数据,不做任何保存。但是服务器会记录Blackhole表的日志,所以可以用于复制数据到备库,或者只是简单地记录到日志。这种特殊的存储引擎可以在--些特殊的复制架构和8志审核时发挥作用。但这种应用方式我们碰到过很多问题,因此并不推荐。

CSV引擎

CSV引擎可以将普通的CSV文件(逗号分割值的文件)作为MySQL的表来处理,但这种表不支持索引。CSV引擎可以在数据库运行时拷入或者拷出文件。可以将Excel等电子表格软件中的数据存储为CSV文件,然后复制到MySQL数据目录下,就能在MySQL中打开使用。同样,如果将数据写人到一个CSV引擎表,其他的外部程序也能立即从表的数据文件中读取CSV格式的数据。因此CSV引擎可以作为- -种数据交换的机制,非常有用。

Federated引擎

Federated引擎是访问其他MySQL服务器的-一个代理,它会创建-一个到远程MySQL服务器的客户端连接,并将查询传输到远程服务器执行,然后提取或者发送需要的数据。最初设计该存储引擎是为了和企业级数据库如Microsoft SQL Server和Oracle的类似特性竞争的,可以说更多的是一种市场行为。尽管该引擎看起来提供了一种很好的跨服务器的灵活性,但也经常带来问题,因此默认是禁用的。

Memory引擎

如果需要快速地访问数据,并且这些数据不会被修改,重启以后丢失也没有关系,那么使用Memory表(以前也叫做HEAP表)是非常有用的。Memory 表至少比MyISAM表要快一个数量级,因为所有的数据都保存在内存中,不需要进行磁盘I/O。Memory 表的结构在重启以后还会保留,但数据会丢失。

选择合适的引擎

一般情况下,都应该使用 InnoDB 存储引擎,除非需要用到某些 InnoDB 不具备的特性。另外,不要混合使用多种存储引擎。对存储引擎的选择,可以考虑以下几个因素:事务、备份、崩溃恢复、特有的特性。当然,还要根据具体的使用场景决定使用何种存储引擎。InnoDB 对于 95% 以上的用户都是最佳选择.

转换表的引擎

类型 方法
ALTER TABLE ALTER TABLE mytable ENGINE=InnoDB;
导入与导出 mysqldump工具蒋书记导出到文件
创建与查询 先穿件一个新的存储引擎的表, 然后利用INSERT...SELECT来导数据: create table innodb_table like myisam_table; alter table innodb_table engine=InnoDB; insert into innodb_table select * from myisam_table; 数据量大的话, 可以使用事务分批提交. 如有必要, 可以对原表加锁, 以确保新表和原表的数据一致性. 最后删除原表.

总结

MySQL拥有分层的架构, 上层是服务层的服务和查询引擎, 下次则是存储引擎. 理解MySQL在存储引擎和服务层之间处理查询时如何通过API来交互, 就能抓住MySQL的核心.

下面的文章生动的说明了存储引擎的API是怎样的:


image.png

补充阅读

MVCC-数据库村的旺财和小强
InnoDB事务模型和锁
自己写一个存储引擎
mysql connection handling and scaling: 每个客户端连接都会对应一个线程, 那客户端没有查询发出的时候,这个线程能不能被别的客户端连接使用呢?

你可能感兴趣的:(第一章 Mysql架构与历史)