欢迎光临
我们一直在努力

Mysql八股总结:(总结必看)

优化:

慢查询:

Why:聚合查询、多表查询、深度分页查询、表数据量过大查询导致页面加载过慢、压测响应时间过长。

How:1.调试工具:Arthas 2.Mysql自带的慢日志查询:可记录超过指定时间的sql语句放入到指定的日志文件。(调试阶段使用),生产阶段不开启,否则会影响到Mysql的性能。

slow_query_log = ON # 开启慢查询日志(默认OFF)
slow_query_log_file = /var/lib/mysql/slow.log # 日志存储路径(需MySQL有写权限)
long_query_time = 2 # 慢查询阈值(单位:秒,默认10秒),超过该时间的SQL会被记录
log_queries_not_using_indexes = ON # 可选:记录未使用索引的SQL(即使没超阈值,避免“隐性慢查询”)
log_output = FILE # 日志输出方式(FILE=文件,TABLE=mysql.slow_log表,可同时配置)

What:通过关键字EXPLAIN或Desc来查询sql的执行情况;可通过key和key_len两个字段可判断索引是否被命中,如果刚添加索引,也可以进行索引是否生效的判断;可以通过Exact字段给出的优化建议来判断该sql是否需要回表查询,如果出现,可以尝试添加索引或修改返回字段来优化;可以通过type字段来判断sql的连接类型,进行性能的优化,比如是否存在全索引扫描或全表扫描。

索引:

Who:索引,在项目中经常被使用,是帮助mysql高效地获取数据的数据结构,它是基于b+树进行的,主要用来提高数据的检索效率,降低了数据库的IO消耗,同时通过索引进行数据的排序,降低了数据排序的成本,降低了数据库对cpu的消耗。

索引的数据结构(B+Tree):MySQL中的InnoDB引擎采用了B+树的数据结构来存储索引。

  • 阶数更多(分叉),路径更短。

  • 非叶子节点只存储指针,叶子节点存储数据,降低了磁盘读写消耗。

  • 叶子节点通过双向指针相互链接,构成了双向的循环列表,便于扫库、区间查询。

  • B树和B+树的区别:

  • B+树采用非叶子节点只存储索引,叶子结点存储数据,提高了数据的检索效率,降低了io的消耗。

  • B+树采用了叶子结点之间使用双向指针来链接,构成了双向循环列表,便于扫库和区间查询。

  • 聚簇索引和非聚簇索引:

  • 聚簇索引(聚集索引):索引和数据聚合到一起,叶子结点通过索引存储的是一行数据,有且只有一个。

  • 非聚簇索引(二级索引):索引和数据分开存储,叶子结点通过索引存储主键,可以有多个,通常我们自定义的索引都是非聚簇索引。

  • 回表查询:

    Who:先通过二级索引查到主键值,通过主键值再进行聚集查询,获取整行数据。

    覆盖索引:

    Who:在查询中使用了索引,并且需要返回的列,在该索引中全部被找到(一次性查询,就能全查询到Select 语句中所需要的列)。就是在查询数据时,没使用到回表查询。

    Mysql超大分页:

    Who:在进行数据查询时,数据量非常大时,利用Limit分页查询时且排序字段为索引时,且返回的列在索引中不能全部被找到,那样数据库默认使用回表查询,先进行二级索引查询limit所有主键Id,再进行聚集查询全部数据,最后获取需要的数据,那样就太耗时了。

    解决方法:利用覆盖查询+子查询。

    • 第一次覆盖查询:走二级索引(排序字段的索引),只查 “主键 ID”(二级索引里包含主键,所以是覆盖);

    • 第二次覆盖查询:走聚簇索引(主键索引),查 “所有需要的列”(聚簇索引包含所有数据,所以是覆盖)。

    实际场景例子(直接套用)

    假设我们有一张表 user,结构如下:

    • 主键:id(聚簇索引);

    • 二级索引:idx_age(age)(排序字段是age,对应你的需求);

    • 其他字段:name、address、phone(查询时需要返回这些列)。

    问题场景:超大分页查询(偏移量 10 万,取 10 条)

    SELECT id, name, address, phone FROM user ORDER BY age LIMIT 100000, 10;

    数据库默认执行逻辑(慢)

    ① 走二级索引 idx_age(age),按 age 排序,找到前 100010 条数据的主键 ID(因为要偏移10万,取10条);
    ② 对这 100010 个主键 ID,逐个去聚簇索引查对应的 name/address/phone(这就是 100010 次回表);
    ③ 丢弃前 100000 条数据,只返回最后 10 条。

    核心耗时:10 万 + 次回表(磁盘 I/O 密集操作)+ 丢弃大量数据。

    覆盖查询 + 子查询优化(快)

    SELECT u.id, u.name, u.address, u.phone
    FROM user u
    INNER JOIN (– 子查询:第一次覆盖查询(走二级索引,只查主键ID,无回表)SELECT id
    FROM user ORDER BY age LIMIT 100000, 10) AS temp ON u.id = temp.id; — 第二次覆盖查询(走聚簇索引,查所有列,无回表)

    执行逻辑:

    ① 子查询:走二级索引 idx_age(age),按 age 排序,只取“第100001-100010条”的主键 ID(10个ID)—— 覆盖查询,无回表,毫秒级;
    ② 主查询:用这 10 个 ID 去聚簇索引查对应的所有列(name/address/phone)—— 聚簇索引是主键索引,直接定位数据行,10次“精准查询”,无回表,毫秒级;
    ③ 直接返回这10条完整数据,无需丢弃。

    索引创建的原则:

  • 数据量大,且查询频率的表。

  • 经常被where、order by、group by的字段。

  • 尽量联合索引,减少单列索引,在查询时,往往更容易覆盖查询,避免回表,提高了查询效率。

  • 控制索引的数量,索引越多,插入 / 更新 / 删除时,需同时维护所有索引,索引越多,写操作的效率就越低。

  • 避免过长字段建索引,可用 “前缀索引”。

  • 如果字段区分度不高,可以将其放在组合索引的后面。

  • 索引列字段不能为null,创建表时使用not null进行约束。

  • 索引失效:

  • 不符合最左前缀法则,在使用联合索引时,左侧索引不能被跳过。

  • 当索引进行模糊匹配like时,若%在索引的左侧,则会引起索引失效,右侧时,则有效。

  • 索引上进行运算操作会导致失效。

  • 不加引号,可能会发生类型转换,也会影响索引失效。

  • 当索引进行范围判断时,右侧索引会失效, “范围判断的字段” 放在联合索引的最右侧,避免影响其他字段的索引生效

  • Sql的优化:

    • 建表时选择合适的字段类型,参考了阿里开发开发手册。

    • 使用索引,遵循创建索引的原则。

    • 编写高效的SQL语句,比如避免索引失效、避免使用SELECT *,尽量使用UNION ALL代替UNION,以及在表关联时使用INNER JOIN。

    • 采用主从复制和读写分离提高性能。

    • 在数据量大时考虑分库分表。

    事务:

    事务的特性:

    Who:ACID(可利用转账的案例进行描述)

    • 原子性:作为一组最小的不可分割的操作集合,要么同时成功,要么同时失败。

    • 一致性:所有的数据保持一致性。

    • 隔离性:数据库系统提供隔离机制,当外界发生并发操作时,保证事务不受到影响且独立环境下运行。

    • 持久性:当事务发生了提交或回滚,数据的改变是永久的

    What:将事务集合比作转账场景,当我们进行转账时,要么同时成功,要么同时失败;且资金的总和是不变的;当我们进行转账时不受外界的并发操作的影响,独立环境下运行;当转账完成后,这个数据就是不可以改变的。

    并发事务出现的问题(隔离机制)

    并发事务可能会造成脏读、不可重复读、幻读三种情况:

    • 脏读:当一个事务进行数据Update更新时,还未提交时,另一个事务获取到了这个数据。(读到了无效的数据即回滚的数据。)

    • 不可重复读:当一个事务经历两次select操作获取数据时,另一个事务在这两次数据之间进行了数据更新Update/delete,导致了两次查询的数据不一致,即造成了不可重复读的问题。(同一行数据,两次读到的值不同)

    • 幻读:当一个事务执行select-insert-select一系列操作时,在这个事务还未执行insert/delete语句时,另一个事务已经执行完插入语句了,导致了查到的数据的行数不一样。(同一查询条件,两次查询行数发生了变化)

    隔离级别:注意:1.默认的隔离级别为可重复读。2.事务的隔离级别越低,数据越安全,但性能就越底。

    undo_log和redo_log的区别:

    当进行数据页变化利用追加的操作向redo_log同步信息后,内存结构向磁盘结构通过WAL(顺序磁盘io写操作性能很高)。遵循日志先行,数据延迟。

  • 先修改内存中的 “数据页”(Buffer Pool 里的页);

  • 同时把 “数据页的物理变更” 写入 redo_log(顺序磁盘 IO,极快);

  • 事务提交后,redo_log 会被标记为 “已提交”,此时事务的 “持久性” 已保证;

  • 后台线程(Master Thread)会异步、批量把内存中的数据页刷到磁盘(延迟刷盘,避免频繁随机 IO)。

  • redo_log:记录的是数据页变化的物理日志,当数据库宕机时可恢复数据,保证了事物的持久性。

    undo_log:记录的是数据库与原操作相反的业务逻辑的逻辑日志,当进行回滚时,通过逆操作来进行数据的回滚,保证了事物的原子性和一致性。

    事物的隔离性是如何保证的:

    锁:排他锁(当一个事务获取了单行数据的排他锁,其他的事务就不能获取这行数据的排他锁了)

    Mvcc:多版本并发控制:指维护了一行数据的多个版本,避免了读写操作的冲突

      作用:其实就是在快照读时,决定读取哪个版本。

    二者互补:锁负责处理「写 – 写冲突」和「当前读的读 – 写冲突」,MVCC 负责处理「快照读的读 – 写冲突」

    MVCC实现:

  • 隐藏字段:DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID.

  • undo_log日志:

  •   版本链:当一条记录被同一事务或不同事务修改时,undo_log就会生成一条记录版本的数据,通过回滚指针将其链接成记录版本的数据链表,该数据链表的头部为最新的旧数据,尾部为最旧的旧数据。

  • ReadView读视图:是当执行快照的sql语句时MVCC提取数据的依据,记录并维护系统当前活跃的事物的id。

  •   当前读:读取的是当前最新的版本,读取过程中其他线程不会出现对这条记录并发的状况,因为在读取时对这条记录进行了加锁(共享锁或者排他锁)。

      快照读:普通的Select语句,不加锁,读取的数据是可见版本。

    • RC隔离级别:当每次进行快照读时,都会生成最新的ReadView读视图。

    • RR隔离级别:当第一次进行快照读时,会生成ReadView读视图,后续还进行快照读时,会复用同一个读视图。

    主从同步原理:

    What:

  • 当主库执行DDL语句或者DML语句进行数据的变更,主库会将这些语句记录到二进制文件Bin_log中。

  • 从库读取Bin_log文件后,写入到从库中的中继文件Relay_log中。

  • 最后,从库重新执行这些指令进行数据的同步。

  • Mysql的分库分表:

    分库分表出现问题:

    解决问题:使用分库分表的中间件:MyCat、sharding_sphere。

  • 那你之前使用过水平分库吗?
  • 候选人:使用过。当时业务发展迅速,某个表数据量超过1000万,单库优化后性能仍然很慢,因此采用了水平分库。我们首先部署了3台服务器和3个数据库,使用mycat进行数据分片。旧数据也按照ID取模规则迁移到了各个数据库中,这样各个数据库可以分摊存储和读取压力,解决了性能问题。

    赞(0)
    未经允许不得转载:171主机测评 » Mysql八股总结:(总结必看)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址