八股文之MySQL


[TOC]

基础

1. 数据库的三范式是什么

  1. 第一范式:每个列都不可以在拆分。
  2. 第二范式:在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分
  3. 第三范式:在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键。

2. MySQL 支持哪些存储引擎?

MySQL 支持多种存储引擎,比如 InnoDB,MyISAM,Memory等等。InnoDBMySQL5.5.5之后的默认存储引擎。

MyISAMInnoDB的区别有哪些:

  • InnoDB 支持事务,外键;MyISAM不支持事务,外键。
  • InnoDB是聚集索引,数据文件是和索引绑在一起的,必须要有主键,通过主键索引效率很高;**MyISAM是非聚集索引**,数据文件是分离的,索引保存的是数据文件的指针,主键索引和辅助索引是独立的。
  • InnoDB不支持全文索引,而 MyISAM支持全文索引,查询效率上 MyISAM 要高;
  • InnoDB不保存表的具体行数,MyISAM用一个变量保存了整个表的行数。
  • MyISAM采用表级锁(table-level locking);InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁。

3. 内连接,外连接和交叉连接的区别?

表与表之间常用的关联方式有两种:内连接、外连接

交叉连接:不使用任何条件,直接将一个表的所有记录和另一个表中的所有记录一 一匹配。

内连接:**INNER JOIN**来实现,它是返回两张表中满足连接条件的数据,不满足条件的数据不会查询出来。

外连接:**OUTER JOIN来实现,它会返回两张表中满足连接条件的数据,也返回不满足连接条件的数据。外连接有两种形式:左连接(LEFT JOIN)右连接(RIGHT JOIN)**。

  • 左连接(LEFT JOIN):它会返回左表中的所有记录和右表中满足连接条件的记录。
  • 右连接(RIGHT JOIN):它会返回右表中的所有记录和左表中满足连接条件的记录。

count(1)、count(*)与count(列名)的区别

  • count(*)包括了所有的列,在统计结果的时候,包含字段为null 的记录。
  • count(1) 会统计表中的所有的记录数,包含字段为null 的记录。
  • count(列名) 会统计该列字段在表中出现的次数,不统计字段为null 的记录。

执行效率来看

  1. 若列名为主键,count(列名)会比count(1)快
  2. 若列名不为主键,count(1)会比count(列名)快
  3. 若表多个列并且没有主键,则 count(1) 的执行效率优于 count(*)
  4. 若表有主键,则 select count(主键)的执行效率是最优的
  5. 若表只有一个字段,则 select count(*)最优。

所以实际业务中一般用count(1)比较普遍,但是如果需要聚合多个列,则用count(列名)比较合适。

谈谈你对SQL注入的理解

SQL注入的原理是将SQL代码伪装到输入参数中,传递到服务器解析并执行的一种攻击手法。

如何解决SQL注入

  1. 参数校验。提前进行对特殊字符校验即可。
  2. SQL预编译。将绑定的参数传到MySQL服务器,MySQL服务器对参数进行编译,即填充到相应的占位符的过程中,做了转义操作。我们常用的JDBC就有预编译功能,不仅提升性能,而且防止SQL注入。

使用过的持久层框架,mybatis和原生JDBC区别?引导我说安全问题,又引导出sql注入问题,问mybatis是怎么解决的?什么是预编译?看过源码吗?(他说其实不是预编译,而是mybatis会做一些特殊的字符串处理,我说我看过源码,是有setString()等方法)又问mysql层面的预编译及其意义?(我猜是提高效率,他说对)

where和having有什么区别?

  • where是一个约束声明where用来约束数据库的数据,在结果返回之前起作用的,where中不能使用聚合函数。
  • having是一个过滤声明,对查询结果进行的过滤操作,在having中可以使用聚合函数;另一方面,having子句中不能使用除了分组字段和聚合函数之外的其他字段。

从性能的角度来说,having子句中如果使用了分组字段作为过滤条件,应该替换成where子句。因为having可以在执行分组操作和计算聚合函数之前过滤掉不需要的数据,性能会更好。

3. 超键、候选键、主键、外键分别是什么?

  • 超键:在关系中能唯一标识元组的属性集称为关系模式的超键。一个属性可以为作为一个超键,多个属性组合在一起也可以作为一个超键。超键包含候选键和主键。
  • 候选键:是最小超键,即没有冗余元素的超键。
  • 主键:数据库表中对储存数据对象予以唯一和完整标识的数据列或属性的组合。一个数据列只能有一个主键,且主键的取值不能缺失,即不能为空值(Null)。
  • 外键:在一个表中存在的另一个表的主键称此表的外键。

4. SQL 约束有哪几种?

  • NOT NULL: 字段的内容不能为空(NULL)。
  • UNIQUE: 字段内容不能重复,一个表允许有多个 Unique 约束。
  • PRIMARY KEY: 也是用于控件字段内容不能重复,一个表只允许一个主键。
  • FOREIGN KEY: 用于预防破坏表之间连接的动作,也能防止非法数据插入外键列,因为它必须是它指向的那个表中的值之一。
  • CHECK: 用于控制字段的值范围。

5. MySQL 中的 varchar 和 char 有什么区别?

  1. char 是定长字段。varchar是可变字段
  2. 在检索效率上来讲。char > varchar,因此在使用中,如果确定某个字段的值的长度,可以使用 char,否则应该尽量使用 varchar。例如存储用户 MD5 加密后的密码,则应该使用 char。
  3. 存储不同
    1. char占用空间较大。char类型存储是初始预计字符串+一个记录字符串长度的字节。
    2. varchar占用空间较小。varchar类型存储是实际字符串+一个记录字符串长度的字节。

6. MySQL中 in 和 exists 区别

  1. in是把外表和内表做hash连接,先查询内表;
  2. exists是对外表做loop循环,循环后再对内表查询;
  3. 在外表大的时候in效率更快,内表大用exists更快。

7. drop、delete与truncate的区别

delete,drop,truncate 都有删除表的作用,区别在于:

  1. delete 和 truncate 仅仅删除表数据,drop 连表数据和表结构一起删除
  2. delete 可以回滚,truncate 和 drop 不能回滚。
  3. 执行的速度上,drop>truncate>delete。
truncate操作与delete操作对比
操作 回滚 高水线 空间 效率
truncate 不能 下降 回收
delete 可以 不变 不回收
水位线有了解不?delete删除数据后,水位线不变会有什么影响?

https://blog.csdn.net/houdi2108/article/details/23916897

在数据库表刚建立的时候,由于没有任何数据,所以这个时候水位线是空的,也就是说HWM为最低值。

当插入了数据以后,高水位线就会上涨。但delete语句不影响高水位线,也就是说,这条高水位线在日常的增删操作中只会上涨,不会下跌。

总结:

  1. 水位线低查询速度越快,但delete不影响高水位线。所以删除1千万的数据,再出入几条数据,查询速度还是怎么慢。
  2. 再者表的存储空间一直存在,导致其他表没法使用。

8. 什么是存储过程?有哪些优缺点?

  1. 重复使用:存储过程可以重复使用,从而可以减少数据库开发人员的工作量。
  2. 减少网络流量:存储过程位于服务器上,调用的时候只需要传递存储过程的名称以及参数就可以了,因此降低了网络传输的数据量。
  3. 安全性:参数化的存储过程可以防止SQL注入式攻击,而且可以将Grant、Deny以及Revoke权限应用于存储过程。

9. MySQL查询语句的过程

  1. 客户端通过 TCP 与 MySQL 连接器进行三次握手,连接器会对该请求进行权限验证及连接资源分配
  2. 查缓存。缓存命中直接返回结果。
  3. 分析器:语法分析(SQL 语法是否写错了)
  4. 优化器。是否使用索引,生成执行计划。
  5. 执行器,操作引擎。将数据保存到结果集中,同时会逐步将数据缓存到查询缓存中,最终将结果集返回给客户端。

img

更新语句执行会复杂一点。需要检查表是否有排它锁,写 binlog,刷盘,是否执行 commit。

更新语句的执行流程

img

  1. 连接器:变更语句通过网络请求达到 MySQL 的连接器
  2. SQL 接口:调用 SQL 接口,传递请求内容到数据库组件
  3. 缓存组件:变更语句哈希(语句前后必须一样,多一个空格都不行)后查看 MySQL缓存组件是否存在(只要语句对应的表有变更则缓存全部失效),否则往下执行
  4. 分析器:分析变更语句是否符合语法,符合则往下执行
  5. 优化器:MySQL 解析完语句后根据语句优化,通过判断 IO 成本 + CPU 消耗选择性能最高的执行方式进行执行(比如选择走哪个索引,可以事前用 explain 进行执行计划查看
  6. 存储引擎:之后会调用存储引擎进行
    1. 1,先查询缓冲池有无对应数据,无则读取磁盘刷到缓存池里面(如果有自适应哈希就会命中变更前生成undolog (不同事务隔离级别生成的不一样)
    2. 2,变更缓冲池里面的数据,同时写入 redolog buffer提交事务(2PC),将 redolog 刷盘并且这行数据设置为 prepare 阶段,生成 bin log 文件(这里涉及主从复制的话,异步直接提交事务,半同步会等 binlog被任意一个从库读取完后才算提交完成等,同时将 redo log buffer 刷进 redo log 文件(修改为 commit)
  7. 整个流程基本结束

索引

索引有哪些优缺点?

索引的优点

  • 可以大大加快数据的检索速度;通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

索引的缺点

  • 索引需要动态的维护,会降低增/改/删的执行效率;
  • 空间方面:索引需要占物理空间。

MySQL有哪几种索引类型?

主键索引和唯一索引,聚簇索引与非聚簇索引,普通索引,组合索引,全文索引。

主键索引和唯一索引的区别?

  1. 主键索引是一种特殊的唯一索引,不允许有空值
  2. 唯一索引要求索引列的值必须唯一,但允许有空值。
  3. 主键是一种约束,唯一索引是一种索引,两者在本质上是不同的
  4. 主键可以被其他表引用为外键,而唯一索引不能。

聚集索引和非聚集索引的区别?

  • **InnoDB**是聚集索引,数据文件是和索引绑在一起的,必须要有主键,通过主键索引效率很高;聚簇索引的叶子节点存放了表中的所有记录;非聚簇索引的叶子节点仍然是索引节点。
  • **MyISAM**是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针,主键索引和辅助索引是独立的。

普通索引

  • 普通索引是最基本的索引,它没有任何限制,值可以为空,仅加速查询。

联合索引

  • 联合索引指在多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用联合索引时遵循最左前缀集合。

联合索引是什么?为什么需要注意联合索引中的顺序?

联合索引:多个字段组成的索引

为什么需要注意联合索引中的顺序

  • 第一个字段相同,才会去比较字段
  • 一般情况下,将查询需求频繁或者字段选择性高的列放在前面

https://blog.csdn.net/c1776167012/article/details/120788728

为什么 MySQL 不推荐使用 uuid 作为主键

  1. 增大了磁盘 IO****。因为uuid不是自增的,所以有可能需要读更多的索引页去查找合适的位置。
  2. 插入耗时变长。因为 uuid不是自增的,导致以 B+ 树为基础的索引树会在插入时,索引节点分裂的概率更高。
  3. 内存碎片变多。根据(2)中说的,分裂的越多,导致页变得稀疏,最终导致数据有碎片。

MySQL为什么经常有varchar定义长度为255

  1. 索引长度问题。建立一个字段的普通索引最大长度为768字节 = 255 * 3 = 767 + 2长度字节
  2. varchar存储长度的字段问题。mysql的varchar可以超过255,之所以默认255是因为,varchar的第一个字节存储了字符长的长度,一个字节8位,可以表示0~255,也就是说。如果你用varchar(255),只要一个字节就能存储长度了。只是分界线。

为什么MySQL不建议删除数据,而是建议逻辑删

  • 基于内存结构:基于b+树的索引调整结构耗时,并且删除后表空间并没有减少
  • 基于业务:业务数据丢失,出现问题时难以追溯。

最左匹配原则和索引下推有什么关系

最左前缀原则(基于联合索引):在查询数据时会从联合索引的最左边开始匹配,碰到范围查询时就会失效

什么是索引下推(ICP):是最左前缀原则失效的一种优化,减少了回表次数。是在查询联合索引因为模糊查询失效时,如果后面的查询条件仍然命中了联合索引值,则会继续根据查询条件进行查询。

4. 说一说索引的底层实现?

B+Tree索引

数据都在叶子节点上,并且增加了顺序访问指针,每个叶子节点都指向相邻的叶子节点的地址。进行范围查找时只需要查找两个节点,进行遍历即可。

img

b-tree和b+tree的区别

  1. B-树的关键字、索引和记录是放在一起的, B+树的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。
  2. 在B-树中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;而B+树中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。

5. 为什么索引使用B+Tree?

B+Tree

B+树的高度一般都在2~4层,只需要2到4次IO操作

  1. 减少IO次数,提高效率
  2. 查询效率更加稳定,因为数据放在叶子节点
  3. 提高范围查询的效率,因为叶子节点指向下一个叶子节点

为什么官方建议使用自增长主键作为索引。 结合B+Tree的特点,自增主键是连续的,在插入过程中尽量减少页分裂,即使要进行页分裂,也只会分裂很少一部分。并且能减少数据的移动,每次插入都是插入到最后。总之就是减少分裂和移动的频率。

7. 非聚簇索引一定会回表查询吗?

不一定,这涉及到查询语句所要求的字段是否全部命中了索引,如果全部命中了索引,那么就不必再进行回表查询。

举个简单的例子,假设我们在员工表的年龄上建立了索引,那么当进行select age from employee where age < 20的查询时,在索引的叶子节点上,已经包含了age信息,不会再次进行回表查询。

一个索引包含(覆盖)所有需要查询字段的值,被称之为”覆盖索引“。

9. 讲一讲MySQL的最左前缀原则?

MySQL建立联合索引****时会遵守最左前缀匹配原则,即最左优先,以最左边的为起点任何连续的索引都能匹配上,同时遇到范围查询(>、<、between、like)就会停止匹配。

比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。

=和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式。

11. 了解索引下推吗?

索引下推是MySQL5.6版本的新特性,它能减少回表查询次数,提高查询效率。默认开启,使用SET optimizer_switch = ‘index_condition_pushdown=off’;可以将其关闭。

12. 怎么查看MySQL语句有没有用到索引?

通过explain,如以下例子:

EXPLAIN SELECT * FROM employees.titles WHERE emp_no=’10001’ AND title=’Senior Engineer’ AND from_date=’1986-06-26’;

id select_type table partitions type possible_keys key key_len ref filtered rows Extra
1 SIMPLE titles null const PRIMARY PRIMARY 59 const,const,const 10 1
  • id:在⼀个⼤的查询语句中每个SELECT关键字都对应⼀个唯⼀的id ,如explain select * from s1 where id = (select id from s1 where name = ‘egon1’);第一个select的id是1,第二个select的id是2。有时候会出现两个select,但是id却都是1,这是因为优化器把子查询变成了连接查询 。
  • select_type:select关键字对应的那个查询的类型,如SIMPLE,PRIMARY,SUBQUERY,DEPENDENT,SNION 。
  • table:每个查询对应的表名 。
  • typetype 字段比较重要, 它提供了判断查询是否高效的重要依据依据. 通过 type 字段, 我们判断此次查询是 全表扫描 还是 索引扫描 等。如const(主键索引或者唯一二级索引进行等值匹配的情况下),ref(普通的⼆级索引列与常量进⾏等值匹配),index(扫描全表索引的覆盖索引) 。
  • 通常来说, 不同的 type 类型的性能关系如下:
    • ALL < index < range ~ index_merge < ref < eq_ref < const < system
    • ALL 类型因为是全表扫描, 因此在相同的查询条件下, 它是速度最慢的。而 index 类型的查询虽然不是全表扫描, 但是它扫描了所有的索引, 因此比 ALL 类型的稍快.
    • possible_key:查询中可能用到的索引*(可以把用不到的删掉,降低优化器的优化时间)* 。
    • key:此字段是 MySQL 在当前查询时所真正使用到的索引。
    • filtered:查询器预测满足下一次查询条件的百分比 。
    • rows 也是一个重要的字段. MySQL 查询优化器根据统计信息, 估算 SQL 要查找到结果集需要扫描读取的数据行数。这个值非常直观显示 SQL 的效率好坏, 原则上 rows 越少越好。
    • extra:表示额外信息,如Using where,Start temporary,End temporary,Using temporary等。

13. 为什么官方建议使用自增长主键作为索引?

结合B+Tree的特点,自增主键是连续的,在插入过程中尽量减少页分裂,即使要进行页分裂,也只会分裂很少一部分。并且能减少数据的移动,每次插入都是插入到最后。总之就是减少分裂和移动的频率。

插入连续的数据:

图片来自:https://www.javazhiyin.com/40232.html

img

插入非连续的数据:

img

14. 如何创建索引?

创建索引有三种方式。

1、 在执行CREATE TABLE时创建索引

CREATE` `TABLE` `user_index2 (``  ``id ``INT` `auto_increment ``PRIMARY` `KEY``,``  ``first_name ``VARCHAR` `(16),``  ``last_name ``VARCHAR` `(16),``  ``id_card ``VARCHAR` `(18),``  ``information text,``  ``KEY` `name` `(first_name, last_name),``  ``FULLTEXT ``KEY` `(information),``  ``UNIQUE` `KEY` `(id_card)``);

2、 使用ALTER TABLE命令去增加索引。

ALTER` `TABLE` `table_name ``ADD` `INDEX` `index_name (column_list);

ALTER TABLE用来创建普通索引、UNIQUE索引或PRIMARY KEY索引。

其中table_name是要增加索引的表名,column_list指出对哪些列进行索引,多列时各列之间用逗号分隔。索引名index_name可自己命名,缺省时,MySQL将根据第一个索引列赋一个名称。另外,ALTER TABLE允许在单个语句中更改多个表,因此可以在同时创建多个索引。

3、 使用CREATE INDEX命令创建。

CREATE INDEX index_name ``ON table_name (column_list);

15. 创建索引时需要注意什么?

  • 非空字段:应该指定列为NOT NULL,除非你想存储NULL。在mysql中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替空值;
  • 取值离散大的字段:(变量各个取值之间的差异程度)的列放到联合索引的前面,可以通过count()函数查看字段的差异值,返回值越大说明字段的唯一值越多字段的离散程度高;
  • 索引字段越小越好:数据库的数据存储以页为单位一页存储的数据越多一次IO操作获取的数据越大效率越高。

17. 使用索引查询一定能提高查询的性能吗?

通常通过索引查询数据比全表扫描要快。但是我们也必须注意到它的代价。

索引需要空间来存储,也需要定期维护, 每当有记录在表中增减或索引列被修改时,索引本身也会被修改。 这意味着每条记录的I* NSERT,DELETE,UPDATE将为此多付出4,5 次的磁盘I/O。 因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢。使用索引查询不一定能提高查询性能,索引范围查询(INDEX RANGE SCAN)适用于两种情况:

  • 基于一个范围的检索,一般查询返回结果集小于表中记录数的30%。
  • 基于非唯一性索引的检索。

什么情况下不走索引(索引失效)?

  1. like查询是以%开头
    1. SELECT * FROM user WHERE name LIKE '%冰';当%放在匹配字段前是不走索引的,放在后面才会走索引。
  2. 使用!= 或者 <> 导致索引失效
  3. 运算符导致的索引失效
    1. SELECT * FROM user WHERE age - 1` `= 20``;如果你对列进行了(+,-,*,/,!), 那么都将不会走索引。
  4. OR引起的索引失效
    1. SELECT * FROM user WHERE name = '张三'` `OR height = ‘175’``;OR导致索引是在特定情况下的。
    2. 注意:要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引。
  5. 函数导致的索引失效
    1. 如:SELECT * FROM user WHERE DATE(create_time) = '2020-09-03';如果使用函数在索引列,这是不走索引的。
  6. 类型不一致导致的索引失效
  7. NOT IN、NOT EXISTS导致索引失效
  8. 字符串不加单引号索引失效
    1. SELECT * from staffs where name='2000'; – 因为mysql会在底层对其进行隐式的类型转换

事务

说一下MySQL的四种隔离级别 :读未提交,读已提交,可重复读,串行化。

读取未提交。会导致脏

  • 解决办法:1,把隔离级别调整到读已提交;读取时,加共享锁,读取完之后释放事务。2,修改时,加入排他锁,直至事务提交之后才释放。

读取已提交。也称不可重复读:指的是一个事务先后读取同一条记录,但两次读取的数据不同。

  • 解决办法:方法1,把事务隔离级别调整到可重复读。方法2:读取数据时加共享锁,写数据时加排他锁,都是事务提交才释放锁。读取时候不允许其他事物修改该数据,不管数据在事务过程中读取多少次,数据都是一致的,避免了不可重复读问题。

可重复读,是MySQL的默认事务隔离级别。会出现幻读:指的是同一个事务中,用同样的的操作读取两次,得到的记录数不相同。

  • 解决办法:把事务隔离级别调整到串行化。

Serializable (可串行化)

  • 通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

MySQL默认隔离级别是可重复读Oracle默认的是读已提交隔离级别事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC (多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。

  1. 因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是读已提交;但是你要知道的是InnoDB存储引擎默认使用可重读并不会有任何性能损失。
  2. InnoDB存储引擎在分布式事务的情况下一般会用到SERIALIZABLE(可串行化)隔离级别。

介绍一下事务具有的四个特征

事务就是一组原子性的操作,这些操作要么全部发生,要么全部不发生。事务把数据库从一种一致性状态转换成另一种一致性状态。

  • 原子性(Atomic) :对数据的修改要么全部执行,要么全部不执行。
  • 一致性(Consistent): 在事务执行前后,数据状态保持一致性。
  • 隔离性(Isolated) :一个事务的处理不能影响另一个事务的处理。
  • 持久性(Durable) :事务处理结束,其效果在数据库中持久化。

Mysql怎么保证原子性的?

利用Innodb的undo log,当事务对数据库进行修改时,InnoDB会生成对应的undo log。如果事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。

undo log属于逻辑日志,它记录的是sql执行相关的信息。当发生回滚时,InnoDB会根据undo log的内容做与之前相反的工作。对于insert,回滚时会执行delete。对于delete,回滚时会执行insert。对于update,回滚时则会执行相反的update,把数据改回去。

Mysql怎么保证持久性的?

是利用Innodb的redo log

MySQL的innoDB存储引擎,使用Redo log保证了事务的持久性。当事务提交时,先将事务的所有日志写入日志文件进行持久化,就是我们常说的WAL(write ahead log)机制。保证断电或宕机等情况发生后,已提交的事务不会丢失,这个能力称为crash-safe

Mysql是先把磁盘上的数据加载到内存中,在内存中对数据进行修改,再刷回磁盘上。即使突然宕机,内存中的数据也不会丢失。

Mysql隔离性实现原理:

MySQL实现隔离性本质是通过多版本并发控制(MVCC)和 读视图(Read View)。

  • 多版本并发控制(MVCC) 是MySQL解决读写冲突的一种无锁的策略,根据事务开始的先后顺序,按递增为事务分配不同的事务ID
  • 读视图(Read View)是MySQL 源码中的一个类。事务进行 快照读 的时候生产读视图 (Read View),可以记录维护系统当前活跃的事务的ID等信息。

当前读和快照读: 读取某条记录的最新版本就是当前读,读取历史版本叫做快照读。 增删改都是当前读,需要加锁,select操作也有可能是当前读,这时也需要加锁 这时候这些当前读就是串行化的。快照读读取的是历史版本,所以不用加锁,这就是mvcc提高读写并发效率的方式。

什么是 MVCC?

MVCC是多版本并发控制。MVCC是通过对比版本号来实现,是通过保存数据在某个时间点的快照来实现的。根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。

MVCC 的实现原理

对于 InnoDB ,MVCC 记录中包含 3 个隐藏的列:

  • ROW ID:隐藏的自增 ID,如果表没有主键,InnoDB 会自动按 ROW ID 产生一个聚集索引树。
  • 事务 ID:记录最后一次修改该记录的事务 ID。
  • 回滚指针:指向这条记录的上一个版本。

我们拿上面的例子,对应解释下 MVCC 的实现原理,如下图:

img

如图,首先 insert 语句向表 t1 中插入了一条数据,a 字段为 1,b 字段为 1, ROW ID 也为 1 ,事务 ID 假设为 1,回滚指针假设为 null。当执行 update t1 set b=666 where a=1 时,大致步骤如下:

  • 数据库会先对满足 a=1 的行加排他锁;
  • 然后将原记录复制到 undo 表空间中;
  • 修改 b 字段的值为 666,修改事务 ID 为 2;
  • 并通过隐藏的回滚指针指向 undo log 中的历史记录;
  • 事务提交,释放前面对满足 a=1 的行所加的排他锁。

在前面实验的第 6 步中,session2 查询的结果是 session1 修改之前的记录,这个记录就是来自 undolog 中。

因此可以总结出 MVCC 实现的原理大致是:

InnoDB 每一行数据都有一个隐藏的回滚指针,用于指向该行修改前的最后一个历史版本,这个历史版本存放在 undo log 中。如果要执行更新操作,会将原记录放入 undo log 中,并通过隐藏的回滚指针指向 undo log 中的原记录。其它事务此时需要查询时,就是查询 undo log 中这行数据的最后一个历史版本。

MVCC 最大的好处是读不加锁,读写不冲突,极大地增加了 MySQL 的并发性。通过 MVCC,保证了事务 ACID 中的 I(隔离性)特性。

redo log与bin log,undo log

redo log

  • **redo log(重做日志)**的作用:当我们修改数据,写完内存,但是数据还没有真正写到磁盘的时候,此时我们的数据库挂掉了,我们就可以根据redo log对数据进行恢复,同时因为redo log是顺序io,所以写入速度很快,同时redo log记录的是物理变化,(xxx页做了什么修改),所以体积很小。

bin log

  • **bin log(归档日志)**是记录数据库表结构和表表数据的变更,(比如 update/insert/delete/truncate),它不会记录select,因为select没有变化
  • binlog一般用来复制和恢复数据,比如现在mysql一般都是一主多从,从服务器与主服务器数据保持一致,一般都是通过binlog来做又因为binlog其实记录着数据库表的变化,所以可以用来做数据恢复和主从复制

undo log

  • undo log是回滚日志。主要用来回滚和mvcc多版本控制。比如说我们要insert一条语句,那么undolog就记录着一条delete语句,所以说它可以用来做回滚
  • undo log记录的是修改之前的数据,相当于前一个版本,而mvcc实现的是读写不阻塞,读的时候只要返回前一个版本的数据就好了。

redo log与bin log的区别

有以下三点不同。

  1. redo log是InnoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用。
  2. redo log是物理日志,记录的是“在某个数据页上做了什么修改”;binlog是逻辑日志,记录的 是这个语句的原始逻辑,比如“给ID=2这一行的c字段加1 ”。
  3. redo log是循环写的,空间固定会用完;binlog是可以追加写入的。“追加写”是指binlog文件 写到一定大小后会切换到下一个,并不会覆盖以前的日志。

简述 MySQL 中的 MVCC、readview、undolog 的概念及相互的联系

MVCC

MVCC 是 MySQL 中处理并发读写的一种机制,让一个数据有多个版本,保证读写操作没有冲突。MVCC 具体是由 readview 和 undlog 还有每行的三个隐藏的字段实现的。

readview

readview 是事务进行时生成的数据快照。事务隔离级别不同生成的时机不一样(比如 RC 是每次查询的时候都会生成一个,之后快照读都会读最新;RR 是第一次查询时会生成之后就不再生成,之后快照读都会读同一个),readview 本质上其实是根据每行的记录隐藏的当前事务 ID,去读取 undolog 的记录来生成当前事务允许读到的数据集,所以才有快照读这个说法。

undolog

undolog 是回滚日志(实现了 ACID 中的 A 原子性),在执行任何变更操作都会生成一条记录对应一个 undolog 记录,每行记录在一个事务内变更的记录都会生成一个一个 undolog 让他们从近到远的顺序连接在一起形成 undolog 版本链

总结

MVCC 的实现基于 readview ,而 readview 的实现基于 undolog 以及每行的隐藏字段(事务 ID 等)。

1. 为什么要加锁?

当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。保证数据库完整性和一致性。

2. 按照锁的粒度分数据库锁有哪些?

在关系型数据库中,可以按照锁的粒度把数据库锁分行级锁(INNODB引擎)、表级锁(MYISAM引擎)和页级锁(BDB引擎 )。

行级锁

  • 行级锁是MySQL中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁 和 排他锁。
  • 开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

表级锁

  • 表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。
  • 开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。

页级锁

  • 页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。BDB支持页级锁
  • 开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

MyISAM和InnoDB存储引擎使用的锁:

  • MyISAM采用表级锁(table-level locking)。
  • InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁

3. 从锁的类别上分MySQL都有哪些锁呢?

从锁的类别上来讲,有共享锁和排他锁

  • 共享锁: 又叫做读锁。 当用户要进行数据的读取时,对数据加上共享锁。共享锁可以同时加上多个。
  • 排他锁: 又叫做写锁。 当用户要进行数据的写入时,对数据加上排他锁。排他锁只可以加一个,他和其他的排他锁,共享锁都相斥。

用上面的例子来说就是用户的行为有两种,一种是来看房,多个用户一起看房是可以接受的。 一种是真正的入住一晚,在这期间,无论是想入住的还是想看房的都不可以。

锁的粒度取决于具体的存储引擎,InnoDB实现了行级锁,页级锁,表级锁。

他们的加锁开销从大到小,并发能力也是从大到小。

4. 数据库的乐观锁和悲观锁是什么?怎么实现的?

乐观锁和悲观锁的区别

  1. 悲观锁(Pessimistic Lock)
    1. 每次获取数据的时候,都会担心数据被修改,所以每次获取数据都加锁。由于数据进行加锁,期间对该数据进行读写的其他线程都会进行等待。
      1. 实现乐观锁的方式就是记录数据版本(version)或者是时间戳来实现CAS思想:https://blog.csdn.net/qq_44625745/article/details/125364292
    2. 悲观锁适用场景:比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。
  2. 乐观锁(Optimistic Lock)
    1. 每次获取数据的时候,都不会担心数据被修改,所以每次获取数据都不加锁,但是在更新数据的时候需要判断该数据是否被别人修改过。如果数据被其他线程修改,则不进行数据更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁,期间该数据可以被其他线程进行读写操作。
      1. 乐观锁实现:对数据表增加一个数字类型的version字段来实现。读取记录时,将version字段一同读出,数据每更新一次,对version字段+1。当更新记录时,检查记录当前version是否与之前读取时的相同,只有相同才给予记录更新操作。
    2. 乐观锁适用场景:比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。

总结:两种所各有优缺点,读取频繁用乐观锁,写入频繁用悲观锁。

CAS

说到乐观锁,就必须提到一个概念:CAS:Compare-and-Swap,即比较并替换,也有叫做Compare-and-Set的,比较并设置。

1、比较:读取到了一个值A,在将其更新为B之前,检查原值是否仍为A(未被其他线程改动)。

2、设置:如果是,将A更新为B,结束。如果不是,则什么都不做。

上面的两步操作是原子性的,可以简单地理解为瞬间完成,在CPU看来就是一步操作。

有了CAS,就可以实现一个乐观锁,允许多个线程同时读取(因为根本没有加锁操作),但是只有一个线程可以成功更新数据,并导致其他要更新数据的线程回滚重试。 CAS利用CPU指令,从硬件层面保证了操作的原子性,以达到类似于锁的效果。

Java中真正的CAS操作调用的native方法

因为整个过程中并没有“加锁”和“解锁”操作,因此乐观锁策略也被称为无锁编程。换句话说,乐观锁其实不是“锁”,它仅仅是一个循环重试CAS的算法而已,但是CAS有一个问题那就是会产生ABA问题,什么是ABA问题,以及如何解决呢?

ABA 问题:

如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 “ABA”问题。

ABA 问题解决:

我们需要加上一个版本号(Version),在每次提交的时候将版本号+1操作,那么下个线程去提交修改的时候,会带上版本号去判断,如果版本修改了,那么线程重试或者提示错误信息~

5. InnoDB引擎的行锁是怎么实现的?

InnoDB是基于索引来完成行锁

例: select * from tab_with_index where id = 1 for update;

for update 可以根据条件来完成行锁锁定,并且 id 是有索引键的列,如果 id 不是索引键那么InnoDB将完成表锁,并发将无从谈起

6. 什么是死锁?怎么解决?

死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。

常见的解决死锁的方法

1、如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。

2、在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;

3、对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;

如果业务处理不好可以用分布式事务锁或者使用乐观锁

8. 优化锁方面的意见?

  • 使用较低的隔离级别
  • 设计索引,尽量使用索引去访问数据,加锁更加精确,从而减少锁冲突
  • 选择合理的事务大小,给记录显示加锁时,最好一次性请求足够级别的锁。列如,修改数据的话,最好申请排他锁,而不是先申请共享锁,修改时在申请排他锁,这样会导致死锁
  • 不同的程序访问一组表的时候,应尽量约定一个相同的顺序访问各表,对于一个表而言,尽可能的固定顺序的获取表中的行。这样大大的减少死锁的机会。
  • 尽量使用相等条件访问数据,这样可以避免间隙锁对并发插入的影响
  • 不要申请超过实际需要的锁级别
  • 数据查询的时候不是必要,不要使用加锁。MySQL的MVCC可以实现事务中的查询不用加锁,优化事务性能:MVCC只在committed read(读提交)和 repeatable read (可重复读)两种隔离级别
  • 对于特定的事务,可以使用表锁来提高处理速度活着减少死锁的可能。

分库分表

1. 为什么要分库分表?

分表

比如你单表都几千万数据了,你确定你能扛住么?绝对不行,单表数据量太大,会极大影响你的 sql执行的性能,到了后面你的 sql 可能就跑的很慢了。一般来说,就以我的经验来看,单表到几百万的时候,性能就会相对差一些了,你就得分表了。

分表就是把一个表的数据放到多个表中,然后查询的时候你就查一个表。比如按照用户 id 来分表,将一个用户的数据就放在一个表中。然后操作的时候你对一个用户就操作那个表就好了。这样可以控制每个表的数据量在可控的范围内,比如每个表就固定在 200 万以内。

分库

分库就是你一个库一般我们经验而言,最多支撑到并发 2000,一定要扩容了,而且一个健康的单库并发值你最好保持在每秒 1000 左右,不要太大。那么你可以将一个库的数据拆分到多个库中,访问的时候就访问一个库好了。

这就是所谓的分库分表。

2. 用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?

这个其实就是看看你了解哪些分库分表的中间件,各个中间件的优缺点是啥?然后你用过哪些分库分表的中间件。

比较常见的包括:

  • cobar
  • TDDL
  • atlas
  • sharding-jdbc
  • mycat

cobar

阿里 b2b 团队开发和开源的,属于 proxy 层方案。早些年还可以用,但是最近几年都没更新了,基本没啥人用,差不多算是被抛弃的状态吧。而且不支持读写分离、存储过程、跨库 join 和分页等操作。

TDDL

淘宝团队开发的,属于 client 层方案。支持基本的 crud 语法和读写分离,但不支持 join、多表查询等语法。目前使用的也不多,因为还依赖淘宝的 diamond 配置管理系统。

atlas

360 开源的,属于 proxy 层方案,以前是有一些公司在用的,但是确实有一个很大的问题就是社区最新的维护都在 5 年前了。所以,现在用的公司基本也很少了。

sharding-jdbc

当当开源的,属于 client 层方案。确实之前用的还比较多一些,因为 SQL 语法支持也比较多,没有太多限制,而且目前推出到了 2.0 版本,支持分库分表、读写分离、分布式 id 生成、柔性事务(最大努力送达型事务、TCC 事务)。而且确实之前使用的公司会比较多一些(这个在官网有登记使用的公司,可以看到从 2017 年一直到现在,是有不少公司在用的),目前社区也还一直在开发和维护,还算是比较活跃,个人认为算是一个现在也可以选择的方案

mycat

基于 cobar 改造的,属于 proxy 层方案,支持的功能非常完善,而且目前应该是非常火的而且不断流行的数据库中间件,社区很活跃,也有一些公司开始在用了。但是确实相比于 sharding jdbc 来说,年轻一些,经历的锤炼少一些。

3. 如何对数据库如何进行垂直拆分或水平拆分的?

水平拆分的意思,就是把一个表的数据给弄到多个库的多个表里去,但是每个库的表结构都一样,只不过每个库表放的数据是不同的,所有库表的数据加起来就是全部数据。水平拆分的意义,就是将数据均匀放更多的库里,然后用多个库来抗更高的并发,还有就是用多个库的存储容量来进行扩容。

img

垂直拆分的意思,就是把一个有很多字段的表给拆分成多个表或者是多个库上去。每个库表的结构都不一样,每个库表都包含部分字段。一般来说,会将较少的访问频率很高的字段放到一个表里去,然后将较多的访问频率很低的字段放到另外一个表里去。因为数据库是有缓存的,你访问频率高的行字段越少,就可以在缓存里缓存更多的行,性能就越好。这个一般在表层面做的较多一些。

img

两种分库分表的方式

  • 一种是按照 range 来分,就是每个库一段连续的数据,这个一般是按比如时间范围来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。
  • 或者是按照某个字段hash一下均匀分散,这个较为常用。

range 来分,好处在于说,扩容的时候很简单,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了;缺点,但是大部分的请求,都是访问最新的数据。实际生产用 range,要看场景。

hash 分发,好处在于说,可以平均分配每个库的数据量和请求压力;坏处在于说扩容起来比较麻烦,会有一个数据迁移的过程,之前的数据需要重新计算 hash 值重新分配到不同的库或表

读写分离、主从同步(复制)

1. 什么是MySQL主从同步?

主从同步使得数据可以从一个数据库服务器复制到其他服务器上,在复制数据时,一个服务器充当主服务器(master),其余的服务器充当从服务器(slave)。

因为复制是异步进行的,所以从服务器不需要一直连接着主服务器,从服务器甚至可以通过拨号断断续续地连接主服务器。通过配置文件,可以指定复制所有的数据库,某个数据库,甚至是某个数据库上的某个表。

2. MySQL主从同步的目的?为什么要做主从同步?

  1. 通过增加从服务器来提高数据库的性能,在主服务器上执行写入和更新,在从服务器上向外提供读功能,可以动态地调整从服务器的数量,从而调整整个数据库的性能。
  2. 提高数据安全-因为数据已复制到从服务器,从服务器可以终止复制进程,所以,可以在从服务器上备份而不破坏主服务器相应数据
  3. 在主服务器上生成实时数据,而在从服务器上分析这些数据,从而提高主服务器的性能
  4. 数据备份。一般我们都会做数据备份,可能是写定时任务,一些特殊行业可能还需要手动备份,有些行业要求备份和原数据不能在同一个地方,所以主从就能很好的解决这个问题,不仅备份及时,而且还可以多地备份,保证数据的安全

3. 如何实现MySQL的读写分离?

其实很简单,就是基于主从复制架构,简单来说,就搞一个主库,挂多个从库,然后我们就单单只是写主库,然后主库会自动把数据给同步到从库上去。

4. MySQL主从复制流程和原理?

基本原理流程,是3个线程以及之间的关联

主:binlog线程——记录下所有改变了数据库数据的语句,放进master上的binlog中;

从:io线程——在使用start slave 之后,负责从master上拉取 binlog 内容,放进自己的relay log中;

从:sql执行线程——执行relay log中的语句;

复制过程如下

img

Binary log:主数据库的二进制日志

Relay log:从服务器的中继日志

第一步:master在每个事务更新数据完成之前,将该操作记录串行地写入到binlog文件中。

第二步:salve开启一个I/O Thread,该线程在master打开一个普通连接,主要工作是binlog dump process。如果读取的进度已经跟上了master,就进入睡眠状态并等待master产生新的事件。I/O线程最终的目的是将这些事件写入到中继日志中。

第三步:SQL Thread会读取中继日志,并顺序执行该日志中的SQL事件,从而与主数据库中的数据保持一致。

5. MySQL主从同步延时问题如何解决?

MySQL 实际上在有两个同步机制,一个是半同步复制,用来 解决主库数据丢失问题;一个是并行复制,用来 解决主从同步延时问题。

  • 半同步复制,也叫 semi-sync 复制,指的就是主库写入 binlog 日志之后,就会将强制此时立即将数据同步到从库,从库将日志写入自己本地的 relay log 之后,接着会返回一个 ack 给主库,主库接收到至少一个从库的 ack 之后才会认为写操作完成了。
  • 并行复制,指的是从库开启多个线程,并行读取 relay log 中不同库的日志,然后并行重放不同库的日志,这是库级别的并行。

MySQL优化

1. 如何定位及优化SQL语句的性能问题?

对于低性能的SQL语句的定位,最重要也是最有效的方法就是使用执行计划,MySQL提供了explain命令来查看语句的执行计划。 我们知道,不管是哪种数据库,或者是哪种数据库引擎,在对一条SQL语句进行执行的过程中都会做很多相关的优化,对于查询语句,最重要的优化方式就是使用索引。

而执行计划,就是显示数据库引擎对于SQL语句的执行的详细情况,其中包含了是否使用索引,使用什么索引,使用的索引的相关信息等。

img

2. 大表数据查询,怎么优化

  • 优化shema、sql语句+索引;
  • 第二加缓存,memcached, redis
  • 主从复制,读写分离;
  • 垂直拆分,根据你模块的耦合度,将一个大的系统分为多个小的系统,也就是分布式系统;
  • 水平切分,针对数据量大的表,这一步最麻烦,最能考验技术水平,要选择一个合理的sharding key, 为了有好的查询效率,表结构也要改动,做一定的冗余,应用也要改,sql中尽量带sharding key,将数据定位到限定的表上去查,而不是扫描全部的表;

3. 超大分页怎么处理?

数据库层面,这也是我们主要集中关注的(虽然收效没那么大),类似于select * from table where age > 20 limit 1000000,10 这种查询其实也是有可以优化的余地的. 这条语句需要 load1000000 数据然后基本上全部丢弃,只取 10 条当然比较慢. 当时我们可以修改为select * from table where id in (select id from table where age > 20 limit 1000000,10).这样虽然也 load 了一百万的数据,但是由于索引覆盖,要查询的所有字段都在索引中,所以速度会很快。

解决超大分页,其实主要是靠缓存,可预测性的提前查到内容,缓存至redis等k-V数据库中,直接返回即可.

阿里巴巴《Java开发手册》中,对超大分页的解决办法是类似于上面提到的第一种.

【推荐】利用延迟关联或者子查询优化超多分页场景。

说明:MySQL并不是跳过offset行,而是取offset+N行,然后返回放弃前offset行,返回N行,那当offset特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行SQL改写。

正例:先快速定位需要获取的id段,然后再关联:

SELECT a.* FROM 表1 a, (select id from 表1 where 条件 LIMIT 100000,20 ) b where a.id=b.id

4. 统计过慢查询吗?对慢查询都怎么优化过?

在业务系统中,除了使用主键进行的查询,其他的我都会在测试库上测试其耗时,慢查询的统计主要由运维在做,会定期将业务中的慢查询反馈给我们。

慢查询的优化首先要搞明白慢的原因是什么? 是查询条件没有命中索引?是load了不需要的数据列?还是数据量太大?

所以优化也是针对这三个方向来的,

  • 首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。
  • 分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。
  • 如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表。

5. 如何优化查询过程中的数据访问

  • 访问数据太多导致查询性能下降
  • 确定应用程序是否在检索大量超过需要的数据,可能是太多行或列
  • 确认MySQL服务器是否在分析大量不必要的数据行
  • 查询不需要的数据。解决办法:使用limit解决
  • 多表关联返回全部列。解决办法:指定列名
  • 总是返回全部列。解决办法:避免使用SELECT *
  • 重复查询相同的数据。解决办法:可以缓存数据,下次直接读取缓存
  • 是否在扫描额外的记录。解决办法:
  • 使用explain进行分析,如果发现查询需要扫描大量的数据,但只返回少数的行,可以通过如下技巧去优化:
  • 使用索引覆盖扫描,把所有的列都放到索引中,这样存储引擎不需要回表获取对应行就可以返回结果。
  • 改变数据库和表的结构,修改数据表范式
  • 重写SQL语句,让优化器可以以更优的方式执行查询。

6. 如何优化关联查询

  • 确定ON或者USING子句中是否有索引。
  • 确保GROUP BY和ORDER BY只有一个表中的列,这样MySQL才有可能使用索引。

7. 数据库结构优化

一个好的数据库设计方案对于数据库的性能往往会起到事半功倍的效果。

需要考虑数据冗余、查询和更新的速度、字段的数据类型是否合理等多方面的内容。

  1. 将字段很多的表分解成多个表

对于字段较多的表,如果有些字段的使用频率很低,可以将这些字段分离出来形成新表。

因为当一个表的数据量很大时,会由于使用频率低的字段的存在而变慢。

  1. 增加中间表

对于需要经常联合查询的表,可以建立中间表以提高查询效率。

通过建立中间表,将需要通过联合查询的数据插入到中间表中,然后将原来的联合查询改为对中间表的查询。

  1. 增加冗余字段

设计数据表时应尽量遵循范式理论的规约,尽可能的减少冗余字段,让数据库设计看起来精致、优雅。但是,合理的加入冗余字段可以提高查询速度。

表的规范化程度越高,表和表之间的关系越多,需要连接查询的情况也就越多,性能也就越差。

注意:

冗余字段的值在一个表中修改了,就要想办法在其他表中更新,否则就会导致数据不一致的问题。

8. MySQL数据库cpu飙升到500%的话他怎么处理?

当 cpu 飙升到 500%时,先用操作系统命令 top 命令观察是不是 MySQLd 占用导致的,如果不是,找出占用高的进程,并进行相关处理。

如果是 MySQLd 造成的, show processlist,看看里面跑的 session 情况,是不是有消耗资源的 sql 在运行。找出消耗高的 sql,看看执行计划是否准确, index 是否缺失,或者实在是数据量太大造成。

一般来说,肯定要 kill 掉这些线程(同时观察 cpu 使用率是否下降),等进行相应的调整(比如说加索引、改 sql、改内存参数)之后,再重新跑这些 SQL。

也有可能是每个 sql 消耗资源并不多,但是突然之间,有大量的 session 连进来导致 cpu 飙升,这种情况就需要跟应用一起来分析为何连接数会激增,再做出相应的调整,比如说限制连接数等。

如何对业务上的查询接口进行查询优化(假设存储是 MySQL 的情况)

利用索引的数据结构特性

  • 适时利用覆盖索引,命中最左匹配原则,开启索引下推功能,减少回表次数
  • 使用聚簇索引的特性,查询分页时往往都会使用 offset + limit,而当数据量大的时候,offset 会先查询偏移量的数据值,达不到优化的效果,这时可以使用 MySQL 索引的 B+ 树数据结构的特性,使用 id > xxx + limit 值进行分页,但这个情况仅仅使用于数据增长的顺序和 id 成正比的情况

起始位置重定义

记住上次查找结果的主键位置,避免使用偏移量 offset。

降级策略

拦截非法请求,基于第二点的查询策略优化,设置降级阈值,当 id 大于某个值时不再查询数据库

9. 大表怎么优化?

类似的问题:某个表有近千万数据,CRUD比较慢,如何优化?分库分表了是怎么做的?分表分库了有什么问题?有用到中间件么?他们的原理知道么?

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

  • 限定数据的范围: 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内;
  • 读/写分离: 经典的数据库拆分方案,主库负责写,从库负责读;
  • 缓存: 使用MySQL的缓存,另外对重量级、更新少的数据可以考虑;
  • 通过分库分表的方式进行优化,主要有垂直分表和水平分表。

sql中左连接和右连接的区别

左连接:只要左边表中有记录,数据就能检索出来,而右边有的记录必要在左边表中有的记录才能被检索出来。

右连接:右连接是只要右边表中有记录,数据就能检索出来。

分布式事务

1.为什么需要分布式事务

因为随着历史的发展,出现了分库分表,微服务化。经过了拆分后,本来能在一个数据库上完成的操作会被拆分成在多个数据库完成,而数据库的事务只能保证单机上的数据一致性而不能保证跨数据库的一致性,但是业务又需要多个数据库上的操作是个原子操作且数据一致,所以就出现了分布式事务

2.什么是分布式事务

从常用的 MySQL 数据库存储中可以知道,「事务」用来保证单机数据库的数据一致性,而「分布式事务」是用来分布式服务集群上的数据一致性,分布式事务的一个操作会被拆分成各个小操作分散在分布式集群的不同服务。

3.什么是 XA 协议

XA 协议是 X/OPEN 组织定义的一种分布式事务的规范协议。协议定义出一个保证分布式事务数据一致问题的模型,模型中包含全局事务管理器,和资源管理器,以及两个管理器之间的交互接口。

  • 全局事务管理器一般是数据库中间件去承担
  • 资源管理器一般指分布式集群每个服务的对应的数据库们,而且都是资源管理器数量都是 2 个以上,1 个的话也不存在分布式问题。不过 MySQL 有实现内部 XA 的 2PC(虽然是单机,引擎层和服务层之间的数据一致)

4.基于 XA 协延伸二阶段提交(2PC)、和三阶段提交(3PC)

无论是 2PC 还是 3PC,都是基于 XA 延伸出来的解决方案

2PC 主要流程是通过事务管理者向每个资源管理发出 prepare 的信号询问是否可以提交事务,等每个资源管理器都返回 OK 后,再开始对每个资源管理器提交事务 commit,或者只要有一个资源管理器不返回 ok 就 rollback。2PC 是一个比较简单的解决分布式事务一致性的方案,但同时存在一些问题

  1. 单点故障。当事务管理器在某个流程宕机后,全部资源管理器对应的事务都会锁住
  2. 同步阻塞。2PC 期间,每个资源管理器涉及到的资源都会锁住,后面的事务只能串行阻塞,并且如果因为网络原因和事务管理者无法连接,会一直锁住阻塞下去
  3. 数据不一致。尽管 2PC 是一个用来解决分布式事务一致性的方案,但是会涉及到分布式集群里面的多个服务,会存在部分提交成功,部分因为网络或者其他原因提交不成功导致的数据不一致的情况

而 3PC 比起 2PC ,仅仅是使用了「超时回滚」的机制解决了「单点故障」和「同步阻塞」,没有根本的解决数据一致性问题

3PC 的主要流程分为三步,canCommit、preCommit、doCommit。只是增加了一个 canCommit 的缓冲阶段去保证最后提交阶段之前各个资源管理器状态是一一致的。在 preCommit 和 doCommit 的阶段时,如果等待本阶段的开启命令时,因为网络原因和事务管理器挡掉等待超时都会回滚本地事务

语法

一:Sql之行数限定、数据分组、排序

行数限定:limit

select 列名 from 表名 limit[指定开始查询的行,] 查询的总行数;
  • 方括号0里的部分表示可选,不写则默认从0开始
  • 比如从工人信息表中第2行开始查看,查看5行数据,则写法如下
    • ```SQL
      select * from 工人信息 limit 1, 5;
      – 注意,实际的行要减1,因为mysq|数据库中的行数是从0开始的
      
      ## 二:数据分组
      
      格式为:
      
      ```SQL
      select列名,聚合函数from表名groupby列名;

比如统计每个部门的工资总和,即

select 部门, sum(salary) from 工人信息 group by 部门;

比如统计每个部门的工人人数,即

select 部门, count(工号) from 工人信息 group by 部门;

总结:

  1. group by 语法可以根据给定数据列的每个成员对查询结果进行分组,这里的分组就是将一个“数据集合”划分成若干个“小块”,然后对这些“小块”进行数据处理。最终得到按一个分组汇总的结果表。
  2. select 子句后面的字段一般是聚合函数或者是 group by 后面的。
  3. group by 一般和 sum、max、avg 等聚合 函数一起使用 。

我们还可以在group by 子句后面增加一个having子句,来获得满足条件的分组的返回结果。having可对限定条件进行分组,having子句后可以通过一个或多个用and和or作为连接条件。

  • 比如查看每个工人的总绩效,要求总绩效大于60,则写法如下:
select 工号, sum(performance) from 工人信息 group by工号
having  sum(performance)> 60;

注意,having子句可以包含聚合函数,但where不可以

三、排序

格式如下:

select 列名 from 表名 order by 列名; -- 默认是升序asc
select 列名 from 表名 order by 列名 desc; -- 此为降序

比如查询工人信息表中所有工人的记录,要求按绩效升序排列,绩效相同时,按工号降序排列,则可以这么写

Select * from 工人信息 order by performance, 工号 desc;

四:Sq|表连接

img

一、内部连接:即两张表相同数据交集

这里提供了两种表与表之间的连接方式

  1. 通过where关键字进行关联
  2. 通过inner join on实现连接

内部连接

1、通过where关键字进行关联

格式如下:

select 表名1.列名1,表名1.列名2,表名2.列名1,表名2.列名.....from 表名1,表名2
where 表名1.列名 = 表名2.列名;

注意:表与表之间建立起关联的列,列名可以不一样,但where条件后跟的列的数据类型和内容得保持一致

2、通过inner joinon实现连接

格式如下:

select表名1.列名1,表名1.列名2,表名2.列名1,表名2.列名2........ from 表名1
 [inner] join 表名2 on 表名1.列名 = 表名2.列名;
-- 此处的inner可写可不写

比如查询worker表中工人的工号、姓名、年龄,以及工人信息表中,工人所在的部门和薪资

从以下几个步骤来思考:

1、确定表,此例中为worker表和工人信息表

2、确定列,用到了哪张表中的哪个列,此例中为worker.工号,姓名,年龄,以及工人信息部门,薪资

3、确定表与表之间能够关联的列,此例中为部门编号

4、确定题上是否还有额外的要求,此例中没有

经过分析后,最终的sq|语句如下:

select worker.工号,姓名,年龄,工人信息.salary
from worker join 工人信息
on worker.部门编号 = 工人信息.部门编号;

再比如查询籍贯是江苏,部门以‘淘’开头的工人的工号,姓名,年龄,以及所在的部门和薪资

select worker.工号,姓名,年龄,工人信息.salary as 薪资
from worker W join 工人信息 info
on w.部门编号 = info.部门编号 and w.籍贯 = '江苏’ and info.部门名称
like '淘%';

若表名太长,可以使用表别名,此例中即w和info,若不想使用默认的字段名也可以设置字段别名,此例中即薪资

二、外部连接

它分为左外连接和右外连接

  • 左连接:两个表相交,返回左表所有的行;如果右表中没有匹配,则结果为 NULL。

img

写法:

SELECT column_name(s) FROM table1 
LEFT JOIN table2 
ON table1.column_name = table2.column_name;
  • 右连接:两个表相交,返回右表所有的行;如果左表中没有匹配,则结果为 NULL。

img

写法:

SELECT column_name(s) FROM table1 
RIGHT JOIN table2 
ON table1.column_name = table2.column_name;

子查询

即查询语句里面嵌套查询语句

格式为:

select 列名 from 表名 where 列名 in(select 查询语句);
  1. 嵌套子查询

比如查询部门编号为1的工人来自哪些地方(部门编号是在工人信息表中,籍贯在worker表中),则可以这么写

select 籍贯 from worker where 工号 
in(select 工号 from 工人信息 where 部门编号 ='1');
  1. 相关子查询

也即单值子查询,指的是子查询结果有且仅有一个值

比如查询和’千羽’在同一个地方的工人信息(工号,姓名,籍贯,部门编号),则写法如下

select worker.工号,姓名,籍贯,部门编号
from worker W,工人信息 info
where w.工号 = info.工号 and W.籍贯 
=(select 籍贯 from worker where 姓名 ='千羽');

文章作者: 千羽
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 千羽 !
评论
  目录