高性能MySQL(第三版)第一章: MySQL架构与历史

MySQL架构与历史

  • 1 MySql架构
    • 1.1 MySql逻辑架构
    • 1.2 读写锁
    • 1.3 锁粒度
    • 1.4 死锁
    • 1.5 事务
    • 1.6 事务的隔离级别
    • 1.7 多版本并发控制(MVCC)
  • 2 存储引擎
    • 2.1特点
    • 2.2 MyISAM
      • 2.2.1 特性
      • 2.2.2 MyISAM压缩
      • 2.2.3 MyISAM适用场景
    • 2.3 InnoDB
      • 2.3.1 特性
      • 2.3.2 适用场景
    • 2.4 MyISAM与InnoDB比较
    • 2.5 存储引擎选择的基本原则

1 MySql架构

1.1 MySql逻辑架构

高性能MySQL(第三版)第一章: MySQL架构与历史_第1张图片
每个虚线框为一层,总共三层。
第一层,客户端层:为请求做连接处理,授权认证,安全等。
第二层,服务层:查询解析,分析,优化,缓存,提供内建函数;存储过程,触发器,视图。
第三层,存储引擎层,不光做存储和提取数据,而且针对特殊数据引擎还要做事务处理。

1.2 读写锁

读锁(read lock):也称共享锁,读锁是共享的,或者说是相互不阻塞的。多个线程在同时可以读取同一个资源而不相互干扰。
写锁(write lock):也称排他锁,写锁是排他的,也就是说,一个写锁会阻塞其他的写锁和读锁。

1.3 锁粒度

锁粒度也称锁的级别,分为表锁行锁
锁策略:就是在锁的开销和数据的安全性之间寻求平衡。
表锁(table lock):是MySQL中最基本的锁策略,也是成本最小的锁策略。一个用户对表进行写操作时,会锁定整张表。这时会阻塞其他用户对表的所有读写操作。读锁之间是不相互阻塞的。写锁比读锁有更高的优先级,因此一个写锁请求可能会被插入到读锁队列的前面。(尽管存储引擎可以自己管理锁,但是MySql服务器会为诸如ALTER TABLE之类的语句使用表锁而忽略存储引擎的锁机制)
行级锁(row lock):行级锁可以最大程度的地支持并发处理,同时也带来了最大的锁开销。行级锁只会在存储引擎层实现。

1.4 死锁

死锁是指两个或者多个事务在同一个资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。当多个事务试图以不同的顺序锁定资源时,可能会造成死锁。多个事务同时锁定同一个资源,也会产生死锁。
高性能MySQL(第三版)第一章: MySQL架构与历史_第2张图片
如果凑巧,两个事务都执行了第一条更新语句,并且都锁定了这一行数据,接着两个事务试着去执行第二行语句时,却发现该行数据已经被对方锁定,同时自身又持有对方的锁,出现死锁现象。

解决办法

  • InnoDB目前处理死锁的方式是,将持有最少行级排他锁的事务进行回滚
  • 还有的是当查询的时间达到锁等待超时的设定后,放弃锁的请求,这种方式通常来说不太好。

锁的行为和顺序与存储引擎相关。死锁产生的原因有两种,一种是真正的数据冲突,一种是由于存储引擎的实现方式导致的。

1.5 事务

事务(transaction):是一组原子性的SQL查询,或者说是一个独立的工作单元。事务内的语句,要么就全部执行,要么就全都不执行
事务特性(ACID):

  1. 原子性(Atomicity):原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
  2. 一致性(Consistency):一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
    拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
  3. 隔离性(Isolation):隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
    即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
  4. 持久性(Durability):持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
    例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。

1.6 事务的隔离级别

数据库事务的隔离级别有4种,由低到高分别为未提交读(Read uncommitted )、提交读(Read committed )、重复读(Repeatable read )、序列化(Serializable) 。而且,在事务的并发操作中可能会出现脏读,不可重复读,幻读。

  1. 未提交读(Read uncommitted )
    顾名思义,未提交读就是一个事务可以读取另一个未提交事务的数据
    事例:老板要给程序员发工资,程序员的工资是3.6万/月。但是发工资时老板不小心按错了数字,按成3.9万/月,该钱已经打到程序员的户口,但是事务还没有提交,就在这时,程序员去查看自己这个月的工资,发现比往常多了3千元,以为涨工资了非常高兴。但是老板及时发现了不对,马上回滚差点就提交了的事务,将数字改成3.6万再提交。
    分析:实际程序员这个月的工资还是3.6万,但是程序员看到的是3.9万。他看到的是老板还没提交事务时的数据。这就是脏读。
    脏读(Dirty Read):事务可以读取未提交的数据。
  2. 提交读(Read committed )
    提交读也叫nonrepeatable read(不可重复读)就是一个事务要等另一个事务提交后才能读取数据。一个事务从开始到提交之前,所做的任何修改对其他事务都是不可见的。

事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候!!程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。程序员就会很郁闷,明明卡里是有钱的…

分析:这就是读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读(两次执行一样查询,得到不同的结果)

  1. 可重复读(Repeatable read)
    可重复读,就是在开始读取数据(事务开启)时,不再允许修改操作。Mysql的默认隔离级别是Repeatable read

事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有3.6万。这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。

分析:重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作

什么时候会出现幻读
事例:程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉,这就是幻读。
幻读(phantom Read):指的是一个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(phantom row)
InnoDB存储引擎通过Next-Key Locking解决了幻读的问题

  1. 序列化(Serializable)
    Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。Serializable 在读取的每一行数据上都加锁。
隔离级别 读数据一致性 脏读 不可重复 读 幻读
未提交读(Read uncommitted) 最低级别
已提交读(Read committed) 语句级
可重复读(Repeatable read) 事务级
可序列化(Serializable) 最高级别,事务级

事务常见问题
更新丢失(Lost Update)
原因:当多个事务选择同一行操作,并且都是基于最初选定的值,由于每个事务都不知道其他事务的存在,就会发生更新覆盖的问题。类比github提交冲突。

脏读(Dirty Reads)
原因:事务A读取了事务B已经修改但尚未提交的数据。若事务B回滚数据,事务A的数据存在不一致性的问题。

不可重复读(Non-Repeatable Reads)
原因:事务A第一次读取最初数据,第二次读取事务B已经提交的修改或删除数据。导致两次读取数据不一致。不符合事务的隔离性。

幻读(Phantom Reads)
原因:事务A根据相同条件第二次查询到事务B提交的新增数据,两次数据结果集不一致。不符合事务的隔离性。指的是一个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(phantom row)

幻读和脏读有点类似
脏读是事务B里面修改了数据,
幻读是事务B里面新增了数据。

1.7 多版本并发控制(MVCC)

MVCC是行级锁的一个变种,它在很多情况下都避免了加锁操作,因此开销更低。虽然实现机制不同,但大都实现了非阻塞的读操作,写操作也只锁定必要的行。MVCC是通过保存数据在某个时间点的快照来实现的。
InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的,这两个列,分别保存了这个行的创建时间,一个保存的是行的删除时间(或过期时间)。这里存储的并不是实际的时间值,而是系统版本号(可以理解为事务的ID),每开始一个新的事务,系统版本号就会自动递增,事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的行记录的版本号做比较。因此InnoDB使用MVCC解决了幻读的问题。
高性能MySQL(第三版)第一章: MySQL架构与历史_第3张图片
MVCC只在repeatable read和read committed两个隔离级别下工作,其他两个隔离级别都与MVCC不兼容。read uncommitted 总是读取最新的行,serializable则会对多有读取到的行都加锁。

2 存储引擎

2.1特点

Mysql区别于其他数据库的一个重要特点是其插件式的表存储引擎,注意,存储引擎是基于表的,而不是基于数据库。

2.2 MyISAM

2.2.1 特性

  • 加锁与并发
    MyISAM对整张表加锁读取时会对所有需要读到的表加共享锁,写入时则对表加上排他锁。但是在表有读取操作时,也可以往表中插入新的记录。
  • 修复
    对于MyISAM表,MySQL可以手工或者自动执行检查和修复,但是与事务恢复以及崩溃恢复不同。执行表的修复可能会丢失数据并且执行非常慢。
  • 索引特性
    即使是blob或者text等长字段,也可以选取前500个字符创建索引。MyISAM也支持全文索引,可以支持复杂查询。
  • 延迟更新索引键
    创建MyISAM表的时候,如果指定了DELAY_ KEY_ WRITE选项,在每次修改执行完成时,不会立刻将修改的索引数据写人磁盘,而是会写到内存中的键缓冲区(in-memorykey buffer),只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入到磁盘。这种方式可以极大地提升写入性能,但是在数据库或者主机崩溃时会造成索引损坏,需要执行修复操作。延迟更新索引键的特性,可以在全局设置,也可以为单个表设置。
  • 不支持事务
    MyISAM存储引擎不支持事务,所以对事务有要求的业务场景不能使用。
  • 只会缓存索引
    MyISAM可以通过key_buffer缓存以大大提高访问性能减少磁盘IO,但是这个缓存区只会缓存索引,而不会缓存数据

2.2.2 MyISAM压缩

如果表在创建并且导入数据后,不再进行修改操作,那么可以采取MyISAM压缩表。压缩表可以极大减少磁盘空间占用,因此可以减少磁盘I/O,从而提升查询性能。压缩表也支持索引,但索引是只读的。

2.2.3 MyISAM适用场景

  • 不需要事务支持(不支持)
  • 并发相对较低(锁定机制问题)
  • 数据修改相对较少(阻塞问题)
  • 以读为主,数据一致性要求不是非常高

一、MyISAM引擎的特点
1、不支持事物(事物是指逻辑上的一组操作,组成这组操作的各个单元,要么全成功要么全失败)
2、表级锁定,数据更新时锁定整个表,其锁定级别是表级锁定,这虽然可以染锁定的实现成本很小但是也同时大大降低了其并发性能
3、读写互相阻塞:不仅会在写入的时候阻塞读取,MyISAM还会在读取的时候阻塞写入,但读本身并不会阻塞另外的读
4、只会缓存索引:MyISAM可以通过key_buffer_size缓存索引,以大大提高读取的时候阻塞写入,但读本身并不会阻塞另外的读。(key_buffer_size = 16M)
5、读取速度轻快,占用资源相对较少
6、不支持外键约束、但支持全文索引

2.3 InnoDB

2.3.1 特性

  • 具有较好的事务支持:支持4个事务隔离级别,支持多版本读 行级锁定:通过索引实现,全表扫描仍然会是表锁,注意间隙锁的影响。
  • 读写阻塞与事务隔离级别相关 具有非常高效的缓存特性:能缓存索引,也能缓存数据 整个表和主键以Cluster方式存储,组成一颗平衡树。
  • 所有Secondary Index都会保存主键信息。
  • 与myisam相比Innodb存储引擎支持事务,支持行锁和外键,支持非锁定读(默认读操作不会产生锁)。还可以通过MVCC(多版本并发控制)来获得高并发性,实现了标准的4中隔离级别,默认为Repeatable read级别。还通过next—key Locking的策略来避免幻读(phantom)现象的产生,Innodb还支持插入缓存(insert buffer)、二次写(double write)、自适应哈希索引(adaptive hash index)、预读(read ahead)等功能。
  • Innodb中每张表的存储都是按照主键的顺序进行存放的,如果没有定义主键,Innodb会为每一行生成一个6字节的ROW
    ID,以此作为主键。

2.3.2 适用场景

  • 需要事务支持(具有较好的事务特性)
  • 行级锁定对高并发有很好的适应能力,但需要确保查询是通过索引完成
  • 数据更新较为频繁的场景
  • 数据一致性要求较高
  • 硬件设备内存较大,可以利用InnoDB较好的缓存能力来提高内存利用率,尽可能减少磁盘 IO

2.4 MyISAM与InnoDB比较

  1. 存储结构
    MyISAM:每个MyISAM在磁盘上存储成三个文件。第一个文件的名字以表的名字开始,扩展名指出文件类型。.frm文件存储表定义。数据文件的扩展名为.MYD (MYData)。索引文件的扩展名是.MYI (MYIndex)。
    InnoDB:所有的表都保存在同一个数据文件中(也可能是多个文件,或者是独立的表空间文件),InnoDB表的大小只受限于操作系统文件的大小,一般为2GB。

  2. 存储空间
    MyISAM:可被压缩,存储空间较小。支持三种不同的存储格式:静态表(默认,但是注意数据末尾不能有空格,会被去掉)、动态表、压缩表。
    InnoDB:需要更多的内存和存储,它会在主内存中建立其专用的缓冲池用于高速缓冲数据和索引。

  3. 可移植性、备份及恢复
    MyISAM:数据是以文件的形式存储,所以在跨平台的数据转移中会很方便。在备份和恢复时可单独针对某个表进行操作。
    InnoDB:免费的方案可以是拷贝数据文件、备份 binlog,或者用 mysqldump,在数据量达到几十G的时候就相对痛苦了。

  4. 事务支持
    MyISAM:强调的是性能,每次查询具有原子性,其执行数度比InnoDB类型更快,但是不提供事务支持。
    InnoDB:提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。

  5. AUTO_INCREMENT
    MyISAM:可以和其他字段一起建立联合索引。引擎的自动增长列必须是索引,如果是组合索引,自动增长可以不是第一列,他可以根据前面几列进行排序后递增。
    InnoDB:InnoDB中必须包含只有该字段的索引。引擎的自动增长列必须是索引,如果是组合索引也必须是组合索引的第一列。

  6. 表锁差异
    MyISAM:只支持表级锁,用户在操作myisam表时,select,update,delete,insert语句都会给表自动加锁,如果加锁以后的表满足insert并发的情况下,可以在表的尾部插入新的数据。
    InnoDB:支持事务和行级锁,是innodb的最大特色。行锁大幅度提高了多用户并发操作的新能。但是InnoDB的行锁,只是在WHERE的主键是有效的,非主键的WHERE都会锁全表的。

  7. 全文索引
    MyISAM:支持 FULLTEXT类型的全文索引
    InnoDB:不支持FULLTEXT类型的全文索引,但是innodb可以使用sphinx插件支持全文索引,并且效果更好(在InnoDB 1.2 也就是MySQL 5.6版本后InnoDB也支持全文索引)。

  8. 表主键
    MyISAM:允许没有任何索引和主键的表存在,索引都是保存行的地址。
    InnoDB:如果没有设定主键或者非空唯一索引,就会自动生成一个6字节的主键(用户不可见),数据是主索引的一部分,附加索引保存的是主索引的值。

  9. 表的具体行数
    MyISAM:保存有表的总行数,如果select count() from table;会直接取出出该值。
    InnoDB:没有保存表的总行数,如果使用select count(
    ) from table;就会遍历整个表,消耗相当大,但是在加了wehre条件后,myisam和innodb处理的方式都一样。

  10. CURD操作
    MyISAM:如果执行大量的SELECT,MyISAM是更好的选择。
    InnoDB:如果你的数据执行大量的INSERT或UPDATE,出于性能方面的考虑,应该使用InnoDB表。DELETE 从性能上InnoDB更优,但DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的删除,在innodb上如果要清空保存有大量数据的表,最好使用truncate table这个命令。

  11. 外键
    MyISAM:不支持
    InnoDB:支持
    通过上述的分析,基本上可以考虑使用InnoDB来替代MyISAM引擎了,原因是InnoDB自身很多良好的特点,比如事务支持、存储 过程、视图、行级锁定等等,在并发很多的情况下,相信InnoDB的表现肯定要比MyISAM强很多。另外,任何一种表都不是万能的,只用恰当的针对业务类型来选择合适的表类型,才能最大的发挥MySQL的性能优势。如果不是很复杂的Web应用,非关键应用,还是可以继续考虑MyISAM的,这个具体情况可以自己斟酌。

为什么在一定程度上MyISAM比innodb读取数据要快?
1.innodb不仅要缓存数据还要缓存索引,MyISAM只需要缓存索引;
2.innodb寻址要映射到块,再到行,MYISAM记录的直接是文件的OFFSET,定位比INNODB要快
3.INNODB还需要维护MVCC一致;虽然在查询的场景可能没有,但还是需要去检查和维护,而MyISAM由于没有了多行,不需要判断 选取可见的那行数据

InnoDB:通过为每一行记录添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。但是InnoDB并不存储这些事件发生时的实际时间,相反它只存储这些事件发生时的系统版本号。这是一个随着事务的创建而不断增长的数字。每个事务在事务开始时会记录它自己的系统版本号。每个查询必须去检查每行数据的版本号与事务的版本号是否相同。让我们来看看当隔离级别是REPEATABLEREAD时这种策略是如何应用到特定的操作的:
  SELECT InnoDB必须每行数据来保证它符合两个条件:
  1、InnoDB必须找到一个行的版本,它至少要和事务的版本一样老(也即它的版本号不大于事务的版本号)。这保证了不管是事务开始之前,或者事务创建时,或者修改了这行数据的时候,这行数据是存在的。
  2、这行数据的删除版本必须是未定义的或者比事务版本要大。这可以保证在事务开始之前这行数据没有被删除

2.5 存储引擎选择的基本原则

采用MyISAM引擎

  • R/W > 100:1 且update相对较少
  • 并发不高
  • 表数据量小
  • 硬件资源有限

采用InnoDB引擎

  • R/W比较小,频繁更新大字段
  • 表数据量超过1000万,并发高
  • 安全性和可用性要求高

采用Memory引擎

  • 有足够的内存
  • 对数据一致性要求不高,如在线人数和session等应用
  • 需要定期归档数据

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