Mysql简介
版本号3.23(2001)Mysql的诞生,引入MyISAM和InnoDB。
版本号4.0(2003)支持更多语法,如UNION和多表DELETE语法,引入查询缓存。
版本号5.0(2006)出现企业级Mysql特性:视图,触发器,存储过程和存储函数。之后Sun收购Mysql后,5.1版本,引入分区和基于行的复制备份,以及可插拔的存储引擎API。
版本号5.5(2010)Oracle收购Sun以后,将InnoDB设为默认存储引擎,增加了其扩展性和性能提升。
版本号5.6(2013)为InnoDB加入全文检索。
Mysql 服务器架构
Mysql的整体架构如下图所示,分为三层:
第一层为大部分应用都拥有的client端或者接口端,主要负责调用Mysql服务器的服务。
第二层为Mysql服务器层,其中包含了所有在调用存储引擎之前所做的预备工作
第三层为存储引擎层,该层和服务器层是完全分离,并且由上面可以知道已经实现了存储引擎可插拔的模式
Mysql模块架构图
1、Client& Server 交互协议模块
任何C/S 结构的软件系统,都肯定会有自己独有的信息交互协议,MySQL 也不例外。MySQL的Client & Server 交互协议模块部分,实现了客户端与MySQL 交互过程中的所有协议。当然这些协议都是建立在现有的OS 和网络协议之上的,如TCP/IP 以及Unix Socket。
2、初始化模块
顾名思议,初始化模块就是在MySQL Server 启动的时候,对整个系统做各种各样的初始化操作,比如各种buffer,cache 结构的初始化和内存空间的申请,各种系统变量的初始化设定,各种存储引擎的初始化设置,等等。
3、网络交互模块
底层网络交互模块抽象出底层网络交互所使用的接口api,实现底层网络数据的接收与发送,以方便其他各个模块调用,以及对这一部分的维护。所有源码都在vio 文件夹下面。
4、连接管理、连接线程模块
连接管理模块负责监听对MySQL Server 的各种请求,接收连接请求,转发所有连接请求到线程管理模块。每一个连接上MySQL Server 的客户端请求都会被分配(或创建)一个连接线程为其单独服务。而连接线程的主要工作就是负责MySQL Server 与客户端的通信,接受客户端的命令请求,传递Server 端的结果信息等。线程管理模块则负责管理维护这些连接线程。包括线程的创建,线程的cache 等。
5、用户模块
用户模块所实现的功能,主要包括用户的登录连接权限控制和用户的授权管理。他就像MySQL 的大门守卫一样,决定是否给来访者“开门”。
6、Query 解析和转发模块
在MySQL 中我们习惯将所有Client端发送给Server 端的命令都称为query,在MySQL Server 里面,连接线程接收到客户端的一个Query 后,会直接将该query 传递给专门负责将各种Query 进行分类然后转发给各个对应的处理模块,这个模块就是query 解析和转发模块。其主要工作就是将query 语句进行语义和语法的分析,然后按照不同的操作类型进行分类,然后做出针对性的转发。
7、QueryCache 模块
Query Cache 模块在MySQL 中是一个非常重要的模块,他的主要功能是将客户端提交给MySQL 的Select 类query 请求的返回结果集cache 到内存中,与该query 的一个hash 值做一个对应。该Query 所取数据的基表发生任何数据的变化之后,MySQL 会自动使该query 的Cache 失效。在读写比例非常高的应用系统中,Query Cache 对性能的提高是非常显著的。当然它对内存的消耗也是非常大的。
8、日志记录模块
日志记录模块主要负责整个系统级别的逻辑层的日志的记录,包括error log,binary log,slow query log 等。
9、Query 优化器模块
Query 优化器,顾名思义,就是优化客户端请求的query,根据客户端请求的query 语句,和数据库中的一些统计信息,在一系列算法的基础上进行分析,得出一个最优的策略,告诉后面的程序如何取得这个query 语句的结果。
10、表变更管理模块
表变更管理模块主要是负责完成一些DML 和DDL 的query,如:update,delte,insert,create table,alter table 等语句的处理。
11、表维护模块
表的状态检查,错误修复,以及优化和分析等工作都是表维护模块需要做的事情。
12、复制模块
复制模块又可分为Master 模块和Slave 模块两部分, Master 模块主要负责在Replication 环境中读取Master 端的binary 日志,以及与Slave 端的I/O 线程交互等工作。
Slave 模块比Master 模块所要做的事情稍多一些,在系统中主要体现在两个线程上面。一个是负责从Master请求和接受binary 日志,并写入本地relay log 中的I/O 线程。另外一个是负责从relay log 中读取相关日志事件,然后解析成可以在Slave 端正确执行并得到和Master端完全相同的结果的命令并再交给Slave 执行的SQL 线程。
13、系统状态管理模块
系统状态管理模块负责在客户端请求系统状态的时候,将各种状态数据返回给用户,像DBA 常用的各种showstatus 命令,showvariables 命令等,所得到的结果都是由这个模块返回的。
14、访问控制模块
造访客人进门了就可以想干嘛就干嘛么?为了安全考虑,肯定不能如此随意。这时候就需要访问控制模块实时监控客人的每一个动作,给不同的客人以不同的权限。访问控制模块实现的功能就是根据用户模块中各用户的授权信息,以及数据库自身特有的各种约束,来控制用户对数据的访问。用户模块和访问控制模块两者结合起来,组成了MySQL 整个数据库系统的权限安全管理的功能。
15、表管理器
这个模块从名字上看来很容易和上面的表变更和表维护模块相混淆,但是其功能与变更及维护模块却完全不同。大家知道,每一个MySQL 的表都有一个表的定义文件,也就是*.frm文件。表管理器的工作主要就是维护这些文件,以及一个cache,该cache 中的主要内容是各个表的结构信息。此外它还维护table 级别的锁管理。
16、存储引擎接口模块
存储引擎接口模块可以说是MySQL 数据库中最有特色的一点了。目前各种数据库产品中,基本上只有MySQL 可以实现其底层数据存储引擎的插件式管理。这个模块实际上只是一个抽象类,但正是因为它成功地将各种数据处理高度抽象化,才成就了今天MySQL 可插拔存储引擎的特色。
17、核心API
核心API 模块主要是为了提供一些需要非常高效的底层操作功能的优化实现,包括各种底层数据结构的实现,特殊算法的实现,字符串处理,数字处理等,小文件I/O,格式化输出,以及最重要的内存管理部分。核心API 模块的所有源代码都集中在mysys和strings文件夹下面,有兴趣的读者可以研究研究。
存储引擎
可以称之为一种处理数据的能力,也可以称之为是一种表的类型。
InnoDB :5.5版本后Mysql的默认数据库,事务型数据库的首选引擎,支持ACID事务,支持行级锁定
高并发下的mysql会发生什么问题?(以下文章仅仅讨论InnoDB存储引擎)
当一个数据库存在高并发的情况下,如果只进行普通的查操作,那么我相信通过mysql本身的缓存机制和索引机制能够达到很好的性能效果,但是如果还有其他读写操作呢?会发生什么事情?
一个经典的例子是email,如果一个用户在阅读一封邮件的时候,同时另外一个用户删除了这一封邮件,最后会发生什么?结果是不确定的,有可能会报错退出,有可能会读取到不一样的数据。
以此为基础,引入一个概念, 多版本并发控制(Multi-version concurrency control,MVCC),这个概念不仅仅是对于Mysql,包括Oracle、PostgreSQL等其他数据库系统都实现了MVCC,只是各自的机制不同,它可以保证不阻塞地读到一致的数据,实现是通过保存数据在某个时间点的快照来实现的。再引入几个基础概念, 锁、事务和隔离级别。
日志
MySQL Innodb中存在多种日志,除了错误日志、查询日志外,还有很多和数据持久性、一致性有关的日志。
bin.log是mysql服务层产生的日志,常用来进行数据恢复、数据库复制,常见的mysql主从架构,就是采用slave同步master的binlog实现的, 另外通过解析binlog能够实现mysql到其他数据源(如ElasticSearch)的数据复制。
redo.log记录了数据操作在物理层面的修改,mysql中使用了大量缓存,缓存存在于内存中,修改操作时会直接修改内存,而不是立刻修改磁盘,当内存和磁盘的数据不一致时,称内存中的数据为脏页(dirty page)。为了保证数据的安全性,事务进行中时会不断的产生redo log,在事务提交时进行一次flush操作,保存到磁盘中, redo log是按照顺序写入的,磁盘的顺序读写的速度远大于随机读写。当数据库或主机失效重启时,会根据redo log进行数据的恢复,如果redo log中有事务提交,则进行事务提交修改数据。这样实现了事务的原子性、一致性和持久性。
undo.Log除了记录redo log外,当进行数据修改时还会记录undo log,undo log用于数据的撤回操作,它记录了修改的反向操作,比如,插入对应删除,修改对应修改为原来的数据,通过undo log可以实现事务回滚,并且可以根据undo log回溯到某个特定的版本的数据,实现MVCC。
redo log 和binlog的一致性,为了防止写完binlog但是redo log的事务还没提交导致的不一致,innodb 使用了两阶段提交
锁
一种提高共享资源并发性的操作就是让锁更有选择性,尽量只锁定需要修改的数据而不是所有数据,但是加锁也是需要消耗各种资源。锁的各种操作,包括获得锁,检查锁是否解除,释放锁等,都会增加系统开销,所以想要获得最高性能的并发锁策略,是在锁的开销和数据的安全性之间寻找一个最优点。
锁在架构上分为两层,一种为服务器层面的锁,一种为存储引擎层面的锁。从功能上分为读锁和写锁(共享锁和排它锁)。
服务器层面的锁是在执行Alter Table的某些操作的时候,mysql服务器会忽略存储引擎,直接会使用表锁,执行对应的语句。
存储引擎层面的锁是为了最大程度的支持并发处理,在InnoDB,锁分行锁、Metadata Lock(事务级表锁),行锁的算法共有三种:Record Lock,Gap Lock,Next-Key Lock
Record Lock:单个行记录的上锁
Gap Lock:间歇锁,不包含记录本身的区间锁
Next-Key Lock:包含记录本身的区间锁
只有在RR隔离级别下才会有gap lock,next-key lock,其中
当where条件为 普通索引时为gap lock或者Next-key Lock
当where条件为 主键索引的时候,Next-key Lock 和Gap Lock的锁策略降级为行锁
当where条件 不是索引的时候,innodb会给所有数据上锁,然后返回Mysql server层,然后在Server层过滤掉不符合条件的数据,通过调用 unlock_row方法解锁
以下为测试间歇锁的语句:
-
create table t(id int,name int,key idx_id(name),primary key(id))engine =innodb;
-
insert into t values(1,1),(3,3),(5,5),(8,8),(11,11);
-
session 1:select * from t where name=8 for update;
-
session 2:insert into t(id,name) values(12,6);
-
session 2:insert into t(id,name) values(6,6);
如何在事务中进行行锁操作?
SELECT 语句中加 for update 或者 lock in share mode
或者update、delete
其中还有一种检验死锁的算法叫做 wait-for graph ,没当请求没有立即反应的时候就会执行。
Metadate Lock主要解决了2个问题,一个是事务隔离问题,比如在可重复隔离级别下,会话A在2次查询期间,会话B对表结构做了修改,两次查询结果就会不一致,无法满足可重复读的要求;另外一个是数据复制的问题,比如会话A执行了多条更新语句期间,另外一个会话B做了表结构变更并且先提交,就会导致slave在重做时,先重做alter,再重做update时就会出现复制错误的现象。测试的时候可以在使用show processlist查看alter 表的session是否存在Waiting for table metadata lock状态。
事务
我理解的事务为用户和Mysql服务器完整的交流,从Start Transaction;开始后的所有sql语句直到commit;结束的一次数据交流, 一般mysql中会设置自动提交事务的特点,设置方法为 show variables like 'AUTOCOMMIT';
事务应该具有4个属性: 原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
其中原子性、持久性通过数据库的 redo.log(重做日志)来实现, undo.log用来保证事务的一致性、隔离性。
重做日志:每当有操作执行前,将数据真正更改时,先前相关操作写入重做日志。这样当断电,或者一些意外,导致后续任务无法完成时,系统恢复后,可以继续完成这些更改
撤消日志:当一些更改在执行一半时,发生意外,而无法完成,则可以根据撤消日志恢复到更改之前的壮态
比如某一时刻数据库宕机了,有两个事务,一个事务已经提交,另一个事务正在处理
数据库重启的时候就要根据日志进行前滚及回退,把已提交事务的更改写到数据文件,未提交事务的更改恢复到事务开始前的状态。
关于事务,你可能不知道的地方:
保存点(savepoint),当开始一个事务的时候,里面会隐式的包含一个保存点,也可以在事务过程中使用保存点,当RollBack的时候会回到上一个保存点的位置,每个保存点拥有一个ID。( 保存点只会递增,例如:从3回滚到2,再记录下一个保存点的时候ID为4)
事务还可以进行 链事务(触发器连接)、嵌套事务、分布式事务
隔离级别
未提交读( Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据,两个事务互相透明。
提交读( Read Committed):只能读取到已经提交的数据,但是可能造成同一个事务中,由于另一个事务提前提交了一个更改事务,导致select到的数据不一致,所以为不可重读。
可重复读( Repeatable Read):在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻读。
串行读( Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞
其他的都好理解,看一下什么是 幻读:
| session one | session two |
| begin | begin |
| select table where name ='join' | |
| Empty set (0.00 sec) | |
| | Insert into table (name) values ('join') |
| | Query OK, 1 row affected |
| select table where name ='join' | |
| Empty set (0.00 sec) | |
| >update table set age=18 where | |
| >name='join' ; | |
| Query OK, 1 row affected | commit |
| commit | |
图中标红的地方,可以看到明明查不到数据,但是却update成功了,就和幻象一样,幻读之名由此而来。
设置隔离级别
1.查看 当前会话隔离级别
select @@tx_isolation;
2.查看 系统当前隔离级别
select @@global.tx_isolation;
3.设置 当前会话隔离级别
SET session TRANSACTION ISOLATION LEVEL repeatable read;
4.设置 系统当前隔离级别
SET global TRANSACTION ISOLATION LEVEL repeatable read;
InnoDB下的MVCC强版本控制:
你可将MVCC看成行级别锁的一种妥协,它在许多情况下避免了使用锁,同时可以提供更小的开销。根据实现的不同,它可以
允许非阻塞式读,在写操作进行时只锁定必要的记录。
在每一行数据中额外保存两个隐藏的列:当前行创建时的版本号和删除时的版本号(可能为空)。这里的版本号并不是实际的时间值, 而是
系统版本号。每开始个新的事务, 系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询每行记
录的版本号进行比较。
每个事务又有自己的版本号,这样事务内执行CRUD操作时,就通过版本号的比较来达到数据版本控制的目的。
InnoDB每行数据只在表中保留一份,在更新数据时上行锁,同时将旧版数据写入 undo log;表和 undo log 中行数据都记录着事务ID,在检索时,只读取来自当前已提交的
事务的行数据.
MVCC具体的操作如下:
SELECT:InnoDB会根据以下两个条件检查每行记录:
1)InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,只么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
2)行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。
INSERT:InnoDB为新插入的每一行保存当前系统版本号作为行版本号。
DELETE:InnoDB为删除的每一行保存当前系统版本号作为行删除标识。
UPDATE:InnoDB为插入一行新记录,保存当前系统版本号作为行版本号,同时保存当系统的版本号为原来的行作为删除标识。
保存这两个额外系统版本号,使大多数操作都可以不用加锁。这样设计使得计数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行。不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作。
MVCC只在REPEATABLE READ和READ COMMITED两个隔离级别下工作,其它两个隔离级别和MVCC不兼容。
可是为什么RR级别和RC级别看到的数据不一样呢?我们来看看innodb中MVCC的具体原理是怎么处理的
隐藏列
在分析MVCC原理之前,先看下InnoDB中数据行的结构:
在InnoDB中,每一行都有2个隐藏列 DATA_TRX_ID和 DATA_ROLL_PTR(如果没有定义主键,则还有个隐藏主键列):
DATA_TRX_ID表示最近修改该行数据的事务ID
DATA_ROLL_PTR则表示指向该行回滚段的指针,该行上所有旧的版本,在undo中都通过链表的形式组织,而该值,正式指向undo中该行的历史记录链表
整个MVCC的关键就是通过DATA_TRX_ID和DATA_ROLL_PTR这两个隐藏列来实现的。
事务链表
MySQL中的事务在开始到提交这段过程中,都会被保存到一个叫 trx_sys的事务链表中,这是一个基本的链表结构:
事务链表中保存的都是还未提交的事务,事务一旦被提交,则会被从事务链表中摘除。
Read View
有了前面隐藏列和事务链表的基础,接下去就可以构造MySQL实现MVCC的关键——ReadView。
ReadView说白了就是一个数据结构,在SQL开始的时候被创建。这个数据结构中包含了3个主要的成员: ReadView{low_trx_id, up_trx_id, trx_ids},在并发情况下,一个事务在启动时,trx_sys链表中存在部分还未提交的事务,那么哪些改变对当前事务是可见的,哪些又是不可见的,这个需要通过ReadView来进行判定,首先来看下ReadView中的3个成员各自代表的意思:
low_trx_id表示该事务启动时,当前事务链表中最大的事务id编号,也就是最近创建的除自身以外最大事务编号;
up_trx_id表示该事务启动时,当前事务链表中最小的事务id编号,也就是当前系统中创建最早但还未提交的事务;
trx_ids表示所有事务链表中事务的id集合。
上述3个成员组成了ReadView中的主要部分,简单图示如下:
根据上图所示,所有数据行上DATA_TRX_ID小于up_trx_id的记录,说明修改该行的事务在当前事务开启之前都已经提交完成,所以对当前事务来说,都是可见的。而对于DATA_TRX_ID大于low_trx_id的记录,说明修改该行记录的事务在当前事务之后,所以对于当前事务来说是不可见的。
注意, ReadView是与SQL绑定的,而并不是事务,所以即使在同一个事务中,每次SQL启动时构造的ReadView的up_trx_id和low_trx_id也都是不一样的,至于DATA_TRX_ID大于low_trx_id本身出现也只有当多个SQL并发的时候,在一个SQL构造完ReadView之后,另外一个SQL修改了数据后又进行了提交,对于这种情况,数据其实是不可见的。
最后,至于位于(up_trx_id, low_trx_id)中间的事务是否可见,这个需要根据 不同的事务隔离级别来确定。对于RC的事务隔离级别来说,对于事务执行过程中,已经提交的事务的数据,对当前事务是可见的,也就是说上述图中,当前事务运行过程中,trx1~4中任意一个事务提交,对当前事务来说都是可见的;而对于RR隔离级别来说,事务启动时,已经开始的事务链表中的事务的所有修改都是不可见的,所以在RR级别下,low_trx_id基本保持与up_trx_id相同的值即可。这里解释完也可以了解为什么会出现幻读。
名词解释
DML(Data Manipulation Language)数据操纵语言:
适用范围:对数据库中的数据进行一些简单操作,如insert,delete,update,select等.
DDL(Data Definition Language)数据定义语言:
适用范围:对数据库中的某些对象(例如,database,table)进行管理,如Create,Alter和Drop.
来源:https://blog.csdn.net/liqinglin06/article/details/77750246