MySQL

1. 四大隔离级别

  • 读未提交(READ UNCOMMITTED):脏读,事务可读取其他事务未提交的,解决不了所有问题。
  • 读已提交(READ COMMITTED):只能读已提交的,能解决脏读,但不能解决不可重复读和幻读。
  • 可重复读(REPEATABLE READ):多次读同一数据,结果一致,能解决脏读和不可重复读,但不能解决幻读,这也是MySQL的默认隔离级别。
  • 串行化(SERIALIZABLE):事务完全串行执行,相当于单线程操作,可以解决所有问题,但性能较低。

1.1. 脏读

1.1.1. 定义:

脏读是指一个事务读取了另一个未提交事务修改的数据。

1.1.2. 发生原因:

  • 事务A对某条数据进行了修改,但尚未提交(可能后续会回滚)。
  • 事务B在这个时候读取了事务A修改后的数据。

1.1.3. 后果:

如果事务A最终回滚了它的操作,那么事务B读取到的数据就是无效的("脏"的),基于这些数据做出的决策可能是错误的。

1.1.4. 隔离级别控制:

  • 在读未提交(Read Uncommitted)隔离级别下,可能发生脏读。
  • 在读已提交(Read Committed)及以上隔离级别,不会发生脏读,因为事务只能读取已经提交的数据。

1.2. 不可重复读

1.2.1. 定义:

不可重复读是指在同一个事务内,多次读取同一条数据,却得到了不同的结果。这通常是由于在两次读取之间,另一个事务修改了这条数据并提交了。

1.2.2. 发生原因:

  • 事务A第一次读取某条数据,得到值X。
  • 事务B在这期间修改了这条数据并提交。
  • 事务A再次读取同一条数据,发现值变成了Y(与之前不同)。

1.2.3. 后果:

导致事务A在同一事务内读取到的数据不一致,可能影响业务逻辑的正确性,尤其是在需要数据一致性校验的场景下。

1.2.4. 隔离级别控制:

  • 在读已提交(Read Committed)隔离级别下,可能发生不可重复读,因为事务可以读取其他事务已提交的数据。
  • 在可重复读(Repeatable Read)及以上隔离级别,可以避免不可重复读,因为事务在开始时会"锁定"它读取的数据,防止其他事务修改。

1.3. 幻读

1.3.1. 定义:

幻读是指在同一个事务内,多次执行相同的查询语句,却返回了不同的结果集(多出或少了某些记录)。这通常是由于另一个事务插入或删除了符合查询条件的记录并提交了。

1.3.2. 发生原因:

  • 事务A执行查询:SELECT * FROM users WHERE age > 20,得到10条记录。
  • 事务B在此期间插入了一条age > 20的新记录并提交。
  • 事务A再次执行相同的查询,结果变成了11条记录。

事务A"幻觉"到多了一条记录,这就是幻读。

1.3.3. 后果:

可能导致数据统计错误、分页查询异常等,特别是在做数据汇总、报表生成等业务时影响较大。

1.3.4. 隔离级别控制:

  • 在可重复读(Repeatable Read)隔离级别下,可能发生幻读(虽然能防止同一事务内数据被修改,但无法阻止新数据的插入)。
  • 在串行化(Serializable)隔离级别下,可以避免幻读,因为事务是串行执行的,完全避免了并发冲突。

2. 什么是索引

是一种帮助MySQL高效获取数据的数据结构,主要用来提高数据检索效率,降低数据库的I/O成本。同时,索引列可以对数据进行排序,降低数据排序的成本,也能减少CPU的消耗。

2.1. 索引的底层结构

MySQL的默认存储引擎InnoDB使用的是B+树作为索引的存储结构。选择B+树的原因包括:节点可以有更多子节点,路径更短;磁盘读写代价更低,非叶子节点只存储键值和指针,叶子节点存储数据;B+树适合范围查询和扫描,因为叶子节点形成了一个双向链表。

2.1.1. B树和B+树

  • B 树的所有节点既存放键(key)也存放数据(data),而 B+ 树只有叶子节点存放 key 和 data,其他内节点只存放 key。
  • B 树的叶子节点都是独立的;B+ 树的叶子节点有一条引用链指向与它相邻的叶子节点。
  • B 树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了。而 B+ 树的检索效率就很稳定了,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。
  • 在 B 树中进行范围查询时,首先找到要查找的下限,然后对 B 树进行中序遍历,直到找到查找的上限;而 B+ 树的范围查询,只需要对链表进行遍历即可。

综上,B+ 树与 B 树相比,具备更少的 IO 次数、更稳定的查询效率和更适于范围查询这些优势。

2.2. 创建原则

  • 表中的数据量超过10万以上时考虑创建索引。
  • 选择查询频繁的字段作为索引,如查询条件、排序字段或分组字段。
  • 尽量使用复合索引,覆盖SQL的返回值。
  • 如果字段区分度不高,可以将其放在组合索引的后面。
  • 对于内容较长的字段,考虑使用前缀索引。
  • 控制索引数量,因为索引虽然可以提高查询速度,但也会影响插入、更新的速度。

2.2.1. 失效情况

  • 没有遵循最左匹配原则。
  • 使用了模糊查询且%号在前面。
  • 在索引字段上进行了运算或类型转换。
  • 使用了复合索引但在中间使用了范围查询,导致右边的条件索引失效。

2.2.2. 优化

遵循索引创建原则,确保索引字段是查询频繁的,使用复合索引覆盖SQL返回值,避免在索引字段上进行运算或类型转换,以及控制索引数量。

2.3. 索引类型总结

2.3.1. 按照数据结构维度划分

  • BTree 索引:MySQL 里默认和最常用的索引类型。只有叶子节点存储 value,非叶子节点只有指针和 key。
  • 哈希索引:类似键值对的形式,一次即可定位。
  • RTree 索引:一般不会使用,仅支持 geometry 数据类型,优势在于范围查找,效率较低。
  • 全文索引:对文本的内容进行分词,进行搜索。目前只有 CHARVARCHARTEXT 列上可以创建全文索引。一般不会使用,效率较低。

2.3.2. 按照底层存储方式角度划分

  • 聚簇索引(聚集索引):索引结构和数据一起存放的索引,InnoDB 中的主键索引就属于聚簇索引。
  • 非聚簇索引(非聚集索引):索引结构和数据分开存放的索引,二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。

2.3.3. 按照应用维度划分

  • 主键索引:加速查询 + 列值唯一(不可以有 NULL)+ 表中只有一个。
  • 普通索引:仅加速查询。
  • 唯一索引:加速查询 + 列值唯一(可以有 NULL)。
  • 覆盖索引:一个索引包含(或者说覆盖)所有需要查询的字段的值。
  • 联合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并。
  • 全文索引:对文本的内容进行分词,进行搜索。目前只有 CHARVARCHARTEXT 列上可以创建全文索引。一般不会使用,效率较低。
  • 前缀索引:对文本的前几个字符创建索引,相比普通索引建立的数据更小,因为只取前几个字符。

3. SQL优化经验

  • 建表时选择合适的字段类型。
  • 使用索引,遵循创建索引的原则。
  • 编写高效的SQL语句,比如避免使用SELECT *,尽量使用UNION ALL代替UNION,以及在表关联时使用INNER JOIN
  • 采用主从复制和读写分离提高性能。
  • 在数据量大时考虑分库分表。

4. 四大特性

原子性

一致性

隔离性

持久性

5. 慢查询

聚合查询

多表查询

表数据量过大查询

深度分页查询

表象:页面加载过慢,接口压测响应时间长

运维工具:Skywalking

5.1. 如何定位慢查询

部署运维工具Skywalking,在他的报表中可以看到哪个接口慢,并且能分析出接口中哪部分耗时较多,包括具体的SQL执行时间,这样就能定位到出现问题的SQL。

5.2. SQL语句执行很慢,如何分析

通常会使用MySQL的EXPLAIN命令来分析这条SQL的执行情况。通过keykey_len可以检查是否命中了索引,如果已经添加了索引,也可以判断索引是否有效。通过type字段可以查看SQL是否有优化空间,比如是否存在全索引扫描或全表扫描。通过extra建议可以判断是否出现回表情况,如果出现,可以尝试添加索引或修改返回字段来优化。

6. 部分字段区别

6.1. char和varchar的区别

  1. char是定长字符串,varchar是变长字符串(主要区别)。
  2. char在存储时会在右边填充空格以达到指定的长度,检索时会去掉空格;varchar 在存储时需要使用 1 或 2 个额外字节记录字符串的长度,检索时不需要处理。
  3. char更适合存储长度较短或者长度都差不多的字符串;varchar 类型适合存储长度不确定或者差异较大的字符串,例如用户昵称、文章标题等。

6.2. null和''的区别

  1. null代表缺失或者未知的数据,而''代表一个已知的空字符串。
  2. null代表一个不确定的值,它不等于任何值,包括自身,例如,SELECT NULL = NULL 的结果是null,而不是任何true或者false;'' 表示一个空字符串,它是一个已知的值。
  3. 存储空间:null的存储空间占用取决于数据库的实现,通常需要一些空间来标记该值为空;''的存储空间占用通常较小,因为它只存储一个空字符串的标志,而不存储实际的字符。
  4. 任何值和null进行比较(如=,>,<等)的结果都是null,表示结果不确定,而判断一个值是否为null,必须使用is null或 is not null;''则可以像其它字符串一样进行比较,'' = '' 的结果是true。

6.3. B树和B+树的区别

  1. B树的非叶子节点和叶子节点都存放数据,而B+树的所有数据只出现在叶子节点,这使得B+树在查询时效率更稳定。
  2. B+树在进行范围查询时效率更高,因为所有数据都在叶子节点,并且叶子节点之间形成了双向链表。

7. 部分名词解释

7.1. 回表查询

回表查询是指通过二级索引找到对应的主键值,然后再通过主键值查询聚簇索引中对应的整行数据的过程。

7.2. 覆盖索引

覆盖索引是指在SELECT查询中,返回的列全部能在索引中找到,避免了回表查询,提高了性能。使用覆盖索引可以减少对主键索引的查询次数,提高查询效率。