MySQL架构

逻辑架构

1、常规的C/S架构中的最外层:鉴权、连接处理

2、解析查询、优化 (优化器会向存储引擎询问执行操作的成本和表数据的统计信息)

3、存储引擎

==*== 在5.7.2 版本开始将查询缓存层标记为弃用,8.0版本中完全被移除。(为什么被弃用:随着并发性要求的增高,查询缓存层起不到太大的作用。)缓存重心迁移到Redis或者Memcached。

并发控制

多个线程同时对数据进行读写就会遇到并发的问题。

处理上述的读写访问的系统,一种经典的解决方案是:实现一种共享锁/排他锁,也叫做读锁/写锁的锁系统。

读锁是共享的,不互相阻塞的,一个资源可以被多个客户端同时访问;写锁是排他的,也就是说一个写锁即会阻塞其他的写锁,也会阻塞读锁,防止一个客户端读取正在写入的资源。

锁的粒度

如果要进一步的提高并发性,就要确保锁定的对象是具有选择性的,尽量只锁定需要修改的数据,而不是所有的资源。

锁定策略

在执行锁的时候,也会产生开销,比如获取锁, 释放锁,检查锁是否空闲。对锁的管理和对数据的读写需要进行平衡。

MySQL对锁做出的平衡

MySQL的不同的存储引擎给出了不同的锁策略和不同的锁粒度。锁粒度的控制可以优化某个场景的性能,但是针对其他的场景可能会导致负优化。

MySQL中比较重要的两种锁策略

表锁:table lock 是 MySQL 中最基本也是开销最小的锁策略。

行锁:rowlock 可以最大程度的支持并发,也带来了最大的锁开销,比如谁拥有了这些锁,拥有了多长时间,以及何时清理行级别锁。

行级锁是存储引擎实现的,不是服务器实现的,MySQL Innodb 引擎锁介绍

事务

事务就是一组SQL语句作为工作单元以原子方式进行处理。

事务的特性:ACID 原子性:Atomicity、一致性:Consistency、隔离性:Isolation、持久性:Durability

原子性:Atomicity 一个事务中的SQL要么成功要么失败。

一致性:Consistency 从一个一致性状态转移到下一个一致性状态,如果事务没有被提交,一致性状态不应该改变。

隔离性:Isolation 通常来说一个事务未提交以前对其他的事务都是不可见的,这是隔离性带来的结果。但是会有不同的隔离级别,所以通常情况下是不可见的,有时候也会可见。

持久性:Durability 事务一旦提交所有的数据都会被保存到数据库中,不管程序奔溃或者系统奔溃数据也不会丢失。持久性也分为很多的级别,比如三地两中心等物理上的持久化。

隔离级别

READ UNCOMMITTED(未提交读) 在事务中可以看到其他的事务未提交的更改,这个隔离级别会有很多的问题,即读取未提交的数据也叫做脏读,而且从性能上来说也不会比其他的隔离级别好太多,所以这种隔离级别不经常使用。

READ COMMITTED(提交读)这是大多数数据库的默认隔离级别,但是MySQL不是。一个事物可以看到其他事务已提交的数据,但是该事务在提交之前所做的操作对于其他的事务是不可见的,这个隔离级别仍然有着不可重复读的问题。这意味着同一事务中两次执行相同语句,可能会看到不同的数据结果。

REPEATABLE READ(可重复读)是默认的隔离级别。 这种隔离级别解决了上一个隔离级别的不可重复读的问题,保证了在同一个事务中多次读取相同行数据的结果是一样的。但是理论上还是无法解决幻读的问题。所谓的幻读就是当前的事务在读取某个范围的数据时,其他的事务在该范围中插入了数据,导致该事务读取时出现了幻行。MySQL是通过多版本并发控制(MVCC)解决的幻读的问题。

SERIALIZABLE(可串行化)是最高的隔离级别,通过强制事务按照顺序执行,使事物之间不可能发生发生冲突,从而解决了前面说的幻读问题。原理就是在读取的每一行数据上面都加锁,但是也带来了大量的锁超时和性能问题,实际中很少使用。

以上的各个隔离级别带来的问题和解决的问题:

隔离级别 脏读 不可重复度 幻读 加锁读
READ UNCOMMITTED(未提交读)
READ COMMITTED(提交读)
REPEATABLE READ(可重复读)
SERIALIZABLE(可串行化)

死锁

InnoDB引擎处理死锁的方式是将持有最少行级排他锁的事务回滚(这是一种最容易回滚的近似算法)。

锁的行为和顺序适合存储引擎相关的,同样的查询语句在不同的存储引擎上也会表现出不同的结果。产生死锁的双重原因;有些是真正的数据冲突,这种情况通常很难避免,有些则是存储引擎的实现方式导致的。

事务日志

事务日志有助于提高事务的效率,存储引擎只需要修改内存中的数据副本,而不需要修改磁盘中的表的数据,然后再把更改的记录持久化到事务日志中,事务日志会被持久化到磁盘上,因为事务日志是追加写操作,在磁盘上是一块顺序IO,所以写入事务日志是相对快的,最后回一个后台进程将事务日志同步到磁盘中的表中。因此,大多数使用这种技术(write-ahead logging,预写式日志)的存储引擎修改数据最终需要写入磁盘两次。

MySQL中的事务

默认情况下,insert、update、delete 会隐士的包装在一个事务中,执行后立即提交,这种称为自动提交。

还有一种情况, 在一个事务执行过程中,当执行了其他的DDL语句或者发生了Lock Table,都会造成事务提前提交。

具体哪些会导致事务的提前提交:

  • DDL 语句
  • DCL 语句
  • 在当前事务中开启一个新的事务
  • 锁表或者解锁 lock tables、unlock tables
  • MySQL从机上执行的一些操作如 start slave、stop slave、reset slave 以及 change master to 等语句也会隐式提交事务。
  • 其他的一下操作如刷新权限(flush privileges)、优化表(optimize table)、修复表(repair table)等操作,也会导致事务的隐式提交。

MVCC

MySQL 的事务型存储引擎使用的都是不是简单的行级别锁,而是结合MVCC技术,其他的数据库像Oracle和PostgreSQL也实现了MVCC,但是实现细节可能不相同,

MVCC的实现方式:

主要参与的成员有事务ID,Undo 日志 Redo 日志。

1、在开启事务时,存储引擎就会分配一个事务ID,该ID在事务首次读取任何数据时分配。

2、在该事务修改数据时,在 Undo 日志中写入一天如何恢复该更改的日志,并且事务的回滚指针指向刚刚写入undo日志中的记录。==(这就是事务如何在需要时执行回滚的方法)==。

3、当不同的会话读取聚集主键索引记录时,InnoDB会将该记录的事务ID和该会话的读取视图进行比较,如果当前状态下的记录不应可见(更改它的事务尚未提交),那么Undo日志记录将被跟踪并应用,直到会话达到一个符合可见条件的事务ID。这个过程可以一直循环到完全删除这一行的 Undo 记录,然后向读取视图发出这一行不存在的信号。

所有的 Undo 日志也会写入 Redo 日志。

在记录中保存这些额外的数据的结果就是,大部分查询都不需要在加锁。缺点就是存储引擎在每一行中存储更多的数据,

==MVCC仅适用于REPEATABLE READ和READ COMMITTED隔离级别。==

主从复制

MySQL 提供了一个原生方式来讲写操作复制到其他的节点,源节点为每个副本节点提供一个线程,当写入发生时,线程就会被唤醒,将新的数据复制到副本节点。(通过二进制日志的方式 bin log)

原子DDl

MySQL8引入了原子定义更改,意味着数据定义语句现在要么全部成功完成,要么全部失败回滚,是通过创建DDL特定的 Undo 和 Redo 日志来实现的。