针对这个问题,各方面文档很多,基本都是说mysql是一个开源的关系型数据库管理系统,其实,归根到底,说的通俗易懂一点,mysql是一个按照标准数据结构和格式存储的文件系统。
这是一个很常见的面试题,其实就是让我们说出并比较InnoDB和MyISAM。MySQL5.5之前,默认的存储引擎是MyISAM,之后都改为InnoDB了,具体你使用的mysql的存储引擎是什么,以及mysql存储引擎有哪些,我们可以在mysql上执行命令show engines;查看:
其实看完2.1的图片,我们基本能够对InnoDB和MyISAM进行一个直观的比较了:
InnoDB引擎特点:
1.支持事务:支持4个事务隔离界别,支持多版本读。
2.行级锁定(更新时一般是锁定当前行):通过索引实现,全表扫描仍然会是表锁,注意间隙锁的影响。
3.读写阻塞与事务隔离级别相关(有多个级别,这就不介绍啦~)。
4.具体非常高效的缓存特性:能缓存索引,也能缓存数据。
5.整个表和主键与Cluster方式存储,组成一颗平衡树。(了解)
6.所有SecondaryIndex都会保存主键信息。(了解)
7.支持分区,表空间,类似oracle数据库。
8.支持外键约束,不支持全文索引(5.5之前),以后的都支持了。
9.和MyISAM引擎比较,InnoDB对硬件资源要求还是比较高的。
小结:三个重要功能:Supports transactions,row-level locking,and foreign keys
MyISAM引擎特点:
1.不支持事务
事务是指逻辑上的一组操作,组成这组操作的各个单元,要么全成功要么全失败。
2.表级锁定
数据更新时锁定整个表:其锁定机制是表级锁定,也就是对表中的一个数据进行操作都会将这个表锁定,其他人不能操作这个表,这虽然可以让锁定的实现成本很小但是也同时大大降低了其并发性能。
3.读写互相阻塞
不仅会在写入的时候阻塞读取,MyISAM还会再读取的时候阻塞写入,但读本身并不会阻塞另外的读。
4.只会缓存索引
MyISAM可以通过key_buffer_size的值来提高缓存索引,以大大提高访问性能减少磁盘IO,但是这个缓存区只会缓存索引,而不会缓存数据。
5.读取速度较快
占用资源相对较少
6.不支持外键约束,但只是全文索引
7.MyISAM引擎是MySQL5.5版本之前的默认引擎,是对最初的ISAM引擎优化的产物。
事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消。简单理解就是:一系列相关的操作,要么全部成功,要么全部不执行。
事务具有四个特征:原子性( Atomicity )、一致性( Consistency )、隔离性( Isolation )和持续性( Durability )。这四个特性简称为 ACID 特性:
1 、原子性。事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做
2 、一致性。事 务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。
3 、隔离性。一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
4 、持续性。也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read-uncommitted) | 是 | 是 | 是 |
不可重复读(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
上面所说的存在的问题,都是针对并发场景下,多个事务之间并行操作,由于mysql设置的隔离级别而可能出现的问题,下面就一一进行详细说明: |
深入探究MVCC之前,我们先要对当前读和快照读有一个大概的了解和概念区分:
读-读:不存在任何问题,也不需要并发控制
读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失
MVCC就是为了实现读-写冲突(快找读和数据库的当前读冲突)不加锁,提升数据库性能设计的。而悲观锁或者乐观锁可以解决写写冲突,这样针对不同的场景,可以使用mvcc+乐观锁或者mvcc+悲观锁来实现数据库的事务,同时保障数据库的性能。
MVCC的实现原理主要是依赖mysql每条记录中的 3个默认字段(DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID),和Read View(读视图)以及 undo日志来实现的。三个默认字段具体含义:
DB_TRX_ID:
6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID
DB_ROLL_PTR:
7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)
DB_ROW_ID:
6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引
实际还有一个删除flag隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除flag变了
针对MVCC实现,很多文档只会讲到隐藏的事务id列和删除事务id列(其实,也没有删除事务id列,mysql进行delete时,会给对象打上delete flag,标记为删除,最终的删除操作是purge线程完成的),然后可见性是满足两个条件:1.事务id列小于当前事务id;2.删除事物id为空或者大于当前事务id的列,其实光靠这两列是没法保证可重复读的。比如A事务先开始,事务id为10,然后B事务开始,事务id为11,这个时候按照上面的可见性条件,A事务对某条记录的更新,提交后对于B事务都是可见的。其实这种是为了方便你理解MVCC隐藏列和大概的MVCC机制。
在 InnoDb 存储引擎里,每个数据页中都会有两个虚拟的行记录,用来限定记录的边界,分别是:Infimum Record 和 Supremum Record,Infimum 是比该页中任何记录都要小的值,而 Supremum 比该页中最大的记录值还要大,这两条记录在创建页的时候就有了,并且不会删除/font>。
一条sql的执行历程:
mysql 主要分为两层,与客户端直接交互的是 server 层,包括连接的建立和管理、词法分析、语法分析、执行计划与具体 sql 的选择都是在 server 层中进行的,binlog 就是在 server 层中由 mysql server 实现的,而 innodb 作为具体的一个存储引擎,他通过 redolog 实现了 crash safe 的支持
log | 作用 | 存储内容 |
---|---|---|
redo log | mysql宕机之后进行数据页恢复 | 物理格式的日志,记录的是物理数据页面的修改的信息(数据库中每个页的修改),面向的是表空间、数据文件、数据页、偏移量等 |
bin log | 进行数据同步,比如主从备份,cannal同步 | 逻辑格式的日志,可以简单理解是记录执行过的事务中的sql语句 |
undo log | 用于事务回滚,同时可以提供多版本并发控制(MVCC)下的读 | 逻辑格式的日志,在执行undo的时候,仅仅是将数据从逻辑上恢复至事务之前的状态,而不是从物理页面上操作实现的 |
提交事务时,三个log写入顺序是:开启事务 → ddl&dml操作 → 生成反向sql写入undo log → 写redo log(状态为prepare) → 写binlog → 提交事务commit阶段,成功后将redo log状态改为commit
undo log主要分为两种:
purge
- 从前面的分析可以看出,为了实现InnoDB的MVCC机制,更新或者删除操作都只是设置一下老记录的deleted_bit,并不真正将过时的记录删除。
- 为了节省磁盘空间,InnoDB有专门的purge线程来清理deleted_bit为true的记录。为了不影响MVCC的正常工
作,purge线程自己也维护了一个read view(这个read view相当于系统中最老活跃事务的read view);如果某个
记录的deleted_bit为true,并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定是可以被安全
清除的。
当前读操作时,数据库会先为该行加锁,然后把该行原始数据拷贝到undo log中,如果发现该行记录已经有undo log了,那么最新的旧数据作为链表的表头,插在该行记录的undo log最前面,修改完成之后,改隐藏字段的事务ID,回滚指针指向刚刚拷贝到undo log的副本记录。这样就形成了一个历史数据回溯的链表。MVCC模式下,在读视图确定可以看见的记录行时,可以通过undo log链表拉取对应的历史记录数据版本。
mysql 有一个基本的技术理念,那就是 WAL,即 Write-Ahead Logging,先写日志,再写磁盘,从而保证每一次操作都有据可查,这里所说的“先写日志”中的日志就包括 innodb 的 redolog。
redolog 与持续向后添加的 binlog 不同,他只占用预先分配的一块固定大小的磁盘空间,在这片空间中,redolog 采用循环写入的方式写入新的数据。
RingBuffer的好处:
如果你不知道Read View(读视图),去了解MVCC,基本都是没有触摸到真正的MVCC实现原理。
Read View就是事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)
Read View主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。
不同的隔离级别,生成ReadView的时机不同:
READ COMMITTED :在事务中每一次执行快照读时生成ReadView。
REPEATABLE READ:仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。
Read View遵循一个可见性算法,主要是将要被修改的数据的最新记录中的DB_TRX_ID(即当前事务ID)取出来,与系统当前其他活跃事务的ID去对比(由Read View维护),如果DB_TRX_ID跟Read View的属性做了某些比较,不符合可见性,那就通过DB_ROLL_PTR回滚指针去取出Undo Log中的DB_TRX_ID再比较,即遍历链表的DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的DB_TRX_ID, 那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新老版本。
trx_list(当前视图的活跃事务列表)
一个数值列表,用来维护Read View生成时刻系统正活跃的事务ID
up_limit_id
记录trx_list列表中事务ID最小的ID
low_limit_id
ReadView生成时刻系统尚未分配的下一个事务ID,也就是目前已出现过的事务ID的最大值+1
索引其实是一种数据结构,能够帮助我们快速的检索数据库中的数
MySQL的索引是由存储引擎来实现的。由于存储引擎不同,所以具有不同的索引类型,如BTree索引,B+Tree索引,哈希索引,全文索引等。这里由于主要介绍BTree索引和B+Tree索引,我们平时使用最多的InnoDB引擎就是基于B+Tree索引的。
BTree(又称为B-Tree)是一个平衡搜索多叉树。BTree的结构如下图:
假设树的度为2d(d>1),高度为h,那么BTree有以下性质:
B+Tree是BTree的一种变种。B+Tree和BTree的不同主要在于:
一般在数据库系统或文件系统中使用的B+Tree结构都在经典B+Tree的基础上进行了优化,增加了顺序访问指针,结构大致如下:
创建索引的优点,最简单的就是可以大大提高系统的性能,具体如下:
也许会有人要问:增加索引有如此多的优点,为什么不对表中的每一个列创建一个索引呢?这种想法固然有其合理性,然而也有其片面性。虽然,索引有许多优点,但是,为表中的每一个列都增加索引,是非常不明智的。这是因为,增加索引也有许多不利的一个方面:
聚簇索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的就是整张表的行记录数据,也将聚集索引的叶子节点称为数据页。这个特性决定了索引组织表中数据也是索引的一部分,每张表只能拥有一个聚簇索引。
Innodb通过主键聚集数据,如果没有定义主键,innodb会选择非空的唯一索引代替。如果没有这样的索引,innodb会隐式的定义一个主键来作为聚簇索引。
非聚簇索引,又叫二级索引,或者辅助索引。非聚簇索引底层也是一颗B+树,只是叶子节点中保存的不是行记录数据,而是行的主键值。当通过二级索引查找行,存储引擎需要在二级索引中找到相应的叶子节点,获得行的主键值,然后使用主键去聚簇索引中查找数据行(覆盖索引),,也就是我们俗称的回表(如果查询的字段在索引上的某个字段,或者查询的是主键,则不需要回表)。
联合索引又叫复合索引,是指两个或更多个列上的索引。对于复合索引:Mysql从左到右的使用索引中的字段,遵循最左前缀匹配的原则,即最左优先,在检索数据时从联合索引的最左边开始匹配。例如索引是key index (a,b,c). 可以支持a | a,b| a,b,c 3种组合进行查找,但不支持 b,c进行查找 .当最左侧字段是常量引用时,索引就十分有效。
InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!
由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。
表级锁为表级别的锁定,会锁定整张表,可以很好的避免死锁,是 MySQL 中最大颗粒度的锁定机制。
间隙锁是一种加在两个索引之间的锁,或者加在第一个索引之前,或最后一个索引之后的间隙。
使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据。
间隙锁只阻止其他事务插入到间隙中,他们不阻止其他事务在同一个间隙上获得间隙锁,所以 gap x lock 和 gap s lock 有相同的作用。
Next-key锁是记录锁和间隙锁的组合,它指的是加在某条记录以及这条记录前面间隙上的锁
待补充
SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法。其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 id。在分布式系统中的应用十分广泛,且ID 引入了时间戳,基本上保持自增。
这种方案和UUID其实基本一致,大致来说是一种以划分命名空间的方案。
SnowFlake算法的优点:
(1)高性能高可用:生成时不依赖于数据库,完全在内存中生成。
(2)容量大:每秒中能生成数百万的自增ID。
(3)ID自增:存入数据库中,索引效率高。
SnowFlake算法的缺点:
依赖与系统时间的一致性,如果系统时间被回调,或者改变,可能会造成id冲突或者重复;
机器id的获取(10bit的workerId)
可以根据固定时间戳内生成的序列号推算数据量
其实算法不是唯一的,主要是思想,针对雪花算法的实践,可以参考美团leaf的实现,优化雪花算法和百度的UidGenerator实现
针对多表的join,MySQL官方文档指出,不支持Hash Join,也不支持Sort Merge Join,只支持 Nested Loop Join算法。当然,mysql针对Nested Loop Join也做了一定的优化,主要有下面三种:
Batched Key Access Join算法的工作步骤如下:
1) 将外部表中相关的列放入Join Buffer中。
2) 批量的将Key(索引键值)发送到Multi-Range Read(MRR)接口。
3) Multi-Range Read(MRR)通过收到的Key,根据其对应的ROWID进行排序,然后再进行数据的读取操作。
4) 返回结果集给客户端。
Batched Key Access Join配置:
开启 :SET global optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on'
修改buffer size参数: join_buffer_size