MySQL——内存结构和执行原理

目录

InnoDB架构

内存结构

Buffer Pool

Change Buffer

Adaptive Hash Index

Log Buffer

磁盘结构

System Tablespace

File-Per-Table Tablespaces

General Tablespaces

Temporary Tablespaces

Undo Tablespaces

SQL执行流程

MySQL通信方式

MySQL权限验证

执行流程

SQL接口

缓存模块

解析器

预处理器

优化器

存储引擎

执行引擎

后台线程


InnoDB架构

MySQL——内存结构和执行原理_第1张图片

内存结构

Buffer Pool

1.包含Adaptive Hash Index,Change Buffer,数据页,索引页,数据字典,undo页,插入缓存,锁信息等信息。

2.底层是B+树页的数据结构存储,内部是一个双向链表。页分为Free Page未使用的页,Clean Page数据未被修改过正在使用的页,Dirty Page数据与磁盘不一致的页。

3.在生产上通常将80%左右的内存分配给缓存池,通过参数: show variables like ‘innodb_buffer_pool_size’;设置。InnoDB以变种LRU算法管理缓冲池,并能够解决“预读失效”与“缓冲池污染”的问题;

 预读失效:由于预读(Read-Ahead),提前把页放入了缓冲池,但最终MySQL并没有从页中读取数据,称为预读失效。

要优化预读失效,思路是:

(1)让预读失败的页,停留在缓冲池LRU里的时间尽可能短;

(2)让真正被读取的页,才挪到缓冲池LRU的头部;

以保证,真正被读取的热数据留在缓冲池里的时间尽可能长。具体方法是:

(1)将LRU分为两个部分:

         新生代(new sublist)

         老生代(old sublist)

(2)新老生代收尾相连,即:新生代的尾(tail)连接着老生代的头(head);

(3)新页(例如被预读的页)加入缓冲池时,只加入到老生代头部:

如果数据真正被读取(预读成功),才会加入到新生代的头部

如果数据没有被读取,则会比新生代里的“热数据页”更早被淘汰出缓冲池

MySQL——内存结构和执行原理_第2张图片

举个例子,整个缓冲池LRU如上图:

(1)整个LRU长度是10;

(2)前70%是新生代;

(3)后30%是老生代;

(4)新老生代首尾相连;

MySQL——内存结构和执行原理_第3张图片

假如有一个页号为50的新页被预读加入缓冲池:

(1)50只会从老生代头部插入,老生代尾部(也是整体尾部)的页会被淘汰掉;

(2)假设50这一页不会被真正读取,即预读失败,它将比新生代的数据更早淘汰出缓冲池;

MySQL——内存结构和执行原理_第4张图片

假如50这一页立刻被读取到,例如SQL访问了页内的行row数据:

(1)它会被立刻加入到新生代的头部;

(2)新生代的页会被挤到老生代,此时并不会有页面被真正淘汰;

改进版缓冲池LRU能够很好的解决“预读失败”的问题,但新老生代改进版LRU仍然解决不了缓冲池污染的问题。

缓冲池污染:当某一个SQL语句,要批量扫描大量数据时,可能导致把缓冲池的所有页都替换出去,导致大量热数据被换出,MySQL性能急剧下降,这种情况叫缓冲池污染。

例如,有一个数据量较大的用户表,当执行:

select * from user where name like "%zhangsan%";

虽然结果集可能只有少量数据,但这类like不能命中索引,必须全表扫描,就需要访问大量的页:

(1)把页加到缓冲池(插入老生代头部);

(2)从页里读出相关的行(插入新生代头部);

(3)行里的name字段和字符串zhangsan进行比较,如果符合条件,加入到结果集中;

(4)直到扫描完所有页中的所有行;

如此一来,所有的数据页都会被加载到新生代的头部,但只会访问一次,而真正的热数据被大量换出。

MySQL的解决方案是在缓冲池加入了一个“老生代停留时间窗口”的机制:

(1)假设T=老生代停留时间窗口;

(2)插入老生代头部的页,即使立刻被访问,也不会立刻放入新生代头部;

(3)只有满足“被访问”并且“在老生代停留时间”大于T,才会被放入新生代头部;

MySQL——内存结构和执行原理_第5张图片

继续举例,假如批量数据扫描,有51,52,53,54,55等五个页面将要依次被访问。

MySQL——内存结构和执行原理_第6张图片

如果没有“老生代停留时间窗口”的策略,这些批量被访问的页面,会换出大量热数据。

MySQL——内存结构和执行原理_第7张图片

加入“老生代停留时间窗口”策略后,短时间内被大量加载的页,并不会立刻插入新生代头部,而是优先淘汰那些,短期内仅仅访问了一次的页。而只有在老生代呆的时间足够久,停留时间大于T,才会被插入新生代头部。上述原理有三个比较重要的参数:

(1)innodb_buffer_pool_size:配置缓冲池的大小,在内存允许的情况下,DBA往往会建议调大这个参数,越多数据和索引放到内存里,数据库的性能会越好。

(2)innodb_old_blocks_pct:老生代占整个LRU链长度的比例,默认是37,即整个LRU中新生代与老生代长度比例是63:37。为100,即是普通LRU。

(3)innodb_old_blocks_time:老生代停留时间窗口,单位是毫秒,默认是1000,即同时满足“被访问”与“在老生代停留时间超过1秒”两个条件,才会被插入到新生代头部。

Change Buffer

我们更新一条数据有两种情况,第一种,数据在内存中;第二种,数据不在内存中;

对于第一种情况,数据库直接更新内存,并写redo log,这样的效率是最高的。对于第二种情况,数据库要从磁盘加载到缓存池,修改缓存池,写redo log,这里至少有一次磁盘io。如何优化?

8.0以后引入的概念,5.5版本叫Insert Buffer,只针对insert做了优化;现在对delete和update也有效,叫做更新缓存。

它是一种应用在非唯一普通索引页(non-unique secondary index page)不在缓冲池中,对页进行了写操作,并不会立刻将磁盘页加载到缓冲池,而仅仅记录缓冲变更(buffer changes),等未来数据被读取时,再将数据合并(merge)恢复到缓冲池中的技术。写缓冲的目的是降低写操作的磁盘IO,提升数据库性能。

加入这个缓冲后,再更新的数据不在缓存的时候,先把变更操作在缓冲中记录下来,这里一次内存操作,在写redo log,达到最优情况。这里也不会出现一致性问题呢?

(1)数据库异常奔溃,能够从redo log中恢复数据;

(2)写缓冲不只是一个内存结构,它也会被定期刷盘到写缓冲系统表空间;

(3)数据读取时,有另外的流程,将数据合并到缓冲池;

InnoDB和MyISAM是怎么利用B+树来实现这两类索引,其又有什么差异呢?

MyISAM:索引与行记录是分开存储的,叫做非聚集索引(UnClustered Index)。

其主键索引与普通索引没有本质差异:

  • 有连续聚集的区域单独存储行记录
  • 主键索引的叶子节点,存储主键,与对应行记录的指针
  • 普通索引的叶子结点,存储索引列,与对应行记录的指针

备注:MyISAM的表可以没有主键。

主键索引与普通索引是两棵独立的索引B+树,通过索引列查找时,先定位到B+树的叶子节点,再通过指针定位到行记录。举个例子,有表tb_test(id PK, name KEY, sex, flag),表中有四条记录:

1, shenjian, m, A

3, zhangsan, m, A

5, lisi, m, A

9, wangwu, f, B

MySQL——内存结构和执行原理_第8张图片

如上图,行记录单独存储id为PK,有一棵id的索引树,叶子指向行记录;name为KEY,有一棵name的索引树,叶子也指向行记录。

InnoDB:主键索引与行记录是存储在一起的,故叫做聚集索引(Clustered Index):

  • 没有单独区域存储行记录
  • 主键索引的叶子节点,存储主键,与对应行记录(而不是指针)
  • 因此,InnoDB的PK查询是非常快的。

因为这个特性,InnoDB的表必须要有聚集索引

(1)如果表定义了PK,则PK就是聚集索引;

(2)如果表没有定义PK,则第一个非空unique列是聚集索引;

(3)否则,InnoDB会创建一个隐藏的row-id作为聚集索引;

聚集索引,也只能够有一个,因为数据行在物理磁盘上只能有一份聚集存储。

InnoDB的普通索引可以有多个,它与聚集索引是不同的:普通索引的叶子节点,存储主键(也不是指针)

对于InnoDB表

(1)不建议使用较长的列做主键,例如char(64),因为所有的普通索引都会存储主键,会导致普通索引过于庞大;

(2)建议使用趋势递增的key做主键,由于数据行与索引一体,这样不至于插入记录时,有大量索引分裂,行记录移动;

仍是上面的例子,只是存储引擎换成InnoDB:

MySQL——内存结构和执行原理_第9张图片

其B+树索引构造如上图:

id为PK,行记录和id索引树存储在一起

name为KEY,有一棵name的索引树,叶子存储id

当:select * from t where name=‘lisi’;

MySQL——内存结构和执行原理_第10张图片

会先通过name辅助索引定位到B+树的叶子节点得到id=5,再通过聚集索引定位到行记录。所以,其实扫了2遍索引树。

总结

  • MyISAM和InnoDB都使用B+树来实现索引:
  • MyISAM的索引与数据分开存储
  • MyISAM的索引叶子存储指针,主键索引与普通索引无太大区别
  • InnoDB的聚集索引和数据行统一存储
  • InnoDB的聚集索引存储数据行本身,普通索引存储主键
  • InnoDB一定有且只有一个聚集索引
  • InnoDB建议使用趋势递增整数作为PK,而不宜使用较长的列作为PK

为什么写缓冲优化,仅适用于非唯一普通索引页呢?

如果在索引设置唯一性,在进行修改时,InnoDB必须要做唯一性校验,因此必须查询磁盘,做一次IO操作。会直接将记录查询到BufferPool中,然后在缓冲池修改,不会在ChangeBuffer操作。

哪些场景会触发刷写缓冲中的数据:

(1)有一个后台线程,会认为数据库空闲时;

(2)数据库缓冲池不够用时;

(3)数据库正常关闭时;

(4)redo log写满时;

(5)数据页被访问;

备注:几乎不会出现redo log写满,此时整个数据库处于无法写入的不可用状态。

什么时候不适合使用写缓冲:

(1)数据库都是唯一索引;

(2)或者,写入一个数据后,会立刻读取它;

这两类场景,在写操作进行时(进行后),本来就要进行进行页读取,本来相应页面就要入缓冲池,此时写缓存反倒成了负担,增加了复杂度。

什么时候适合使用写缓冲:

(1)数据库大部分是非唯一索引;

(2)业务是写多读少,或者不是写后立刻读取;

可以使用写缓冲,将原本每次写入都需要进行磁盘IO的SQL,优化定期批量写磁盘。例如,账单流水业务。

上述原理有两个比较重要的参数:

(1)innodb_change_buffer_max_size:配置写缓冲的大小,占整个缓冲池的比例,默认值是25%,最大值是50%。写多读少的业务,才需要调大这个值,读多写少的业务,25%其实也多了。

(2)innodb_change_buffering:配置哪些写操作启用写缓冲,默认是all支持所有DML操作,可以设置成all/none/inserts/deletes等。

Adaptive Hash Index

自适应哈希索引就是当InnoDB注意到某些索引值被使用的非常频繁时,它会在内存中基于b+ tree索引之上再创建一个哈希索引,这样就可以进行哈希查找。

自适应Hash索引是InnoDB自动创建的,当:

① 索引被访问了17次

② 索引中的某个页已经被访问了至少100次

③ 对索引中的页访问的模式是相同的

满足上面的条件就会自动添加到自适应hash索引中。

innodb_adaptive_hash_index:控制innodb自适应哈希索引特性是否开启参数
innodb_adaptive_hash_index_parts:凡是缓存都会涉及多个缓存消费者间的锁竞争。MySQL通过设立多个AHI分区,每个分区使用独立的锁,来减少锁竞争。

Log Buffer

由上文可知,我们对数据进行操作时,需要将数据加载到缓存(buffer pool)中,交给lru链表管理,对于非唯一索引列的增删改,可以暂存在change buffer中,然后找机会和buffer pool中的数据合并。缓存中的数据在innoDB空闲时,持久化到磁盘中。但是这么做,当缓存中数据更改但是还没来得及持久化到磁盘中时,突然断电或者宕机,就会导致已变更的数据丢失。

所以,缓存中的数据每做一次更改,就记录该页的(B+树的)更改位置的新版本数据,并持久化到磁盘中,我们称之为redo log;又记录该页的(B+树的)更改位置的旧版本数据,并持久化到磁盘中,我们称之为undo log;

redo log 可以用于当缓存中数据丢失的恢复;

undo log用于事务回滚旧数据的恢复;
但是每更改一次就要进行一次IO操作,虽然由随机IO变成了顺序IO,但是仍然很浪费性能,所以就产生了log buffer,默认事务提交之前,对数据更改产生的日志(redo log和undo log)都存储在log buffer 中,事务提交时,才持久化到磁盘文件中,这样大大减少了IO操作。

innodb_flush_log_at_trx_commit :控制如何将日志缓冲区的内容写入并刷新到磁盘,默认是1。
innodb_flush_log_at_timeout :控制日志刷新频率

日志文件的缺省数量为两个: ib_logfile0 和 ib_logfile1 。日志具有固定大小,默认大小取决于MySQL版本。从5.7版本开始,默认值是每个48MB,从MySQL 5.6.3开始,最大总大小为 512GB。

如果应用程序是写密集型应用程序,则可以使用48MB,并且鉴于日志以循环方式工作,当日志写满时,有必要对磁盘上的数据文件进行写操作。所以,如redo log大小设置较小,可能会导致频繁的磁盘写入甚至是等待,极大地降低了性能。可通过查看日志序列号状态变量log_lsn_current和log_lsn_last_checkpoint来观察刷新的频率。通过将两个值相减,并与重做日志空间的总大小进行比较,可了解刷新是否比期望的发生更频繁。

要调整 innodb_log_buffer_size 或 innodb_log_file_size 变量,必须在MySQL的my.cnf配置文件中显式定义。调整 innodb_log_buffer_size 或 innodb_log_file_size 变量前,建议关闭实例,以确保MySQL正确无误地停止运行。如果在关闭过程中出现错误,则现有的日志文件可能尚未完全写入数据文件,数据可能会丢失。

 log buffer的持久化策略如下,可以通过参数进行调节 innodb_flush_log_at_trx_commit 进行调节:

MySQL——内存结构和执行原理_第11张图片

 0:每次事务提交时,根本不会去刷日志缓冲区。log buffer将每秒一次地写入到OS cache的log file中,并且log file的flush(刷到磁盘)上的Log Files操作同时进行。
1:每次事务提交时MySQL都会把log buffer的数据写入到OS cache的log file,并且flush(刷到磁盘)Log Files中去,该模式为系统默认。
2:每次事务提交时MySQL都会把log buffer的数据写入到OS cache的log file,但是flush(刷到磁盘)Log Files的操作并不会同时进行。该模式下,MySQL会每秒执行一次 flush(刷到磁盘)操作。

磁盘结构

System Tablespace

在默认情况下 InnoDB 存储引擎有一个共享表空间(对应文件/var/lib/mysql/ ibdata1),也叫系统表空间。

InnoDB 系统表空间包含 InnoDB 数据字典和双写缓冲区,Change Buffer 和 Undo Logs,如果没有指定 file-per-table,也包含用户创建的表和索引数据。

2、数据字典:由内部系统表组成,存储表和索引的元数据(定义信息)。

3、双写缓冲(InnoDB 的一大特性):

InnoDB 的页和操作系统的页大小不一致,InnoDB 页大小一般为 16K,操作系统页 大小为 4K,InnoDB 的页写入到磁盘时,一个页需要分 4 次写。

MySQL——内存结构和执行原理_第12张图片

如果存储引擎正在写入页的数据到磁盘时发生了宕机,可能出现页只写了一部分的 情况,比如只写了 4K,就宕机了,这种情况叫做部分写失效(partial page write),可能会导致数据丢失。

show variables like 'innerdb_doublewrite';

我们不是有 redo log 吗?但是有个问题,如果这个页本身已经损坏了,用它来做崩溃恢复是没有意义的。所以在对于应用 redo log 之前,需要一个页的副本。如果出现了 写入失效,就用页的副本来还原这个页,然后再应用 redo log。这个页的副本就是 double write,InnoDB 的双写技术。通过它实现了数据页的可靠性。

跟 redo log 一样,double write 由两部分组成,一部分是内存的 double write,一个部分是磁盘上的 double write。因为 double write 是顺序写入的,不会带来很大的 开销。

在默认情况下,所有的表共享一个系统表空间,这个文件会越来越大,而且它的空间不会收缩。

File-Per-Table Tablespaces

我们可以让每张表独占一个表空间。这个开关通过 innodb_file_per_table 设置,默 认开启。

show variables like 'innodb_file_per_table';

开启后,则每张表会开辟一个表空间,这个文件就是数据目录下的 ibd 文件(例如 /var/lib/mysql/gupao/user_innodb.ibd),存放表的索引和数据。 但是其他类的数据,如回滚(undo)信息,插入缓冲索引页、系统事务信息,二次 写缓冲(Double write buffer)等还是存放在原来的共享表空间内。

General Tablespaces

通用表空间也是一种共享的表空间,跟 ibdata1 类似。 可以创建一个通用的表空间,用来存储不同数据库的表,数据路径和文件可以自定 义。语法:

create tablespace xxx add datafile 'xxx/xxx/xxx.ibd' file_block_size=16K engine=innodb;

在创建表的时候可以指定表空间,用 ALTER 修改表空间可以转移表空间

create table xxx tablespace xxx;

不同表空间的数据是可以移动的。

删除表空间需要先删除里面的所有表

drop table xxx;

drop tablespace xxx;

Temporary Tablespaces

存储临时表的数据,包括用户创建的临时表,和磁盘的内部临时表。对应数据目录 下的 ibtmp1 文件。当数据服务器正常关闭时,该表空间被删除,下次重新产生。

Undo Tablespaces

undo log(撤销日志或回滚日志)记录了事务发生之前的数据状态(不包括 select)。 如果修改数据时出现异常,可以用 undo log 来实现回滚操作(保持原子性)。 在执行 undo 的时候,仅仅是将数据从逻辑上恢复至事务之前的状态,而不是从物 理页面上操作实现的,属于逻辑格式的日志。 redo Log 和 undo Log 与事务密切相关,统称为事务日志。 undo Log 的数据默认在系统表空间 ibdata1 文件中,因为共享表空间不会自动收 缩,也可以单独创建一个 undo 表空间。

show global variables like '%undo%';

SQL执行流程

MySQL通信方式

MySQL可支持同步通信,异步通信,异步通信要使用连接池。

第一种是 Unix Socket。 比如我们在 Linux 服务器上,如果没有指定-h 参数,它就用 socket 方式登录(省略 了-S /var/lib/mysql/mysql.sock)。它不用通过网络协议,也可以连接到 MySQL 的服务器,它需要用到服务器上的一个物理文件(/var/lib/mysql/mysql.sock)。

select @@socket;

如果指定-h 参数,就会用第二种方式,TCP/IP 协议。

mysql -h192.168.8.211 -uroot -p123456

我们的编程语言的连接模块都是用 TCP 协议连接到 MySQL 服务器的,比如 mysql-connector-java-x.x.xx.jar。

通信方式有:

单工:数据只能有发送器发到接收器,不能双向传输

半双工:数据可以双向传输,但是数据不能同时传输

全双工:数据可以双向传输,也可以同时传输

MySQL使用的是半双工通信方式,不能同时传输,所以客户端发送的数据不能被分成小块,不管多大,都只能一次性传输,这个时候我们必须要调整 MySQL 服务器配置 max_allowed_packet 参数的值(5.7默认 是 4M,8.0版本默认是64M),把它调大,否则就会报错。

show variables like 'max_allowed_packet';

另一方面,对于服务端来说,也是一次性发送所有的数据,不能因为你已经取到了想要的数据就中断操作,这个时候会对网络和内存产生大量消耗。 所以,我们一定要在程序里面避免不带 limit 的这种操作,比如一次把所有满足条件的数据全部查出来,一定要先 count 一下。如果数据量的话,可以分批查询。 

另外还有命名管道(Named Pipes)和内存共享(Share Memory)的方式,这两种 通信方式只能在 Windows 上面使用,一般用得比较少。

MySQL可支持长连接和短连接,超时时间是28800秒,即8个小时

show global status like 'Thread%';命令可看当前连接数,有

Threads_cached,Threads_connected,Threads_created,Threads_running四个参数

root用户通过show processlist;命令可查连接状态

show variables like 'max_connections';命令可查询最大连接数,默认是151个,最大可设置2^14(16384)个

动态修改:set,重启后失效;永久生效,修改配置文件/etc/my.cnf,set global max_connections = 1000;

MySQL权限验证

MySQL中存在4个控制权限的表,分别为user表,db表,tables_priv表,columns_priv表:

user表:存放用户账户信息以及全局级别(所有数据库)权限,决定了来自哪些主机的哪些用户可以访问数据库实例

db表:存放数据库级别的权限,决定了来自哪些主机的哪些用户可以访问此数据库

tables_priv表:存放表级别的权限,决定了来自哪些主机的哪些用户可以访问数据库的这个表

columns_priv表:存放列级别的权限,决定了来自哪些主机的哪些用户可以访问数据库表的这个字段

MySQL权限表的验证过程为:

1、 先从user表中的Host,User,Password这3个字段中判断连接的IP、用户名、密码是否存在,存在则通过验证。

2、通过身份认证后,进行权限分配,按照user,db,tables_priv,columns_priv的顺序进行验证。即先检查全局权限表user,如果user中对应的权限为Y,则此用户对所有数据库的权限都为Y,将不再检查db,tables_priv,columns_priv;如果为N,则到db表中检查此用户对应的具体数据库,并得到db中为Y的权限;如果db中为N,则检查tables_priv中此数据库对应的具体表,取得表中的权限Y,以此类推。

3、如果在任何一个过程中权限验证不通过,都会报错。

执行流程

MySQL——内存结构和执行原理_第13张图片

获取连接——》查询缓存(8.0版本之后没有缓存了),有缓存直接返回,没有就下一步——》SQL接口——》解析器——》预处理器——》优化器——》执行引擎——》存储引擎

SQL接口

接受SQL语句

缓存模块

数据是kv形式放在内存里里边的,show variables like 'query_cache%';这个命令可以查看缓存一些信息,默认是关闭的,MySQL不推荐使用缓存

解析器

词法解析,就是把MySQL语句拆成一个一个的单词,语法解析,会对 SQL 做一些语法检查,比如单引号有没有闭合, 然后根据 MySQL 定义的语法规则,根据 SQL 语句生成一个数据结构。这个数据结构我 们把它叫做解析树(select_lex)

预处理器

它会检查生成的解析树,解决解析器无法解析的语义。比如,它会检查表和列名是否存在,检查名字和别名,保证没有歧义。 预处理之后得到一个新的解析树。

优化器

根据解析树生成不同的执行计划(Execution Plan),然后选择一种最优的执行计划,MySQL 里面使用的是基于开销(cost)的优化器,那种执行计划开销最小,就用哪种。 可以使用这个命令查看查询的开销:show status like 'Last_query_cost';

优化器是怎么工作的?首先开启优化器追踪,show variables like ‘optimizer_trace’;set optimizer_trace=’enabled=on’; 注意开启这开关是会消耗性能的,因为它要把优化分析的结果写到表里面,所以不要轻易开启,或者查看完之后关闭它(改成 off)

注意开启这开关是会消耗性能的,因为它要把优化分析的结果写到表里面,所以不要轻易开启,或者查看完之后关闭它(改成 off)。

注意:参数分为 session 和 global 级别。

接着我们执行一个 SQL 语句,优化器会生成执行计划:

select t.tcid from teacher t,teacher_contact tc where t.tcid = tc.tcid

这个时候优化器分析的过程已经记录到系统表里面了,我们可以查询:

select * from information_schema.optimizer_trace\G

它是一个 JSON 类型的数据,主要分成三部分,准备阶段、优化阶段和执行阶段。

expanded_query 是优化后的 SQL 语句。 considered_execution_plans 里面列出了所有的执行计划。 分析完记得关掉它:

set="enabled=off";

SHOW VARIABLES LIKE ;

优化器最终会把解析树变成一个查询执行计划,查询执行计划是一个数据结构。当然,这个执行计划是不是一定是最优的执行计划呢?不一定,因为 MySQL 也有可能覆盖不到所有的执行计划。我们怎么查看 MySQL 的执行计划呢?比如多张表关联查询,先查询哪张表?在执行 查询的时候可能用到哪些索引,实际上用到了什么索引? MySQL 提供了一个执行计划的工具。我们在 SQL 语句前面加上 EXPLAIN,就可以 看到执行计划的信息。

EXPLAIN select name from user where id=1;注意 Explain 的结果也不一定最终执行的方式。

存储引擎

通过命令:show table status from ‘表名’;或者通过show create table/database ‘表名/数据库名’;可以看到存储引擎,这里通过数据创建语句看不到,建表可以看到。

在 MySQL 里面,我们创建的每一张表都可以指定它的存储引擎,而不是一个数据库只能使用一个存储引擎。存储引擎的使用是以表为单位的。而且,创建表之后还可以修改存储引擎。我们说一张表使用的存储引擎决定我们存储数据的结构,那在服务器上它们是怎么存储的呢?我们先要找到数据库存放数据的路径:show variables like 'datadir';

默认情况下,每个数据库有一个自己文件夹,以test 数据库为例。 任何一个存储引擎都有一个 frm 文件,这个是表结构定义文件。不同的存储引擎存放数据的方式不一样,产生的文件也不一样,innodb 是 1 个, memory 没有,myisam 是两个。

这些存储引擎的差别在哪呢?

MyISAM 和 InnoDB 是我们用得最多的两个存储引擎,在 MySQL 5.5 版本之前, 默认的存储引擎是 MyISAM,它是 MySQL 自带的。我们创建表的时候不指定存储引擎, 它就会使用 MyISAM 作为存储引擎。MyISAM 的前身是 ISAM(Indexed Sequential Access Method:利用索引,顺序 存取数据的方法)。5.5 版本之后默认的存储引擎改成了 InnoDB,它是第三方公司为 MySQL 开发的。 为什么要改呢?最主要的原因还是 InnoDB 支持事务,支持行级别的锁,对于业务一致性要求高的场景来说更适合。

这个里面又有 Oracle 和 MySQL 公司的一段恩怨情仇。 InnoDB 本来是 InnobaseOy 公司开发的,它和 MySQL AB 公司合作开源了 InnoDB 的代码。但是没想到 MySQL 的竞争对手 Oracle 把 InnobaseOy 收购了。 后来 08 年 Sun 公司(开发 Java 语言的 Sun)收购了 MySQL AB,09 年 Sun 公司 又被 Oracle 收购了,所以 MySQL,InnoDB 又是一家了。有人觉得 MySQL 越来越像 Oracle,其实也是这个原因。

我们可以用这个命令查看数据库对存储引擎的支持情况:show engines;

其中有存储引擎的描述和对事务、XA 协议和 Savepoints 的支持。

XA 协议用来实现分布式事务(分为本地资源管理器,事务管理器)。

Savepoints 用来实现子事务(嵌套事务)。创建了一个 Savepoints 之后,事务就可以回滚到这个点,不会影响到创建 Savepoints 之前的操作,这些数据库支持的存储引擎,分别有什么特性呢?可看官网dev.mysql.com/doc/refman/5.7/en/storage-engines.html

MyISAM:

  • 支持表级锁(插入和更新会锁表),不支持事务。
  • 拥有较高的插入和查询速度。
  • 储存了表的行数,速度更快
  • (怎么快速向数据库插入 100 万条数据?我们有一种先用 MyISAM 插入数据,然后修改存储引擎为 InnoDB 的操作。适合:只读之类的数据分析的项目。)

InnoDB:

mysql 5.7 中的默认存储引擎。InnoDB 是一个事务安全(与 ACID 兼容)的 MySQL 存储引擎,它具有提交、回滚和崩溃恢复功能来保护用户数据。InnoDB 行级锁(不升级为更粗粒度的锁)和 Oracle 风格的一致非锁读提高了多用户并发性和性能。InnoDB 将用户数据存储在聚集索引中,以减少基于主键的常见查询的 I/O。为了保持数据完整性,InnoDB 还支持外键引用完整性约束。

  • 支持事务,支持外键,因此数据完整性,一致性更高;
  • 支持行级别的锁,表级别的锁
  • 支持读写并发,写不阻塞读(MVCC )
  • 特殊的索引存放方式,可以减少io,提升查询效率
  • 适合经常更新的表,存在并发读写或者有事务处理的业务系统

执行引擎

执行引擎使用执行计划,利用存储引擎提供的api,操作存储引擎,先从缓存中获取数据,写redo log,返回得到数据。

后台线程

后台线程的主要作用是负责刷新内存池中的数据和把修改的数据页刷新到磁盘。

后台线程分为:master thread,IO thread,purge thread,page cleaner thread。

  1. master thread :负责刷新缓存数据到磁盘并协调调度其它后台进程。
  2. IO thread :分为 insert buffer、log、read、write 进程。分别用来处理 insert buffer、 重做日志、读写请求的 IO 回调。
  3. purge thread: 用来回收 undo 页。
  4. page cleaner thread :用来刷新脏页。

你可能感兴趣的:(MySQL,Java,数据库,mysql,java)