一、MySQL逻辑架构
第一层的服务不是MySQL独有的,大多数是基于网络的客户端/服务端的工具,如连接处理、授权认证、安全等等。
第二层就是MySQL的核心功能,包括查询解析、分析、优化、缓存以及所有的内置函数,所有的跨存储引擎的功能都在这一层实现:存储过程、触发器、视图等。
第三层包含了存储引擎,主要负责MySQL中数据的存储和提取。
1.连接管理与安全
每个客户端连接都会服务器进程中拥有一个线程,这个连接的查询只会在这个单独的线程中执行,该线程只能轮流在某个CPU核心或者CPU中运行,服务器会缓存线程,所以不需要对每个连接新建或者销毁一个线程(可以复用缓存中的线程)。
当客户端连接到MySQL时服务器需要对其进行认证,如果认证成功,服务器会继续验证该客户是否有执行某个待定查询的权限。
2.优化与执行
MySQL会解析查询,并且创建内部数据结构(解析树),然后对其进行各种优化,包括重写查询SQL、决定表的读取顺序,以及选择合适的索引等。
对于一个SELECT查询语句来说:在解析查询之前,服务器会先检查查询缓存,如果能够在其中找到对应的查询,服务器会直接返回结果,不需要再执行查询解析、优化和执行整个过程。
二、并发控制
1.读写锁
比如有一张表,多个线程对这张表进行操作,加入刚好有一个线程在对一条数据更改,另一个线程也对其进行读取操作,那么进行读操作的这个线程可能读到的数据是不正确的,怎么解决这个问题呢?这时就用到了并发控制,主要是通过两种锁来控制的:读锁(共享锁)、写锁(排他锁)
读锁是共享的,或者说是相互不阻塞的,即对个线程读取同一个资源,互不干扰;写锁是排他的,也就是说只要有一个线程对其进行写操作,那么其他的读和写线程将都会被阻塞。只有这样才能保证才能保证在给定的时间内只有一个线程在写操作,并防止其他线程读取正在写入的数据。
2.锁粒度
最理想的情况是:需要写入哪个对象就锁定哪个对象。尽量只锁定需要修改的部分数据,而不是所有的数据。在任何时候,在给定的资源上,锁定的数据越少,系统的并发程度越高。
但是还有一个问题,加锁也会消耗资源,锁的各种操作:获得锁、检查锁是否已经解除、释放锁等,都会增加系统的开销,如果花费大量的时间来管理锁,而不是存取数据,那么系统的性能将会受到影响。
所谓的锁策略,就是在锁的开销和数据的完全性之间寻求平衡。
表锁是MySQL中最基本的锁策略,并且是开销最小的策略(意味着并发程度低),但是表锁的缺点是在执行插入、删除、更改的操作时会使其他的用户线程阻塞。(注意:读锁之间是互不影响的)。另外写锁比读锁的优先级更高。写锁可以插到读锁中,但是读锁却不能插到写锁中。
行级锁:可以最大程度的支持并发处理(但是同时也带来了更大的锁开销)最典型的就是InnoDB存储引擎,行级锁只在存储引擎层实现。
三、事务
最重要的事务终于到啦,事务即ACID(原子性、一致性、隔离性、持久性),在开始讲这四个特性之前我们先举一个最经典的例子:某账户存了1000元,现在用户A要往里面存200元、用户B要往出来取200元。
原子性:一个事务必须被视为不可分割的最小工作单元,整个事务的所有操作要么全部提交完成,要么全部失败回滚,对于一个事务来说不可能执行其中的一部分。那上面例子来说:A要从账户取200需要执行以下四步操作:
1 START TRANSACTION; 2 SELECT '金额' FROM '账户表' WHERE user_id = 1221221; 3 UPDATE '账户表' SET '金额' = '金额' - 200 WHERE user_id = 1221221; 4 COMMIT;
解释:1.开启事务 ,2.查询账户有没有200元 ,3.扣除金额 , 4.提交事务。
原子性就是指这四步完整执行不可再分。
一致性:数据库从一个一致性的状态转换为另一个一致性的状态。就上面那个例子:如果在第三步执行完系统崩溃,账户中的金额还是1000元,因为一个事务没有执行完,在最终提交前是不会保存到数据库中的。
隔离性:通常来讲一个事务在最终提交前,对其他事务是不可见的。就之前例子来说,第三步执行完,在没提交之前B账户来看到的金额仍然是1000元。是不可见的。
持久性:事务一旦提交其所做的修改就会永远的被保存到数据库中,即使系统崩溃也不会丢失。
一个实现了事务的数据库相比于没有实现事务的数据库它需要更强的CPU处理能力、更大的内存、更多的磁盘空间。对于有些不需要事务的查询类应用选择一个非事务型的存储引擎可以获得更高的性能。
1.隔离级别
一共有四种隔离级别先简单做个介绍:
A.READ UNCOMMIT(读未提交)
在这个隔离级别中,事务的修改,即使没有提交,对其他的事务也是可见的。事务可以读到未提交的数据,这些数据也被称为脏读。
B.READ COMMIT(读已提交)(不可重复读)
大多数数据库系统的默认隔离级别都是读已提交,但是MySQL不是,MySQL的默认隔离级别是可重复读。简单说就是一个事务开始时只能 “看见” 已经提交的事务所做的修改,换句话说,一个事务从开始到提交这个过程中所做的所有修改对其他事务是不可见的。这个级别对于两次执行相同情况的查询,可能会得到不同的结果。
C.REPEATABLE READ(可重复读)
该级别保证了在同一个事务中多次读取同样的记录结果是一致的,但是它无法解决幻读的问题。
D.SERIALIZABLE(可串行化)
是最高的隔离级别。它通过强制事务串行化执行,避免了所有问题,他会在读取的每一行记录上面都加锁,所以可能导致大量的超时与锁等待的问题,并发低。
讲几个概念:
1.脏读:A用户把钱取出没有提交,B用户读取到修改了的金额为800,过一会A因为异常事务回滚,B再次读取发现值变为1000,两次不一样,第一次读到的是脏数据。
2.不可以重复读:B读到数据为1000后,A修改数据为800并且提交,B再次读取为800,两次数据不一致。
3.幻读:与不可重复读类似:B读到一个表里有10条记录,A插了一条并提交,B再次读为11条,感觉发生幻觉一样。
不同隔离级别引发的问题:
2.死锁
多个事务在同一资源上相互占有,并且请求锁定对方占用的资源,从而导致恶性循环的现象。
数据库系统实现了各种死锁检测和死锁超时机制。InnoDB目前处理死锁的方法是:将持有最少行排它锁的事务进行回滚。
3.事务日志
事务日志可以提高事务的效率,使用事务日志时:存储引擎在修改表数据时只需要修改其内存拷贝,再把该修改行为记录到持久在硬盘上的事务日志中,不用每次将修改的数据本身持久到硬盘。事务日志采用追加的方式,写日志的操作是磁盘上的一小块区域内的顺序I/O.事务日志持久后,内存中被修改的数据将在后台慢慢刷回到磁盘。所以一共需要两次磁盘I/O:一次写日志磁盘I/O,一次刷回磁盘I/O.
4.MySQL中的事务
1.自动提交:MySQL默认采用自动提交的方式。比如每个查询就是一个事务执行完自动提交。
2.非事务型存储引擎如:MyISAM无回滚功能。
四、多版本并发控制MVCC
可以认为MVCC是行级锁的一个变种,它在很多情况下避免了加锁,MVCC的实现是通过保存数据在某个时间的快照来实现的。
InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的,这两个列一个保存了行的创建时间,一个保存了行的删除时间,存储的不是真正时间而是系统的版本号。每个新事物开始系统版本号自动递增。事物开始时刻的版本号作为事物的版本号,用来和查询到的每行记录版本号进行比较。
MVCC只在可重复读和读已提交两个隔离级别下工作,其他两个级别都和MVCC不兼容。因为读未提交总是读取最新的数据行,不符合当前事物版本的数据行。可串行化会对所有行加锁。
我的理解其实MVCC就跟CAS是一个原理,就是一个比较并交换的过程。