5. MySQL

5.1 说说自己对于 MySQL 常见的两种存储引擎:MyISAM 与 InnoDB 的理解

  • count 运算上的区别:因为 MyISAM 缓存有表 meta-data(行数等),因此在做 COUNT(*) 时对于一个结构很好的查询是不需要消耗多少资源的。而对于 InnoDB 来说,则没有这种缓存。
  • 是否支持事务和崩溃后的安全恢复: MyISAM 强调的是性能,每次查询具有原子性,其执行数度比 InnoDB 类型更快,但是不提供事务支持。但是 InnoDB 提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACIDcompliant))型表。
  • 是否支持外键: MyISAM 不支持,而 InnoDB 支持。

MyISAM 更适合读密集的表,而 InnoDB 更适合写密集的表。 在数据库做主从分离的情况下,经常选择 MyISAM 作为主库的存储引擎。

一般来说,如果需要事务支持,并且有较高的并发读取频率( MyISAM 的表锁的粒度太大,限制了其它对表中任意部分进行的访问,所以当该表写并发量较高时,要等待的查询就会很多了),InnoDB 是不错的选择

如果你的数据量很大( MyISAM 支持压缩特性可以减少磁盘的空间占用),而且不需要支持事务时, MyISAM 是最好的选择


5.2 数据库索引了解吗?

1. 为什么要使用索引?

  • 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
  • 可以大大加快数据的检索速度(减少了检索的数据量),这也是创建索引的最主要原因。
  • 帮助服务器避免排序和临时表。
  • 将随机 IO 变为顺序 IO。
  • 可以加速表与表之间的连接。

2. 索引这么多优点,为什么不对表中的每一个列创建一个索引呢?

  • 虽然索引大大提高了查询的速度,但却会降低增加、删除、更新的速度,因为索引也要动态维护。
  • 建立索引会占用磁盘的物理空间。

3. 索引是如何提高查询速度的?

将无序的数据变成相对有序的数据(就像查目录一样)。


4. 使用索引的注意事项

  • 经常需要搜索的列上创建索引,可以加快搜索速度。
  • 经常使用在 WHERE 子句中的列上创建索引,加快条件的判断速度。
  • 经常需要排序的列上创建索引,因为索引已经排序,这样可以加快排序查询时间。
  • 对于中型、大型表,建立索引是非常有效的。但是特大型表的话,维护开销会很大,不适合建索引。
  • 经常用在 JOIN 的列上,这些列主要是一些外键,可以加快连接的速度。
  • 避免 WHERE 子句中对字段施加函数,这样会造成无法命中索引。
  • 在使用 InnoDB 时使用与业务无关的自增主键作为主键,即使用逻辑主键,而不要使用业务主键。
  • 将打算加索引的列设置为 NOT NULL,否则将导致引擎放弃使用索引而进行全表扫描。
  • 删除长期未使用的索引,不用的索引会造成不必要的性能损耗。
  • 在使用 limit offset 查询缓慢时,可以借助索引来提高性能。

5. MySQL 索引主要使用的两种数据结构

  • 哈希索引:哈希索引底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择 BTree 索引。
  • BTree 索引:BTree 索引使用的是 B 树中的 B+ Tree。但对于主要的两种存储引擎(MyISAM 和 InnoDB)的实现方式是不同的。

6. MyISAM 和 InnoDB 实现 BTree 索引方式的区别

  • MyISAM索引的 key 是数据表的主键, data 域存放的是数据记录的地址。MyISAM 的辅助索引和主索引在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。这被称为“非聚簇索引”。
  • InnoDB :其数据文件文身就是索引文件。相比 MyISAM(索引文件和数据文件是分离的),InnoDB表的数据文件本身就是按 B+ Tree 组织的一个索引结构,索引的 key 是数据表的主键,data 域保存了完整的数据记录,因此 InnoDB 表数据文件本身就是主索引,这被称为“聚簇索引”。而其余的索引都作为辅助索引,辅助索引的 data 域存储相应记录主键的值而不是地址,这也是和 MyISAM 不同的地方。
    在根据主索引搜索时,直接找到 key 所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再走一遍主索引。

MyISAM 的主索引和辅助索引:
在这里插入图片描述

InnoDB 的主索引:
在这里插入图片描述

InnoDB 的辅助索引:
在这里插入图片描述


7. 覆盖索引介绍

覆盖索引(covering index)指一个查询语句的执行只用从索引中就能够取得,不必从数据表中读取。这样避免了查到索引后再返回表操作,减少I/O提高效率。

使用覆盖索引 InnoDB 比 MyISAM 效果更好---- InnoDB 使用聚簇索引组织数据,如果二级索引中包含查询所需的数据,就不再需要在聚簇索引中查找了。

回表是指在流程中从非主键索引树搜索回到主键索引树搜索的过程。

覆盖索引实例讲解(推荐):MySQL 覆盖索引详解


8. 选择索引和编写利用这些索引的查询的3个原则

  • 单行访问是很慢的
    特别是在机械硬盘存储中(SSD 的随机 IO 要快很多,不过这一点仍然成立)。如果服务器从存储中读取一个数据块只是为了获取其中一行,那么就浪费了很多时间。最好读取的块中能包含尽可能多需要的行,使用索引可以创建位置引用,提高效率。
  • 按顺序访问范围数据是很快的
    原因1:顺序I/O不需要多次磁盘寻道,所以比随机I/O要快很多(特别是对机械硬盘)。
    原因2:如果服务器能够按需要顺序读取数据,那么就不需要额外的排序操作,并且GROUP BY 查询也无须再做排序和将行按组进行聚合计算了。
  • 索引覆盖查询是很快的
    如果一个索引包含了查询所需要的所有列,那么存储引擎就不需要再回表查找行。这避免了大量的单行访问。

总结:编写查询语句,应该尽可能选择合适的索引以避免单行查找,尽可能地使用数据原生顺序从而避免额外的排序操作,并尽可能使用覆盖索引查询。


9. 为什么索引能提高查询速度?(底层原理)

MySQL 的基本存储结构

MySQL 的基本存储结构是页(记录都存在页里边):

在这里插入图片描述
在这里插入图片描述

  • 各个数据页可以组成一个双向链表。
  • 每个数据页中的记录又可以组成一个单向链表。
    • 每个数据页都会为存储在它里边儿的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录
    • 以其他列(非主键)作为搜索条件:只能从最小记录开始依次遍历单链表中的每条记录。

所以说,如果我们写 select * from user where indexname = 'xxx' 这样没有进行任何优化的sql语句,默认会这样做:

  1. 定位到记录所在的页:需要遍历双向链表,找到所在的页
  2. 从所在的页内中查找相应的记录:由于不是根据主键查询,只能遍历所在页的单链表了

很明显,在数据量很大的情况下这样查找会很慢!这样的时间复杂度为O(n)。


使用索引优化后

索引做了些什么可以让我们查询加快速度呢?其实就是将无序的数据变成有序(相对):

在这里插入图片描述

要找到id为8的记录简要步骤:

在这里插入图片描述

很明显的是:没有用索引我们是需要遍历双向链表来定位对应的页,现在通过 “目录” 就可以很快地定位到对应的页上了!(二分查找,时间复杂度近似为O(logn))

其实底层结构就是B+树,B+树作为树的一种实现,能够让我们很快地查找出对应的记录。


10. 最左前缀原则

推荐博客:MySQL最左匹配原则,道儿上兄弟都得知道的原则


5.3 当MySQL单表记录数过大时,数据库的 CRUD 性能会明显下降,一些常见的优化措施如下:

  • 限定数据的范围: 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。

  • 读/写分离: 经典的数据库拆分方案,主库负责写,从库负责读;

  • 垂直分区: 根据数据库里面数据表的相关性进行拆分。 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表

    • 垂直拆分的优点: 可以使得行数据变小,在查询时减少读取的 Block 数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。
    • 垂直拆分的缺点: 主键会出现冗余,需要管理冗余列,并会引起 JOIN 操作,可以通过在应用层进行 JOIN 来解决。此外,垂直分区会让事务变得更加复杂;
  • 水平分区: 保持数据表结构不变,通过某种策略将存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。 水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。

    水平拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 水平拆分最好分库 。水平拆分能够 支持非常大的数据量存储,应用端改造也少,但 分片事务难以解决 ,跨界点Join性能较差,逻辑复杂。

    《Java工程师修炼之道》的作者推荐 尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度 ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。

补充:数据库分片的两种常见方案:

  • 客户端代理: 分片逻辑在应用端,封装在 jar 包中,通过修改或者封装 JDBC 层来实现。 当当网的 Sharding-JDBC 、阿里的TDDL是两种比较常用的实现。

  • 中间件代理: 在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。 我们现在谈的 Mycat、360的Atlas、网易的 DDB 等等都是这种架构的实现。


5.4 事务隔离级别

1. 什么是事务

  • 事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。
  • 在 MySQL 中只有使用了 InnoDB 数据库引擎的数据库或表才支持事务。
  • 事务用来管理 insert,update,delete 语句

2. 事务的特性(ACID)

一般来说,事务是必须满足4个条件(ACID):原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。

  • 原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
  • 一致性: 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
  • 隔离性: 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
  • 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

3. 并发事务带来的问题

在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对统一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题:

  • 脏读(Dirty read)读后写 。当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。

  • 丢失修改(Lost to modify)写后写 。指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。

  • 不可重复读(Unrepeatableread)写后读,这里的“写”指修改 。指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。

  • 幻读(Phantom read)写后读,这里的“写”指插入或删除 。幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。


4. 事务隔离级别

SQL 标准定义了四个隔离级别:

  • READ-UNCOMMITTED(读取未提交):最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • READ-COMMITTED(读取已提交):允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • REPEATABLE-READ(可重复读):对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • SERIALIZABLE(可串行化):最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。

在这里插入图片描述

MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)

这里需要注意的是:与 SQL 标准不同的地方在于 InnoDB 存储引擎在 REPEATABLE-READ(可重读)事务隔离级别下使用的是Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说 InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) 已经可以完全保证事务的隔离性要求,即达到了 SQL标准的SERIALIZABLE(可串行化)隔离级别。

因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 READ-COMMITTED(读取提交内容),但是你要知道的是 InnoDB 存储引擎默认使用 REPEATABLE-READ(可重读)并不会有任何性能损失。

InnoDB 存储引擎在 分布式事务 的情况下一般会用到 SERIALIZABLE(可串行化) 隔离级别。


5. 实际情况演示

在下面我会使用 2 个命令行mysql ,模拟多线程(多事务)对同一份数据的脏读问题。

MySQL 命令行的默认配置中事务都是自动提交的,即执行SQL语句后就会马上执行 COMMIT 操作。如果要显式地开启一个事务需要使用命令: START TARNSACTION

我们可以通过下面的命令来设置隔离级别:
SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]

我们再来看一下我们在下面实际操作中使用到的一些并发控制语句:

  • START TARNSACTION | BEGIN:显式地开启一个事务。
  • COMMIT :提交事务,使得对数据库做的所有修改成为永久性。
  • ROLLBACK: 回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。

① 脏读(读未提交)

在这里插入图片描述

② 避免脏读(读已提交)

在这里插入图片描述

③ 不可重复读

还是刚才上面的读已提交的图,虽然避免了读未提交,但是却出现了,一个事务还没有结束,就发生了 不可重复读 问题。
在这里插入图片描述

④ 可重复读

在这里插入图片描述

⑤ 防止幻读(可重复读)

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/cys975900334/article/details/115230383
今日推荐