首先,学数据库之前,我们先来认识这样一张图,这张图是数据库各组件协同工作的流程图,对我们深入了解MySQL服务器有帮助。
该流程图分为三层
第一层为MySQL服务器,线程/连接架构非MySQL独有,服务器都有类似的架构。
第二层内容十分广泛,大多数核心服务都在这一层,查询、解析、优化、缓存以及所有的内置函数都在这一层,所有存储引擎的功能也都是在这一层实现,如存储过程,视图,触发器等。
第三层包涵了存储引擎,存储引擎负责数据的存储和提取,每个存储引擎都有其优势劣势,服务器通过API(应用程序接口)与存储引擎进行通信。
优化与执行
MySQL会解析查询,同时创建内部数据结构(解析树,B-Tree之流),之后对其进行各类优化,包括重写查询,决定表的读取顺序,选择合适的索引等。用户可以通过特殊关键字提示优化器,影响它的决策过程。也可以请求优化器解释优化过程的各个因素,让用户可以知道服务器是如何进行优化决策的,同时提供参考准则,方便用户重构查询、schema(数据库对象的集合 如表,索引,视图,存储过程等)、修改相关的配置,令应用尽可能高效运转。对于Select语句,在执行之前,服务器会现在查询缓存上查找是否有相同的查询,如果有,则不再进行解析,优化,执行的整个过程,而是直接返回查询缓存中的结果集。
并发控制
多个sql语句在同一时刻对数据库里面的同一条数据做修改时,总会涉及到并发的问题。MySQL在两个层面涉及到处理并发的问题,一个是服务器层,一个是存储引擎层。
读写锁
读锁(read lock)也叫共享锁(shared lock),读锁是共享的,单位时间内,多用户可以同时访问同一资源,与其他用户互不干扰,不会阻塞其他线程。
写锁(write lock)也叫排他锁(Exclusive lock),非共享锁,在单位时间内,只允许一个线程对某一资源进行操作,其他写锁或者读锁会被阻塞,而无法对目标资源进行操作。
在实际情况下,当用户在对数据进行修改时,其他用户会无法对该数据进行访问跟其他操作,因为该数据已经被写锁锁定。大部分情况下,锁的内部管理都是透明的
锁粒度
一种提高共享数据并发性的方式,就是让锁定的对象更具选择性,尽量只锁定需要修改的部分数据,而不去锁定全部数据。任何时候,锁定的数据越少、越精确,并发程度越高,只要相互之间不冲突即可。
加锁需要消耗资源,锁的各种操作,包括获得锁、检测锁是否已经解除、释放锁等,都会占用资源,增加系统的开销。
锁策略
所谓锁策略,就是在锁的开销和数据的安全性之间寻求平衡,这种平衡当然也会影响性能。MySQL提供了多种选择,每种MySQL存储引擎都可以实现自己的所策略和锁粒度。
锁策略—表锁(table lock)
表锁是MySQL中最基本的表策略,并且是开销最小的策略,表锁会锁定整张表,一个用户在对表进行写操作(插入、删除、更新等)前,需要先获得写锁,这会阻塞其他用户对表的所有读写操作。只有没有写锁时,其他读取的用户才能获得读锁,读锁之间是不互相阻塞的。值得注意的是,写锁比读锁有着更高的优先级,因此,一个写锁请求可能会被插入到读锁队列的前面,相反,读锁不能插入到写锁的前面。
锁策略—行级锁(row lock)
行级锁可以最大程度实现并发处理,但同时也带来了最大程度的锁开销,MySQL行级锁只在存储引擎实现,服务器层完全不清楚存储引擎层的锁实现,所有的存储引擎都有自己的方式显现锁机制
事务
事务是一组原子性的SQL查询,或者说是独立的工作单元。如果数据库引擎能够成功地对数据库应用该组查询的全部语句,那么就执行该组查询。如果其中有任何一条语句因为崩溃或其他原因无法进行,那么所有的语句都不会执行。也就是说,事务内的语句,要么全部执行,要么全部执行失败。
事务的ACID概念
原子性(Atomicity),一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作。
一致性(Consistency),数据库总是从一个一致性的状态转换到另一个一致性的状态。一致性确保了事务执行的完整性,就是说一个事务必须完整地执行才会被提交,否则将会回滚,数据都不会得到变更。
隔离性(Isolaion),通常来说,一个事务所做的修改在最终提交前,对其他事务是不可见的。
持久性(Durability),一旦事务提交,则其所做的修改就会被永久保存到数据库中,此时即使系统崩溃,修改的数据也不会丢失。持久性是个有点模糊的概念,因为实际上持久性也分很多不同的级别。有些持久性策略能够提供非常强的安全保障,而有些则未必。而且不可能有能做到100%的持久性保障的策略。
事务的ACID特性可以确保银行不会弄丢你的钱。而在应用逻辑中,要实现这一点非常难,甚至可以说是不可能完成的任务。一个兼容ACID的数据库系统,需要做很多复杂但可能用户并没有觉察到的工作,才能确保ACID的实现。就像锁粒度的升级会增加系统开销一样,这种事务处理过程中额外的安全性,也会需要数据库系统做更多的额外工作。一个实现了ACID的数据库,相比没有实现ACID的数据库,通常会需要更强的CPU处理能力、更大的内存和更多的磁盘空间。
隔离级别
在SQL标准中定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发,系统开销也更低,隔离级别有四种,分别如下。
未提交读(Read Uncommitted)
在Read Uncommitted级别,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也可以被称为脏读(Dirty Read)。这个级别会导致很多问题,从性能上来说,Read Uncommitted不会比其他的级别好太多,但却缺乏其他级别的很多好处,除非真的有必要的理由,在实际应用中一般很少使用。
提交读(Read Committed)
大多数数据库系统的默认隔离级别都是Read Committed(MySQL不是)。Read Committed满足前面提到的隔离性的简单定义:一个事务开始时,只能看见已经提交的事务所做的修改。换句话说,一个事务从开始知道提交之前,所做的任何修改对其他事务都是不可见的。这个级别有时候也叫做不可重复读(nonrepeatable read),因为两次执行同样的查询,可能会得到不一样的结果。
可重复读(Repeatable Read),RepeaTable Read解决了脏读的问题。该级别保证了在同一个事务中多次读取同样记录的结果是一致的。但理论上,可重复读隔离级别还是无法解决另外一个幻读(Phantom Read)的问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生缓行(Phantom Row)。InnoDB和XtraDB存储引擎通过多版本并发控制(Multiversion Concurrency Control)解决了幻读的问题。可重复度是MySQL的默认事务隔离级别。
Serializable(可串行化),Serializable是最高的隔离级别。它通过强制事务串行执行,避免了前面说的幻读的问题。简单来说,Serializable会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑采用该级别。
死锁
死锁是指两个或者多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。当多个事务试图以不同的顺序锁定资源时,就可能会产生死锁。多个事务同时锁定同一个资源时,也会产生死锁。为了解决死锁问题,数据库系统实现了各种死锁检测和死锁超时机制。越复杂的系统,比如InnoDB存储引擎,越能检测到死锁的循环依赖,并立即返回一个错误。这种解决方式很有效,否则死锁会导致出现非常慢的查询。还有一种解决方式,就是当查询的时间达到锁等待超时的设定后放弃锁的请求,这种方式通常来说不太好。InnoDB目前处理死锁的方法是,将持有最少行级排他锁的事务进行回滚,这是相对比较简单的死锁回滚算法。
锁的行为和顺序是和存储引擎相关的,以同样的顺序执行语句,有些存储引擎会产生死锁,有些则不会。死锁的产生有双重原因,有些是因为真正的数据冲突,这种情况通常很难避免,但有些则完全是由于存储引擎的实现方式导致的。死锁发生以后,只有部分或者完全回滚其中一个事务,才能打破死锁。对于事务型的系统,这是无法避免的,所以应用程序在设计时必须考虑如何处理死锁。大多数情况下只需要重新执行因死锁回滚的事务即可。