mysql 存储引擎、索引、执行计划、事务、锁、分库分表、优化
存储引擎规定了数据存储时的不同底层实现,如存储机制、索引、锁、事务等。
可以通过 show engines 命令查看当前服务器的存储引擎信息:
引擎名称 | 服务器是否支持 | 组件 | 事务 | 分布式事务 | 保存点 |
---|---|---|---|---|---|
InnoDB | 支持(默认引擎) | 支持事务、行级锁、外键 | 支持 | 支持 | 支持 |
MRG_MYISAM | 支持 | 相同 MyISAM 表的集合 | 不支持 | 不支持 | 不支持 |
MEMORY | 支持 | 基于哈希、存储在内存中、对临时表很有用 | 不支持 | 不支持 | 不支持 |
BLACKHOLE | 支持 | 写入的任何内容都会消失 ? ? ? | 不支持 | 不支持 | 不支持 |
MyISAM | 支持 | 不支持 | 不支持 | 不支持 | |
CSV | 支持 | 不支持 | 不支持 | 不支持 | |
ARCHIVE | 支持 | 不支持 | 不支持 | 不支持 | |
PERFORMANCE_SCHEMA | 支持 | 不支持 | 不支持 | 不支持 | |
FEDERATED | 不支持 |
注:保存点指的是事务过程中的一个逻辑点,用于取消部分事务。当事务结束后,会自动删除该事务的所有保存点。当事务回滚时,可以通过指定保存点使其回退到指定的点。
InnoDB 存储引擎是一个事务型存储引擎,其特点是支持事务、行锁设计、支持外键、支持非锁定读。
InnoDB 使用 mvcc (多版本并发控制)来获得高并发性;且实现了 SQL 标准的四种隔离级别;使用 next-key locking 的策略来避免幻读现象的产生;提供了 insert buffer(插入缓冲)、double write(二次写)、adaptive hash index(自适应哈希索引)read ahead(预读)等高性能和高可用功能。
对于表中数据的存储,InnoDB 采用了聚集索引的方式,因此每张表中数据的存储都是按主键的存储进行存放(若没有显式创建聚集索引则主键为聚集索引)。若没有显式指定主键,则 InnoDB 会为每行数据生成一个 6 字节的 ROWID(隐藏列)作为主键,且插入数据时自增。
MRG_MYISAM 存储引擎实际上时 MyISAM 的组合,它是多个使用 MyISAM 引擎的表的集合。它内部没有数据,真正的数据在 MyISAM 引擎表中。
MRG_MYISAM 解决了在对分库分表数据进行查询时,不确定数据放在那个表里的问题。它将多个表聚合成一个表统一查询,且不会影响原有数据。
MEMORY 引擎是将表中的数据存放在内存中,若数据库崩溃或重启则表中数据将丢失。它适用于存放临时数据的临时表,以及数据仓库中的维度表。MEMORY 引擎默认使用哈希索引,而不是 B+tree 索引。
虽然 MEMORY 引擎速度非常快,但其在使用上还是有一定的限制。如,只支持表锁,并发性能差;不支持 text 和 blob 类型;存储变长字段字段(varchar)时是按照定长字段(char)的方式进行的,因此会浪费内存。
mysql 使用 MEMORY 引擎作为临时表来存放查询的中间结果集。如果中间结果集大于 MEMORY 引擎表的容量设置,又或者中间结果集含有 text 或 blob 列类型字段,则 mysql 会将其转换到 MyISAM 引擎表存放到而存放到磁盘中。
黑洞存储引擎,擎如其名。
MyISAM 存储引擎表级锁、支持全文索引,但不支持事务,主要面向一些 OLAP(联机分析处理)数据库应用。历史上,曾作为 mysql 的默认存储引擎出现过。MyISAM 引擎的缓冲池只缓存索引文件,而不缓存数据,这点不同于大多数存储引擎。
MyISAM 引擎表由 MYD 和 MYI 组成,MYD 用来存放数据文件,MYI 用来存放索引文件。其系统兼容性好,查询速度快。
CSV 存储引擎,其特点是不支持索引、不能为 null、不能自增;可对数据文件直接编辑;以 csv 格式存储数据;数据以文本方式存储在文件中;更新和删除先写入到临时文件,然后在 rnd_end() 函数中重新生成数据文件。
CSV 引擎可以将 csv 文件当作 mysql 的表进行处理。其 .frm 为表结构描述,.csv 为数据,.csm 为状态、当前记录数量等。
ARCHIVE 存储引擎只支持 insert 和 select 操作,从 mysql 5.1 开始支持索引。其使用 zlib 算法将数据行进行压缩后存储,压缩比一般达到 1:10。ARCHIVE 非常适合用于存储归档数据,如日志信息。ARCHIVE 引擎使用行锁来实现高并发的插入操作,但其并不是事务安全的存储引擎,其设计目的是提供告诉的插入和压缩功能。
PERFORMANCE_SCHEMA 为 mysql 5.5 新出的一个存储引擎,主要用来收集数据库服务器的性能参数。用户无法主动创建该引擎表,都是由 mysql 自己创建。
其提供了进程的锁、文件、互斥变量等信息;保持历史事件的汇总信息,为 mysql 服务器的性能作出详细判断。
FEDERATED 存储引擎的表并不存放数据,而是指向一台远程 mysql 服务器上的表。非常类似于 sql server 的链接服务器和 oracle 的透明网关,不同的是,目前 FEDERATED 引擎只支持 mysql 数据库表,不支持异构数据库表。
索引是关系型数据库中给数据库表的一列或多列的值排序后的存储结构,其主要目的是为了提高数据库的检索性能。
下文所有概念性全都基于 mysql 数据库 InnoDB 存储引擎。
mysql 索引分类及定义如下:
主流的索引数据结构为 B + Tree 和 Hash。聚集索引和非聚集索引都基于 B + Tree 结构。
B + Tree 为传统意义上的索引,且其是目前关系型数据库中最为常用和最为有效的索引。B + Tree 索引的构造类似于二叉树,根据键值对快速查找数据。
B + Tree 由 B Tree 演化而来, B 树为父,B + 树为子,B 树矮胖,B + 树更矮胖;平衡二叉树算是一种特殊的 B 树(B 树又名多路平衡树);平衡二叉树是基于二分法一种二叉树数据结构。(注:这里的 B 指 banlance,而不是 binary)
B Tree
一个 m 阶 B 树有以下特征:
关于上面这个 3 阶 B 树的查询、插入、删除的过程如下:
1、查询元素 4 的过程如下:
2、插入元素 3 的过程如下:
3、删除元素 17的过程如下:
B + Tree
一个 m 阶 B + 树的特征如下:
关于上述 m 阶 b + tree 的查询、插入、删除的过程如下:
过程?过程和 b 树差不太多。
b + 树相对于 b 树查询时的优点:
B + 树索引与哈希索引的区别
b + 树索引:
哈希索引:
区别:
b + 树索引适合大多数场景,如组合查询、范围查询、排序、分组、模糊等;hash 索引适合离散性高、数据基数大、等值查询时。
最左匹配原则
最左匹配原则指的是 where 子句中的条件顺序要与联合索引的列顺序保持一致,否则索引不生效。
# 给列 a、b、c 建立联合索引 (a, b, c)
# 下列查询索引生效(后五个查询索引生效是因为 mysql 有查询优化器,会自动优化顺序)
select * from table_name where a = 'a' and b = 'b' and c = 'c'
select * from table_name where a = 'a' and c = 'c' and b = 'b'
select * from table_name where b = 'b' and a = 'a' and c = 'c'
select * from table_name where b = 'b' and c = 'c' and a = 'a'
select * from table_name where c = 'c' and b = 'b' and a = 'a'
select * from table_name where c = 'c' and a = 'a' and b = 'b'
# 下列查询索引生效
select * from table_name where a = 'a'
select * from table_name where a = 'a' and b = 'b'
select * from table_name where a = 'a' and b = 'b' and c = 'c'
# 下列查询索引生效(但用到的是 a 列索引,b、c 列都没用到)
select * from table_name where a = 'a' and c = 'c'
# 下列查询索引不生效
select * from table_name where b = 'b'
select * from table_name where c = 'c'
select * from table_name where b = 'b' and c = 'c'
select * from table_name where c = 'c' and b = 'b'
顺带说一下最左前缀原则:
最左前缀原则指的是在模糊查询中,只有 like ‘aa%’ 时索引才会生效。其原因是,对于字符类型,其比较规则是一个字符一个字符比较,只有第一个字符匹配到了,才会比较第二个字符。
# 索引生效
select * from table_name where a like 'aa%'
# 索引不生效
select * from table_name where a like '%aa'
select * from table_name where a like '%aa%'
回表
如果 select 所需的列包含非索引列或者根据一次索引不能获得所需数据,则需要到对应表中的对应行找到需要的列值,这个过程就叫回表。
# 如存在表 table_name 含有 id、name、gender、age 字段 且 id 为主键 并为 name 字段建立了普通索引
# 以下查询不需要回表
# 因为通过搜索主键索引树就可以确定对应记录的位置
# 因为主键索引默认为聚集索引,而聚集索引指的是数据行的物理顺序跟字段值的逻辑顺序保持一致
# 也就是搜索了主键索引就相当于搜索了聚集索引,直接确定了数据行的地址
select * from table_name where id = 1
# 以下查询需要回表
# 因为 select 列包含了非索引列,查询时需要先搜索 name 索引树找到主键 id
# 然后搜索主键索引树找到具体数据行
select * from table_name where name = 'momo'
覆盖索引
索引覆盖指的是在一颗索引树上就能获取到 select 所需的数据。即无需回表。
无需回表时,查询速度会更快,所以尽可能的实现索引覆盖将会在很大程度上提高查询效率。
实现索引覆盖最常见的方式就是为被查询的列建立联合索引。
索引下推
索引下推是 mysql 在 5.6 版本添加的查询优化策略(ICP 优化)。其具体实现是在索引搜索过程中,对索引列包含的条件字段先做判断,过滤掉不符合条件的数据,以此来减少回表次数。
# 如存在表 table_name 含有 id、name、gender、age 字段 且 id 为主键 并为 name、gender 字段建立了联合索引
# 表中存在以下记录
# (1, 'aa', 1, 21)
# (2, 'aabb', 1, 21)
# (3, 'aacc', 2, 23)
# (4, 'aadd', 2, 23)
# 对于以下查询会出现索引下推
select * from table_name where name like 'aa%' and gender = 1 and age = 23
# mysql 5.6 之前的版本:
# 查询时首先会搜索联合索引树找出 name 以 'aa' 开头的四条记录的 id,然后逐个回表比较 gender 和 age 字段的值
# mysql 5.6 版本:
# 查询时首先会搜索索引树,根据最左匹配原则,会先匹配到 name like 'aa%' 的记录,同时过滤掉 gender != 1 的值
# 得到 id 为 3、4,然后回两次表再比较 age 的值。
索引失效
以下几种情况会导致索引失效:
通过 mysql 的执行计划可以获取到 sql 执行时表的访问顺序、数据读取操作的操作类型、那些索引可以用、实际使用了那些索引、表之间的引用等。但其也有一定局限性,如不会告诉你触发器、存储过程或用户自定义函数对查询的影响;不能直到 mysql 执行 sql 时对 sql 的自动优化情况;只能作用于 select 操作。
mysql 的执行计划虽然不一定准确,但却在我们分析 sql 时能提供具有很大参考价值的信息。
可以通过 explain 关键字加 sql 语句来查看执行计划。
其中 id、type、key、rows、Extra 对分析执行情况有很大参考价值。
字段解释如下:
id:
执行 id,表示执行 select 子句或操作表的顺序。
id 相同,则表示执行顺序从上往下;如果存在子查询,则 id 值递增,值越大,执行优先级越高;id 若相同,则可认为是一组,组内从上往下执行,在所有族中,id 值越大,执行优先级越高。
select_type:
select_type 表示执行的类型。
select_type | 描述 | 备注 |
---|---|---|
SIMPLE | 简单查询 | 查询中不包含子查询或联合查询 |
PRIMARY | 最外层查询 | 查询中若包含复杂的子查询,则最外层查询被标记为 primary |
SUBQUERY | 子查询 | 在 select 或 where 列表中包含子查询时,该子查询被标记为 subquery |
DERIVED | 衍生查询 | 在 from 列表中包含子查询时,该子查询被标记为 derived |
UNION | 联合查询 | 若第二个 select 出现在 union 关键字之后,则被标记为 union |
UNION RESULT | 联合结果查询 | 从 union 表中获取结果的 select 被标记为 union result |
table:
查询时访问的表名。
partitions:
若查询是基于分区表的,则代表查询时的分区。
type:
type 表示访问类型,又可理解为 mysql 在表中找到所需行的方式。
type | 描述 | 备注 |
---|---|---|
ALL | 全表扫描 | 扫描全表找到匹配行 |
index | 全索引树扫描 | 扫描全索引树 |
range | 索引树范围扫描 | 常见于 between、<、> 等 |
index_merge | 索引合并 | 使用多个单列索引及逆行搜索 |
ref | 非唯一索引扫描 | 即会通过索引找到一个或多个记录 |
eq_ref | 唯一索引扫描 | 即会通过索引找到一个记录,常见于主键索引扫描或唯一索引扫描 |
const | 常量 | 即 mysql 会将某些查询条件优化且转换为常量,如主键条件 |
system | 特殊常量 | const 的特殊情况,当表中只有一行记录时为 system |
BULL | 最高境界 | mysql 在优化过程中分解语句,执行时甚至不用访问表或索引 |
其性能从上到下依次增强,且 range 之前的都可以尝试优化。
possible_key:
查询时可能使用的索引。
key:
查询时实际使用的索引。
key_len:
使用的索引字节长度。
ref:
表示当前查询的连接匹配条件,即那些列或常量被用于查找索引列上的值。
rows:
mysql 根据表统计信息即索引选用信息,预估的找到所需记录需要读取的行数。
filtered:
符合某条件的记录数百分比。
Extra:
额外重要信息。
Extra | 描述 |
---|---|
Using index | 表示当前 select 中使用了覆盖索引 |
Using where | 表示 mysql 存储引擎在接收到记录后进行 “后过滤(Post-filter)”,又可理解为当前 select 没有使用,将使用 where 子句中条件进行过滤 |
Using temporary | 表示当前 select 会使用临时表来存放结果集,常见于排序和分组查询 |
Using filesort | 表示当前排序使用 “文件排序”,无法利用索引排序时就会利用文件排序 |
注:
InnoDB 存储引擎实现了两种标准行级锁:
分库分表是程序员三高(高并发、高可用、高性能)中高并发和高性能的一种实现方式或解决方式(它并不能实现高可用)。
对于 “小” 应用(用户量小、数据量小),单机数据库就能够满足;但是对于大型应用(用户量大、数据量大),由于其并发量大、业务数据增长率高、累积的数据量大,数据库的读写速度将会是限制应用性能的最直接原因。
最简单的解决方法就是读写分离,实现方式则是数据库的主从同步。即主库负责写,从库负责读。但随着用户量的增长,随之而来的就是大量用户请求。对于读操作,则可以考虑水平扩展从库;对于写操作来说,由于得保证数据的一致性,主库不能水平扩展。这时候,就需要考虑分库分表。
分库分表的直接目的是解决高并发下的处理速度问题以及数据库服务器的性能问题。
由于每个服务器的 TPS、IO、内存等都是有限的,所以架构设计主要的目的就是在合理利用服务器、网络等资源的前提下尽可能解决高并发问题。当请求并发量大时。可以考虑集群处理,如上文提到的数据库主从同步等;当单库数据量大时,可以考虑切分成更多更小的库(库中的表少,表里的数据少);当单表数据量大时,可以考虑切分成更多更小的表(表的字段少,表里的数据少)。
分库分表一般分为垂直切分和水平切分。当需要分库分表时,可以考虑先垂直后水平,因为垂直切分较水平切分在实现上相对来说较简单且更容易理解。
分库分表后也会产生新的问题:
水平分表的拆分规则中有一种方法是哈希取模,其实现原理是对数据唯一值进行哈希取模,得到的结果将是存放该数据的数据库节点。如分库分表后有三个数据库节点,分别是 1、2、3,当 userId.hashCode()%n == 1 时(n 代表数据库节点个数)则表示该数据对应数据库 2,userId.hashCode()%n == 0 时,则对应数据库 1。
这种方式存在缺陷,即当新增节点或删除节点时,userId.hashCode()%n 将与原来不同,就会造成历史数据全部失效,此时就需要进行数据迁移。于是就有了一致性哈希。
一致性哈希实现原理,首先通过自定义哈希算法求出所有数据库节点的哈希值(如数据库节点命名唯一值的哈希值),使其较均匀的分布在一个 0 ~ 2^32-1 的数字圆环中,然后求出数据唯一值哈希,同样落在圆环上,其对应数据库将是圆环上顺时针方向上最近的一个节点。这样可以减少数据迁移的操作,当某个节点宕掉,只需要迁移这个节点对应的数据即可。
数据库方面优化可以从 sql、数据库表结构、系统配置、硬件等这几个点进行。
数据库表结构优化则指分库分表。
系统配置优化指通过修改数据库服务器参数配置来提高其性能。
买个更牛逼的数据库服务器。
听见奶奶说什么了吗?
奶奶总说…
艾欧尼亚 昂扬不灭!!!