【学习笔记】高性能MySQL(第三版)——第1章:MySQL架构与历史

  1. MySQL并不完美,却足够灵活,能够适应高要求的环境,例如Web类应用。同时,MySQL既可以嵌入到应用程序中,也可以支持数据仓库、内容索引和部署软件、高可用的冗余系统、在线事务处理系统(OLTP)等各种应用类型。
  2. MySQL最重要、最与众不同的特性是它的存储引擎架构,这种架构的设计将查询处理(Query Processing)及其他系统任务(Server Task)和数据的存储/提取相分离。这种处理和存储分离的设计可以在使用时根据性能、特性,以及其他需求来选择数据存储的方式。

1.1 MySQL逻辑架构

MySQL服务器逻辑架构图
【学习笔记】高性能MySQL(第三版)——第1章:MySQL架构与历史_第1张图片

  • 最上层的服务并不是MySQL所独有的,大多数基于网络的客户端/服务器的工具或者服务都有类似的架构。比如连接处理、授权认证、安全等等。
  • 第二层架构是MySQL比较有意思的部分。大多数MySQL的核心服务功能都在这一层,包括查询解析、分析、优化、缓存以及所有的内置函数(例如,日期、时间、数学和加密函数),所有跨存储引擎的功能都在这一层实现:存储过程、触发器、视图等。
  • 第三层包含了存储引擎。存储引擎负责MySQL中数据的存储和提取。每个存储引擎都有它的优势和劣势。存储引擎API包含了几十个底层函数,但存储引擎不会去解析SQL(注释:InnoDB是一个例外,它会解析外键定义,因为MySQL服务器本身没有实现该功能),不同存储引擎之间也不会相互通信,而只是简单地响应上层服务器的请求。

1.1.2 优化和执行

  1. MySQL会解析查询,并创建内部数据结构(解析树),然后对其进行各种优化,包括重写查询、决定表的读取顺序,以及选择合适的索引等。(第6章详细讨论)
  2. 对于SELECT语句,在解析查询之前,服务器会先检查查询缓存(Query Cache),如果能够在其中找到对应的查询,服务器就不必再执行查询解析、优化和执行的整个过程,而是直接返回查询缓存中的结果集。(第7章详细讨论)

1.2 并发控制

  1. 无论何时,只要有多个查询需要在同一时刻修改数据,都会产生并发控制的问题。
  2. 比如,设计良好的邮箱投递系统会通过锁(Lock)来防止数据损坏。如何客户试图投递邮件,而邮箱已经被其它客户锁住,那就必须等待,直到锁释放才能进行投递。这种锁的方案在实际应用环境中虽然工作良好,但并不支持并发处理。因为任何一个时刻,只有一个进程可以修改邮箱的数据,这在大容量的邮箱系统中是个问题。

1.2.1 读写锁

  1. 在邮箱系统中,邮箱等同于一张数据表,而每封邮件等同于每一条记录,如果在查看邮件的同时删除当前查看的邮件,就可能出现不可预知的结果。解决这类经典问题的方法就是并发控制
  2. 在处理并发读或写时,可以通过实现一个由两种类型的锁组成的锁系统来解决问题。这两种类型的锁通常被称为共享锁(shared lock),也叫读锁(read lock)和排他锁(exclusive lock),也叫写锁(write lock)。读锁可以同时进行,而写锁会阻塞其它的读锁和写锁,这是出于安全策略的考虑,防止用户读取正在写入的同一资源。

1.2.2 锁粒度

  1. 一种提高共享资源并发性的方式就是让锁定对象更有选择性。最理想的方式是,只对会修改的数据片进行精确的锁定。在不发生冲突的情况下,锁定的数据量越小,则系统并发程度越高。
  2. 锁策略:就是在锁的开销和数据的安全性之间寻求平衡,这种平衡当然也会影响到性能。
  3. 表锁(table lock):表锁是MySQL中最基本的锁策略,并且是开销最小的策略。
  4. 行级锁(row lock):行级锁最大程度地支持并发处理(同时也带来了最大的锁开销)。行级锁只在存储引擎层实现,而MySQL服务器层没有实现。

1.3 事务

  1. 事务是一组原子性的SQL查询,或者说是一个独立的工作单元。
  2. START TRANSACTION(开始事务);ROLLBACK(回滚);COMMIT(提交)。
  3. 一个运行良好的事务处理系统,必须具备ACID特征。原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性(durability)。
  4. 原子性(atomicity):一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚。对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性。
  5. 一致性(consistency):数据库总是从一个一致性的状态转换到另外一个一致性的状态。如果事务没有提交,那么事务中所做的修改就不会保存到数据库。
  6. 隔离性(isolation):通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。
  7. 持久性(durability):一旦事务提交,则其所做的修改就会永久保存到数据库中。

1.3.1 隔离级别

  1. 未提交读(READ UNCOMMITED):事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也称为脏读(Dirty Read)。这个级别会导致很多问题,性能上也不比其他级别好太多。
  2. 提交读(READ COMMITED):除MySQL外的大多数数据库系统的默认隔离级别。提交读满足了隔离性的简单定义:一个事务开始时,只能“看见”已经提交的事务所做的修改。这个级别也叫不可重复读(nonrepeatable read),因为两次执行同样的查询,可能得到不一样的结果。
  3. 可重复读(REPEATABLE READ):这是MySQL的默认事务隔离级别,解决了脏读的问题,保证了在同一事务中多次读取同样的记录的结果是一致的。但无法解决幻读(Phantom Read)的问题。幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(Phantom Row)。InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决了幻读的问题。
  4. 可串行化(SERIALIZABLE):最高隔离级别。它通过强制事务串行执行,避免了幻行的问题。可串行化会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题。只有数据的一致性要求很高,并且可以接受没有并发的情况下,才考虑使用。
    【学习笔记】高性能MySQL(第三版)——第1章:MySQL架构与历史_第2张图片

1.3.2 死锁

  1. 死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。
  2. 数据库系统实现了各种死锁检测和死锁超时机制。InnoDB处理死锁的方法是,将持有最少行级排他锁的事务进行回滚(相对比较简单的死锁回滚算法)。
  3. 数据冲突存储引擎的实现方式都会导致死锁的产生。对于事务型的系统,死锁是无法避免的,所以应用程序在设计时就必须考虑如何处理死锁,大多数情况下只需要重新执行因死锁回滚的事务即可。

1.3.3 事务日志

  1. 事务日志可以帮助提高事务的效率。使用事务日志,存储引擎在修改表的数据时只需要修改其内存拷贝,再把该修改行为记录到持久在硬盘中的事务日志中,而不用每次都将修改的数据本身持久到硬盘。事务日志持久以后,内存中被修改的数据在后台可以慢慢地刷回磁盘。目前大多数存储引擎都是这样实现的,通常称之为预写式日志(Write-Ahead Logging),修改数据需要写两次磁盘。
  2. 如果数据的修改已经记录到事务日志并持久化,但数据本身还没有写回磁盘,此时系统崩溃,存储引擎在重启时能够自动恢复这部分修改的数据。具体的恢复方式则视存储引擎而定。

1.3.4 MySQL中的事务

  1. MySQL提供了两种事务型的存储引擎:InnoDB和NDB Cluster。另外还有一些第三方存储引擎也支持事务,比如XtraDB和PBXT。
  2. 自动提交(AUTOCOMMIT):MySQL默认采用自动提交,如果不是显式地开始一个事务,则每个查询都被当做一个事务执行提交操作。可以用SHOW VARIABLES LIKE "AUTOCOMMIT"查看自动提交模式的状态。
  3. MySQL服务器层不管理事务,事务是由下层的存储引擎实现的。所以在同一事务中,使用多种存储引擎是不可靠的。
  4. 在混用事务型和非事务型存储引擎的时候,如果需要回滚,而非事务型存储引擎并不支持,就是产生不可修复的后果。
  5. InnoDB采用的是两阶段锁定协议(two-phase locking protocol)。在事务执行过程中,随时都可以执行锁定,锁只有在执行COMMIT或ROLLBACK时才会释放,并且所有的锁是在同一时刻释放。InnoDB会根据隔离级别在需要时自动加锁。
  6. MySQL支持LOCK TABLESUNLOCK TABLES,这是在服务器层实现的,和存储引擎无关,它有自己的用途,并不能代替事务处理。

1.4 多版本并发控制

  1. MySQL的大多数事务型存储引擎实现的都不是简单的行级锁,它一般都同时实现了多版本并发控制(MVCC)。
  2. MVCC可以认为是行级锁的一个变种,但它在很多情况下避免了加锁操作,因此开销更低。
  3. MVCC的实现,是通过保存数据在某个时间点的快照来实现的。避免了事务开始的时间不同,而看到不同的数据。
  4. InnoDB的MVCC是通过在每行记录后面保存两个隐藏的列来实现的。这两列,一列保存了行的创建时间,一个保存了行的过期时间(或删除时间),等同系统版本号。通过获取适当的版本号的内容来操作数据,保证数据时间上的一致性。
  5. MVCC只在REPEATABLE READ和READ COMMIT两个隔离级别下工作。其它两个都和MVCC不兼容,因为READ UNCOMMIT总是读取最新的数据行,而不是符合当前事务版本的数据行;而SERIALIZABLE则会对所有读取的行都加锁。

1.5 MySQL的存储引擎

  1. 在文件系统中,MySQL将每个数据库(schema)保存为数据目录下的一个子目录。创建表时,MySQL会在数据库子目录下创建一个和表同名的.frm文件保存表的定义。不同的存储引擎保存的数据和索引的方式是不同的,但表的定义则是在MySQL服务层统一处理的。可以使用SHOW TABLE STATUS命令查看表的相关信息。

1.5.1 InnoDB存储引擎

  1. InnoDB是MySQL的默认事务型引擎,也是最重要、使用最广泛的存储引擎。它被设计用来处理大量的短期(short-lived)事务,短期事务大部分情况是正常提交的,很少会被回滚。
  2. InnoDB的性能和自动崩溃恢复特征,使得它在非事务型存储的需求中也很流行。除非有非常特别的原因需要使用其他的存储引擎,否则应该优先考虑InnoDB引擎。如果需要学习存储引擎,InnoDB也是一个非常好的值得花最多时间去深入学习的对象。
  3. InnoDB的数据存储在表空间(tablespace)中,表空间是由InnoDB管理的一个黑盒子,由一系列的数据文件组成。
  4. InnoDB采用MVCC来支持高并发,并且实现了四个标准的隔离级别。其默认级别是可重复读(REPEATABLE READ),并且通过间隙锁(next-key locking)策略防止幻读的出现。间隙锁使得InnoDB不仅仅锁定查询涉及的行,还会对索引中的间隙进行锁定,以防止幻行的插入。
  5. InnoDB表是基于聚簇索引简历的,InnoDB的索引结构和MySQL的其它存储引擎有很大的不同,聚簇索引对主键查询有很高的性能。不过它的二级索引(secondary index,非主键索引)中必须包含主键列,所以如果主键列很大的话,其它的所有索引都会很大。因此,若表上的索引较多的话,主键应当尽可能的小。
  6. InnoDB内部做了很多优化,包括从磁盘读取数据时采用的可预测性预读,能够自动在内存中创建hash索引以加速读操作的自适应哈希索引(adaptive hash index),以及能够加速插入操作的插入缓冲区(insert buffer)等。
  7. 作为事务型的存储引擎,InnoDB通过一些机制和工具支持真正的热备份。MySQL的其它存储引擎不支持热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。

1.5.2 MyISAM存储引擎

  1. 在MySQL5.1及之前的版本,MyISAM是默认的存储引擎。MyISAM提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等,但MyISAM不支持事务和行级锁,而且有一个毫无疑问的缺陷就是崩溃后无法安全恢复。
  2. 对于只读的数据,或者表比较小、可以忍受修复(repair)操作,则依然可以继续使用MyISAM。
  3. MyISAM会将表存储在两个文件中:数据文件和索引文件,分别以.MYD和.MYI为扩展名。MyISAM表可以包含动态或者静态(长度固定)行。MyISAM表可以存储的行记录数,一般受限于可用的磁盘空间,或者操作系统中单个文件的最大尺寸。
  4. 加锁与并发:MyISAM对整张表加锁,而不是针对行。读取时会对需要读到的所有表加共享锁,写入时咋对表加排他锁。但在表有读取查询的同时,也可以往表中插入新的记录(这被称为并发插入,CONCURRENT INSERT)。
  5. 修复:对于MyISAM表,MySQL可以手工或自动执行检查和修复操作,但这里说的修复和事务恢复以及崩溃恢复是不同的概念。执行表的修复可能导致一些数据丢失,而且修复操作是非常慢的。可以通过CHECK TABLE table_name检查表的错误,如果有错误可以通过执行REPAIR TABLE table_name进行修复。另外,如果MySQL服务器已经关闭,也可以通过myisamchk命令行工具进行检查和修复操作。
  6. 索引特性:对于MyISAM表,即使是BLOB和TEXT等长字段,也可以基于其前500个字符创建索引。MyISAM也支持全文索引,这是一种基于分词创建的索引,可以支持复杂的查询。
  7. 延迟更新索引键(Delayed Key Write):创建MyISAM表的时候,如果指定了DELAY_KEY_WRITE选项,在每次修改执行完成时,不会立刻将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区(in-memory key buffer),只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入到磁盘。这种方式可以极大地提升写入性能,但是在数据库或者主机崩溃时会造成索引损坏,需要执行修复操作。延迟更新索引键的特性,可以在全局设置,也可以为单个表设置。
  8. MyISAM压缩表:如果表在创建并导入数据以后,不会再进行修改操作,那么这样的表或许适合采用MyISAM压缩表。可以使用myisampack对MyISAM表进行压缩。压缩表可以极大地减少磁盘空间占用,因此也可以减少磁盘I/O,从而提升查询性能。压缩表也支持索引,但索引也是只读的。
  9. MyISAM引擎设计简单,数据以紧密格式存储,所以在某些场景下的性能很好。但MyISAM最典型的性能问题还是表锁

1.5.3 MySQL內建的其它存储引擎

  1. Archive引擎
  2. Balckhole引擎
  3. CSV引擎
  4. Federated引擎
  5. Memory引擎
  6. Merge引擎
  7. NDB集群引擎

1.5.4 第三方存储引擎

  1. OLTP类引擎
  2. 面向列的存储引擎
  3. 社区存储引擎

1.5.5 选择合适的引擎

除非万不得已,否则建议不要混合使用多种存储引擎。
如果应用需要不同的存储引擎,请先考虑以下几个因素

  • 事务:如果应用需要事务支持,那么InnoDB(或者XtraDB)是目前最稳定并且经过验证的选择。如果不需要事务,并且主要是SELECTINSERT操作,那么MyISAM是不错的选择。一般日执行的应用比较符合这一特性。
  • 备份:备份的需求也会影响存储引擎的选择。如果可以定期地关闭服务器来执行备份,那么备份的因素可以忽略。反之,如果需要在线热备份,那么选择InnoDB就是基本的要求。
  • 崩溃恢复:数据量比较大的时候,系统崩溃后如何快速地恢复是一个需要考虑的问题。相对而言,MyISAM崩溃后发生损坏的概率比InnoDB要高很多,而且恢复速度也要慢。因此,即使不需要事务支持,很多人也选择InnoDB引擎,这是一个非常重要的因素。
  • 特有的特性:有些应用可能依赖一些存储引擎所独有的特性或者优化。

存储引擎适用场景

  • 日志型应用
    MyISAM或者Archive存储引擎对这类应用比较合适,因为它们开销低,而且插入速度非常快。
  • 只读或者大部分情况下只读的表
    有些表的数据用于编制类目或者分列清单(如工作岗位、竞拍、不动产等),这种应用场景是典型的读多写少的业务。如果不介意MyISAM的崩溃恢复问题,选用MyISAM引擎是合适的。不过不能实现崩溃恢复,就意味着不能保证数据的安全性。(MyISAM只将数据写到内存中,然后等待操作系统定期将数据刷出到磁盘上。)仍然建议使用InnoDB存储引擎,随着应用压力的上升,MyISAM存储引擎可能会迅速恶化。各种锁争用、崩溃后的数据丢失等问题都会随之而来。

一个值得推荐的方式,是在性能测试环境模拟真实的环境,运行应用,然后拔下电源模拟崩溃测试。对崩溃恢复的第一手测试经验是无价之宝,可以避免真的碰到崩溃时手足无措。

不要轻易相信“MyISAM比InnoDB快”之类的经验之谈,这个结论往往不是绝对的。在很多我们已知的场景中,InnoDB的速度都可以让MyISAM望尘莫及,尤其是使用到聚簇索引,或者需要访问的数据都可以放入内存的应用。

  • 订单处理
    如果涉及订单处理,那么支持事务就是必要选项。半完成的订单是无法用来吸引用户的。另外一个重要的考虑点是存储引擎对外键的支持情况。InnoDB是订单处理类应用的最佳选择。

  • 电子公告牌和主题讨论论坛
    对于MySQL用户,主题讨论区是个很有意思的话题。当前有很多基于PHP和Perl的免费系统可以支持主题讨论。其中大部分的数据库操作效率不高,因为它们大多倾向于在一次请求中执行尽可能多的查询语句。另外还有部分系统设计为不采用数据库,当然也就无法利用到数据库提供的一些方便的特性。主题讨论区一般都有更新计数器,并且会为各个主题计算访问统计信息。多数应用只设计了几张表来保存所有的数据,所以核心表的读写压力可能非常大。为保证这些核心表的数据一致性,锁成为资源争用的主要因素。但大多数应用在中低负载时可以工作的很好。如果Web站点的规模迅速扩展,流量随之猛增,则数据库访问可能变得非常慢。此时一个典型的解决方案是更改为支持更高读写的存储引擎,但有时用户会返现这么做反而导致系统变得更慢了。是因为不同的SQL语句在不用的存储引擎中运行的效率不同。
    例如:SELECT COUNT(*) FROM table_name在InnoDB和MyISAM中运行的速度完全不同。

  • CD-ROM应用
    如果要发布一个基于CD-ROM或者DVD-ROM并且使用MySQL数据文件的应用,可以考虑使用MyISAM表或MyISAM压缩表,这样表之间可以隔离并且可以在不用介质上相互拷贝。MyISAM压缩表比未压缩的表要节约空间,但压缩是只读的,如果数据在只读介质的场景下,压缩表就是最佳的选择。

  • 大数据量
    如果数据量增长到10TB以上的级别,可能就需要建立数据仓库。Infobright是MySQL数据仓库最成功的解决方案。也有一些大数据库不适合Infobright,却可能适合TokuDB。

1.5.6 转换表的引擎

  1. ALTER TABLE
    ALTER TABLE table_name ENGINE = InnoDB/MyISAM
    执行时间长,MySQL会按行将数据从原表复制到一张新的表中,在复制期间可能会消耗系统所有的I/O能力,同时原表会加上读锁。
  2. 导出和导入
    使用mysqldump工具将数据导出到文件,然后修改文件里CREATE TABLE语句的存储引擎选项,同时需要修改表名,同一个数据库就算是不同的存储引擎也不能存在相同的表名。
  3. 创建和查询(CREATE和SELECT)
    结合了1的高效和2的安全。不需要导出整个表的数据,而是先创建一个新的存储引擎的表,然后利用INSERT···SELECT语法来导数据。数据量很大的话,可以分批操作,操作完成后,新表就是原表的一个全量复制。Percona Toolkit提供了一个pt-online-schema-change工具(基于Facebook的在线schema变更技术),可以较简单、方便地实现表引擎的转换,避免手工操作可能导致的失误和繁琐。
CREATE TABLE innodb_table LIKE myisam_table;
ALTER TABLE innodb_table ENGINE = InnoDB;
INSERT INTO innodb_table SELECT * FROM myisam_table;

1.8 总结

  • 如果能理解MySQL在存储引擎和服务层之间处理查询时如何通过API来回交互,就能抓住MySQL的核心基础架构的精髓。

你可能感兴趣的:(mysql,高性能)