【高性能MySQL】读书摘录1 - MySQL架构与历史

第一章、Mysql架构与历史

1、  Mysql服务器逻辑架构图

【高性能MySQL】读书摘录1 - MySQL架构与历史_第1张图片

最上层的结构不是Mysql独有的,大多数基于网络的客户端/服务器的工具或者服务都有类似的结构,比如连接处理、授权认证、安全等。

第二层是Mysql中比较核心的部分。大多数的Mysql核心功能都在这一层。包括查询解析分析优化缓存以及所有的内置函数,所有跨存储引擎的功能都在这一层实现,存储过程、触发器、视图等。

第三层包含了存储引擎。存储引擎负责Mysql中数据的存储和提取。服务器通过API与存储引擎进行通信,这些接口屏蔽了不同存储引擎之间的差异,使得这些差异对上层的查询过程透明。存储引擎包含几十个底层函数,用于执行诸如“开始一个事务”或者“根据主键提取一行记录”等操作,但存储引擎不会去解析SQL语句(Innodb会解析外键定义),不同存储引擎之间也不会相互通信,只是简单的响应上层服务器的请求。

2、 连接管理与安全性

每个客户端连接在服务器进程中拥有一个线程,这个连接的查询只会在这个单独的线程中执行。服务器负责缓存线程,因此不需要为每一个新建的连接创建或者销毁线程(Mysql5.5支持线程池插件,可以使用池中的少量线程来服务大量的连接)。

客户端连接到Mysql服务器时,服务器需要对其认证,认证基于用户名、原始主机信息和密码。如果使用了SSL,还可以使用证书(X.509)。一旦客户端连接成功,服务器会继续验证该客户端是否具有执行某个特定查询的权限。

3、 优化与执行:

Mysql会解析查询,并创建内部数据结构(解析树),然后对其进行各种优化,包括重写查询,决定表的读取顺序,以及选择合适的索引等。可以通过explain查看优化过程的各个因素

优化器并不关心使用的是什么存储引擎,但是存储引擎就与查询优化是有影响的。索引和Schema的优化(四章、五章)

对于SELECT语句。在解析查询之前,服务器会优先检查查询缓存。如果能够在其中找到对应的查询。服务器就不必在做查询解析、优化和执行的整个过程,而是直接返回查询缓存中的结果集。

iMysql的作者,对于查询缓存的观点是:Mysql查询缓存query cache看起来很美,但实际上大多数情况下都是鸡肋,建议全面禁用:

http://imysql.com/2015/03/27/mysql-faq-why-should-we-disable-query-cache.shtml

淘宝苏普的观点是:

         querycache看上去很美,缓存机制很缓存性能是值得思考的问题。Mysql的query cache是一个适合在少数情况下使用的缓存机制。这个query cache有如下规则:如果数据表被更改,那么和这个数据表相关的全部Query Cache都会无效并删除之。这里的数据表更改包括:

INSERT, UPDATE, DELETE, TRUNCATE, ALTERTABLE, DROP TABLE, or DROP DATABASE等。如果表更新频繁的话,QueryCache会加重服务器的负担。相反,如果数据更新缓慢,那么QC缓存性能还不错。

http://www.orczhou.com/index.php/2009/08/query-cache-1/

这篇文章(http://blog.csdn.net/dba_waterbin/article/details/9201645)也指出:

         Mysql的Query Cache会导致“Waiting on query cache mutex”的问题,存在着比较严重的锁竞争问题。关于Query Cache的参考资料:

1.      http://dev.mysql.com/doc/refman/5.0/en/query-cache.html

2.      http://imysql.com/2015/03/27/mysql-faq-why-should-we-disable-query-cache.shtml

3.      http://www.orczhou.com/index.php/2009/08/query-cache-1/

4.      http://imysql.com/2014/09/05/mysql-faq-why-close-query-cache.shtml

5.      http://www.percona.com/blog/2012/09/05/write-contentions-on-the-query-cache/

6.      《高性能Mysql》第七章的内容。

4、并发控制

        无论何时,只有有多个查询需要在同一时刻修改数据,都会产生并发控制的问题。Mysql两个层面的并发控制:服务器层和存储引擎层。很多系统中都使用锁机制进行并发控制。实际上,锁的性能问题很严重,并不支持大并发处理。

         并发控制的原理,可以通过实现一个由两种类型的锁组成的锁系统来解决:这两种类型的锁通常被称为共享锁排他锁,也叫读锁写锁。读锁是共享的,也就是相互之间不阻塞的。多个客户在同一时刻可以同时读取同一个资源,而互不干扰。写锁是排他的,也就是说一个写锁会阻塞其他的读锁和写锁。确保在同一时刻只有一个用户能够执行写入,防止其他用户读取正在写入的资源。在实际的数据库系统中,每时每刻都会发生锁定。

锁粒度。一般情况下,锁定的数据量越少,则系统的并发程度越高。锁策略是在锁的开销和安全性之间寻求平衡。大多数一般是在表上施加行级锁。Mysql中每个存储引擎都可以实现自己的锁策略和锁粒度。Mysql中有表锁行级锁

表锁: 是Mysql中最基本的锁策略,并且是开销最小的策略,它会锁定整个表。一个用户对表进行写操作,需要首先获得写锁,这回阻塞其他用户对该表的所有读写操作。

尽管存储引擎可以管理自己的锁,但是Mysql本身还是会使用各种有效的表锁来实现不同的目的。比如:服务器会为诸如:ALTER Table之类的语句使用表锁,而忽略存储引擎的锁机制。

行级锁:行级锁可以最大程度的支持并发处理,INNodb和XtraDB存储引擎中实现了行级锁。行级锁只在存储引擎层实现,而在Mysql服务器层没有实现。服务器层完全不了解存储引擎中的锁实现(实际上也不需要了解)。

5、事务处理

事务内的数据,要么全部执行成功,那么全部执行失败。可以Start Transaction开始一个事务,commit提交一个事务,而rollback撤销所有的修改。

事务的ACID属性:

A: atomicity 原子性:事务是不可分割的最小工作单位。

C: consistency一致性:数据库总是从一个一致性的状态转换到另一个一致性的状态。

I:isolation: 隔离性:事务所做的修改在提交之前,对其他事务是不可见的。

D:durability:持久性:一旦事务提交,事务所做的所有修改都会永久保存到数据库中。

 

隔离级别:

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

READ  UNCOMMITTED(未提交读)

         最低的隔离级别。对于事务中的修改,即使没有提交,对其他事务也是可见的,事务可以读取未提交的数据,这被称为脏读。 从性能上说,这个级别并不会比其他级别好太多,但是却缺乏其他级别的好处。实际中应用很少。

READ COMMITTED(提交读)

         大多数数据库系统的默认隔离级别是READ COMMITTED(注意MYSQL不是)。事务开始时,只能看见已经提交的事务所做的修改。换句话说,一个事务从开始到提交之前,所做的任何修改对其他事务都是不可见的。这个级别有时候也叫“不可重复读”,因为执行两次同样的查询,可能得到不一样的结果。

REPEATABLE READ(可重复读)

         这是MYSQL的默认事务隔离级别。REPEATABLE READ解决了脏读的问题。该级别保证了同一个事务中多次读取同样记录的结果是一样的。但是在理论上,可重复读隔离界别还是无法解决另一个幻读的问题。所谓幻读,值得是当某个事务在读取某个范围内的数据时,另外一个事务又在该范围内插入了新的数据,当之前的数据再次读取该范围内的记录时,会产生幻行(Phantom Row).InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC, Multiversion Concurrency Control)解决了幻读的问题。

SERIALIZABLE(可串行化)

这是最高的隔离级别。他通过强制事务串行执行,避免了幻读的问题。SERIALIZABLE会在读取的每一行数据上都加上锁,所以可能会带来大量的超时和锁争用的问题。实际中也很少用到这个隔离界别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑采用该级别。

【高性能MySQL】读书摘录1 - MySQL架构与历史_第2张图片

死锁:事务执行过程中的锁机制可能导致死锁问题,因此数据库系统实现了各种死锁检测和死锁超时机制。InnoDB解决死锁的方式是:将持有最少行级排他锁的事务进行回滚(这是相对比较简单的死锁回滚算法)

事务日志:事务日志可以帮助提升事务的效率。使用事务日志,存储引擎在修改表的数据时只需要修改其内存拷贝,再把修改行为记录到持久在硬盘上的事务日志中,不必每次都将修改的数据持久到磁盘。事务日志采用追加的方式,因此写日志的操作时磁盘上一小块区域的顺序I/O,而不是像随机I/O需要在磁盘的多个地方移动磁头,因此采用事务日志的方式相对来说要快很多。目前大多数存储引擎都是这样实现的,通常称为预写式日志(Write-Ahead Log),修改日志需要写两次磁盘。如果数据的修改已经记录到事务日志并且持久化,但数据本身还没有写回磁盘,此时系统崩溃,存储引擎在重启时能够恢复这部分修改的数据。

MySQL中的事务

MySQL中提供了两种事务型的存储引擎:InnoDB和NDB Cluster

(1)      自动提交

MySQL默认采用自动提交模式,也就是说,如果不是显式地开始一个事务,则每个查询都被当做一个事务执行提交操作,在当前连接中,可以通过设置AUTOCOMMIT变量启用或者禁止自动提交模式。修改AUTOCOMMIT对于非事务型的表如MyIsam或者Memory不会有任何影响,对这些表来说,没有commit或者rollback这些概念。还有一些命令如ALTER TABLELOCK TABLES在执行之前会强制执行COMMIT提交当前的活动事务。

MYSQL可以通过SET TRANSACTION ISOLATION LEVEL命令来设置隔离级别。新的隔离级别会在下一个事务开始的时候生效(可以修改整个数据库的隔离级别,也可以设置更改当前会话的隔离级别)。

MySQL可以识别所有的4个ANSI隔离级别,InnoDB也支持所有的隔离级别。

(2)      事务中混合使用存储引擎

MySQL服务器不管理事务,事务是由下层的存储引擎实现的,所以在同一个事务中,使用了多种存储引擎是不可靠的。混合使用了事务和非事务的表,正常情况下没有问题。但是如果该事务需要回滚,非事务的表上的变更就无法撤销,这会导致数据库处于不一致的情况。在非事务型的表上执行事务操作时,MySQL通常不会发出提醒,也不会报错。

(3)      隐式和显式锁定

InnoDB采用的是两阶段锁定协议。在事务执行的过程中,随时可以执行锁定,锁只有在执行COMMIT或者ROLLLBACK的时候才会释放。并且所有的锁都是在同一时刻被释放。这些是隐式锁,InnoDB会根据隔离级别在需要的时候自动加锁。

另外,InnoDB也支持通过特定的语句进行显式加锁:

SELECT .... LOCKIN SHARE MODE;

SELECT ... FORUPDATE;

MySQL也支持LOCKTABLES和UNLOCK TABLES,这是在服务器层实现的,与存储引擎无关。他们有自己的作用,并不能替代事务处理,如果需要使用事务,最好还是应该选择事务型存储引擎。

经常发现,应用已经将表从MyIsam转换到了InnoDB,但还是显式使用LOCKTABLES.这不但没有必要,还会严重影响性能,实际上,InnoDB的行级锁工作的更好。

多版本并发控制(MVCC

MySQL的大多数事务存储引擎的实现都不是简单的行级锁,基于提升并发性能的考虑,它们一般都实现了MVCC,不仅MySQl, Oracle,PostgreSQL等其他数据库系统也都实现了MVCC,但各自的实现机制各不相同,MVCC并没有统一的实现标准。可以认为MVCC是行级锁的一个变种,但是它在很多情况下避免了加锁操作,因此开销更低。虽然实现机制各不相同,但是大都实现了非阻塞的读操作,锁操作也只锁定必要的行。

MVCC的实现,是通过保存数据在某个时间点的快照实现的。也就是说,不管执行多长时间,每个事务看到的数据都是一致的。

不同存储引擎的MVCC实现是不同的,典型的有乐观并发控制悲观并发控制

InnoDB的MVCC, 是通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建版本,一个保存了行的过期版本(删除版本);。每开始一个新的事物,系统版本号都会递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每个记录的版本号比较。在REPEATABLE READ隔离级别下,MVCC的具体操作:

SELECT:

         InnoDB会根据以下条件检查每行记录:

(1)、InnoDB只查找版本早于当前事务版本的数据行(也就是说,行的系统版本号小于等于事务的系统版本号)。这样可以保证事务读取的行,要么是事务开始前已经存在的,要么是事务自身插入或者修改的。

(2)、行的删除版本要么未定义,要么大于当前事务的版本号。这样可以确保读取到的行,在事务开始之前未被删除。

只有符合上述两个条件的行,才能作为查询结果。

INSERT:

         为插入的每一行数据保存当前系统版本号作为行版本号。

DELETE:

         为插入的每一行数据保存当前系统版本号作为删除版本。

UPDATE:

插入一条新的记录。保存当前系统版本号作为行版本号,同时保存当前系统的版本号到原来的行作为行删除版本号。

可以看出,insert, delete和update的过程都要保存当前系统的版本号,而update的过程,实际是类似于insert+delete。

保存这两个额外的版本号,使得大多数的读操作都可以不用加锁,并且能保证只会读取到符合标准的行。不足之处是每行记录都需要额外的存储空间。

MVCC只在REPEATABLEREAD和READ COMMITED两个隔离级别下使用,其他两个隔离级别与MVCC不兼容。因为READ UNCOMMITED总是读取最新的数据行。而SERIALIZABLE则会对所有读取的行加锁。

你可能感兴趣的:(数据库)