mysql-索引、存储引擎、事务、锁机制和优化

1. MySQL的索引

1.1 概述

索引是通过某种算法,构建出一个数据模型,用于快速找出在某个列中有以特定值的行,不使用索引,MySQL必须从一条记录开始读完整个表,直到找出相关的行,表越大查询数据所花的时间越多,如果表中查询的列有一个索引,MySQL能够快速达到一个位置去搜索数据文件,而不必查看所有数据,那么将会节省很大一部分时间。

1.2 分类

索引是存储引擎用来快速查找记录的一种数据结构:

  1. 按照实现的方式类分,主要有Hash索引和B+Tree索引。

  2. 按照功能划分,单列索引(普通索引、唯一索引、主键索引),组合索引,全文索引,空间索引

1.3 特点

  • 优点

    • 大大加快数据查询的速度;
    • 使用分组和排序进行数据查询时,可以显著减少查询时分组和排序的时间
    • 创建唯一索引,能够保证数据库表中每一行数据的唯一性
    • 在实现数据的参考完整性方面,可以加速表和表之间的连接。
  • 缺点

    • 创建索引和维护索引需要消耗时间,并且随着数据量的增加,时间也会增加
    • 索引需要占据磁盘空间
    • 对数据表中的数据进行增加、修改、删除时,索引也要动态的维护,降低了维护的速度
  • 创建索引的原则

    • 更新频繁的列不应该设置为索引
    • 数据量小的表不要使用索引
    • 重复数据多的字段不应设置为索引(一般来说,重复的数量超过15%就不该建索引)
    • 首先应考虑对WHERE和ORDER BY涉及的列上建立索引

2. 存储引擎------MySQL的核心

存储引擎就是存储数据、建立索引、更新、查询数据等技术的实现方式。存储引擎是基于表的,而不是基于库的,所以存储引擎也可以被称为表类型。

2.1 分类

  • MyISAM:MySQL5.5之前的默认数据库引擎,最为常用。拥有较高的插入,查询速度,但不支持事务。

  • InnoDB:事务型速记的首选引擎,支持ACID事务,支持行级锁定,MySQL5.5成为默认数据库引擎。

  • Memory:所有数据置于内存的存储引擎,拥有极高的插入,更新和查询效率。但是会占用和数据量成正比的内存空间,并且其内容会在MySQL重新启动会丢失。

  • Archive:非常适合存储大量的独立的、作为历史纪录的数据。因为它们不经常被读取。Archive拥有高效的插入速度,但其对查询的支持相对较差。

  • Federated:将不同的MySQL服务器联合起来,逻辑上组成一个完整的数据库。非常适合分布式应用。

  • CSV:逻辑上由都好分割数据的存储引擎。他会在数据库子目录里为每个数据表创建一个.csv文件。这是一种普通文本文件,每个数据行占用一个文本行。CSV存储引擎不支持索引。

  • BlockHole:黑洞引擎,写入的任何数据都会消失,一般用于记录binlog做赋值的中继。

  • ERFORMANCE_SCHEMA:该存储引擎主要用于收集数据库服务器性能参数。

功能 MyISAM MEMORY InnoDB
存储限制 256TB RAM 64TB
支持事务 No No Yes
支持全文索引 Yes No No
支持B树索引 Yes Yes Yes
支持哈希索引 No Yes No
支持集群索引 No No Yes
支持数据索引 No Yes Yes
支持数据压缩 Yes No No
空间使用率 N/A
支持外键 No No Yes
支持锁机制 表锁 表锁 表锁/行锁

3. MyISAM和InnoDB

MyISAM和InnoDB都是MySQL数据库中常见的存储引擎,各自特点和使用场景如下:

  1. MyISAM
  • 特点

    • 基于表格的存储引擎,对每个表对应三个文件,.frm文件存储表结构定义,.MYD文件存储数据,MYI文件存储索引。
    • 不支持事务,也不支持外键。
    • 支持全文索引(Full-Text-Indexing),适合于搜索场景
    • 读操作比写操作效率高,适用于读多写少的场景。
  • 使用场景

    • 非事务性的应用,例如博客、新闻网站等;
    • 对数据的读操作频繁,写操作相对较少的场景;
    • 对全文搜索需求较多的场景;
  1. InnoDB
    MySQL默认的事务型引擎。
  • 特点
    • 提供了对事务的支持(ACID兼容),具有提交、回滚和崩溃恢复的能力;
    • 支持行级锁定,提高了并发性能;
    • 支持外键约束。
    • 采用聚合索引(Clustered Indexing),数据按照主键的顺序存储,可以提高查询性能。
  • 使用场景
    • 需要事务支持的应用,例如电子商务网站、金融系统等。
    • 需要较高并发性能的应用,例如论坛、社交网络等。
    • 需要支持外键约束的应用。

在存储引擎时,需要根据应用的具体需求来决定使用哪种引擎。如果是简单的应用,只需要基本的增删改查功能,可以选择MyISAM。如果需要支持事务、并发性能较高或者有复杂的数据关系,建议选择InnoDB。

MyISAM InnoDB
事务 不支持 支持
表锁 表锁、行锁
文件存储 3个 1个
外键 不支持 支持

4. 事务

4.1 特性(ACID)

  • 原子性(Atomicity):事务开始后所有操作,要么全部完成,要么全部不做,不可能停止在中间环节。事务执行过程中出错,会回滚到事务开始前的状态。即事务是一个整体。

  • 一致性(Consistecy):事务开始前和结束后,数据库的完整性约束没有被破坏。

  • 隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。

  • 持久性(Durability):事务完成后,事务对数据的所有更新将被保存到数据库,不能回滚。

4.2 事务靠什么保证

  • 原子性:由undo log日志保证,它记录了需要回滚的日志信息,回滚时撤销一致性的SQL;

  • 一致性:由其他三大特性共同保证,是事务的目的;

  • 隔离性:由MVCC保证。

  • 持久性:由redo log日志和内存保证,MYSQL修改数据时内存和redo log会记录操作,宕机时可恢复。

4.3 undo log和redo log的区别

  • 缓冲池(buffer pool):主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增删改操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从主磁盘加载并缓存),以一定频率刷新到磁盘,从而减少磁盘IO,加快处理速度。

  • 数据页(page):是InnoDB存储引擎磁盘管理的最小单元,每个页的大小默认为16KB,液中存储的是行数据。

  • redo log

    • 重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。
    • 该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中,用于在刷新脏页到磁盘,发生错误时,进行数据恢复使用。
  • undo log

    • 回滚日志,用于记录数据被修改前的信息,作用包含两个:提供回滚和MVCC(多版本并发控制)。undo log和redo log记录物理日志不一样,它是逻辑日志。
    • 可以认为当delete一条记录时,undo log中会记录一条对应的Insert记录,反之亦然
    • 当update一条记录时,他记录一条对应相反的update的记录。当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。
    • undo log可以实现事物的一致性和原子性
      事物的隔离性是如何保证的
  • :排他锁(如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁)

  • MVCC:多版本并发控制

4.4 事务的隔离级别

高并发情况下,并发事务会产生脏读、不可读重复、幻读等问题,这时需要用隔离级别来控制。

  • 读未提交:允许一个事务读取另一个事务已提交的数据,可能出现不可重复度、幻读。

  • 读提交:只允许事务读取另一个事务没有提交的数据可能会出现不可重复度,幻读。

  • 可重复读:确保同一字段多次读取结果一致,可能出现幻读。

  • 可串行化:所有事务逐次执行,没有并发问题。

InnoDB默认隔离级别为可重复读级别,分为快照读和当前读,并且通过间隙锁解决了幻读问题。

事务隔离级别 脏读 不可重复读 幻读
读未提交(read-uncommitted)
读提交(read-committed)
可重复读(repeatable-read)
串行化(serializable)

4.5 事务的并发问题

  • 脏读:事务A读取了事务B更新的数据,然后事务B回滚操作,那么事务A读取到的数据是脏数据。

  • 不可重复读:事务A多次读取同一数据,事务B在事务A多次读取的过程中,对数据做了更新并提交,导致事务A多次读取同一数据时,结果不一致。

  • 幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这时插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,就成为幻读。

  • 如何解决脏读、幻读、不可重复读

    • 脏读:隔离级别设置为读提交、可重复读、串行化可解决脏读
    • 不可重复读:隔离级别设置为可重复读、串行化可以解决不可重复读
    • 幻读:隔离级别为串行化可以解决幻读、通过MVCV + 区间锁可以解决幻读

4.6 快照读和当前读

  • 快照读:读取的是当前数据的可见版本,可能是会过期数据,不加锁的SELECT就是快照读。

  • 当前读:读取的是数据的最新版本,并且当前读返回的记录都会上锁,保证其他事务不会并发修改这条记录。如UPDATE,DELETE,INSERT,SELECT FOR UNDATE(排他锁),SELECT LOCKIN SHARE MODE(共享锁)都是当前读。

4.7 MVCC

MVCC是多版本并发控制,为每次事务生成一个新版本数据,每个事务都有自己的版本,从而不加锁就解决读写冲突,这种读为快照读。只在读已提交和可重复读中生效。

实现的原理由以下四项保证

  • undo log日志:记录数据历史版本

  • readView:事务进行快照度时动态生成产生的视图,记录了当前系统中活跃的事务id,控制哪个历史版本对当前事务可见。

  • 隐藏字段DB_TRC_ID:最近修改记录的事务ID

  • 隐藏字段DB_Roll_PTR:回滚指针,配合undolog指向数据的上一个版本

5. 锁机制

5.1 定义

锁是计算机协调多个进程或线程并发访问某一资源的机制(避免竞争)。在数据库中,除传统的计算资源(如CPU,RAM,I/O等)的争用外,数据也是一种供许多用户共享的资源。锁机制能保证数据并发访问的一致性、有效性,同时也影响数据库并发访问性能。

5.2 分类

  • 根据对数据操作的粒度

    • 表锁:操作时,会锁定整个表
    • 行锁:操作时,会锁定当前操作行
    锁类型 特点
    表级锁 偏向MyISAM存储引擎,开销大,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
    行级锁 偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁。锁粒度最小,发生锁冲突的概率最低,并发度也最高。
    • 表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用。
    • 行级锁更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。
  • 根据对数据操作的类型

    • 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响
    • 写锁(排它锁):当前操作没有完成之前前,会阻断其他写锁和读锁。
    存储引擎 表级锁 行级锁
    MyISAM 支持 不支持
    InnoDB 支持 支持
    MEMORY 支持 不支持
    BOB 支持 不支持

6. 日志

分类:错误日志、二进制日志、查询日志、慢查询日志

6.1 错误日志

  • 错误日志是MySQl中最重要的日志之一,记录了当MySQL启动个停止时,以及服务器在运行过程中发生任何严重错误时的相关信息,当数据库出现任何故障导致无法正常使用时,可以首先查看错误日志。

  • 该日志默认是开启的,默认存放目录是mysql的数据目录,默认的日志文件名为hosyname.err(hostname为主机名)。

  • 查看日志位置指令

    sql 复制代码
        SHOW variables LIKE 'log_error%'

6.2 二进制日志

二进制日志(BINLOG)记录了所有的DDL语句和DML语句,但不包括DQL语句。此日志对灾难时的数据恢复起着极其重要的作用,MySQL的主从复制就是通过改BINLOG实现的。

二进制日志在MySQL8默认开启,低版本需要通过配置文件开启,并配置MySQL日志的格式。

Windows系统:my.ini Linux系统: my.cnf

6.3 查询日志

查询日志中记录了客户端的所有操作语句,而二进制日志不包含查询数据的SQL语句。

默认情况下,查询日志是未开启的。

6.4 慢查询日志

慢查询日志记录了所有执行时间超过参数long_query_time设置值并且扫描记录数不小于min_examined_row_limit的所有SQL语句的日志。long_query_time默认为10秒,最小为0,精度可以到微秒。

7. 索引

7.1 定义

索引(index)是帮助MySQL高效的获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构(B+树),这些数据结构以某种方式指向数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。这样可以提高数据检索的效率,降低数据库的IO成本(不需要全表扫描)。通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗。

B树与B+树对比

  • 阶数更多,路径更短
  • 磁盘读写代价B+树更低,非叶子节点只存储指针,叶子阶段存储数据
  • 查询效率B+树更加稳定
  • B+树便于扫库和区间查询,非叶子节点是一个双向链表

7.2 聚簇索引和非聚簇索引

7.2.1 聚簇索引

  • 含义:将数据存储与索引放到了一起,索引结构的叶子节点保存了行数据
  • 特点:必须有,而且只有一个

7.2.2 二级索引(非聚簇索引)

  • 含义:将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键
  • 特点:可以存在多个

7.2.3 聚簇索引选取规则

  • 如果存在主键,主键索引就是聚簇索引
  • 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚簇索引。
  • 如果表没有主键,或没有适合的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚簇索引。

7.2.4 回表查询

(首先介绍聚簇索引和非聚簇索引,然后介绍回表查询)

通过二级索引找到对应的主键值,到聚簇索引中查找整行数据,这个过程就是回表。

7.3 覆盖索引

覆盖索引是指查询使用了索引,并且需要返回的列在该索引中已经全部能够找到。

  • 使用id查询,直接走聚簇索引,一次索引扫描,直接返回数据,性能高;

  • 如果返回的列中没有创建索引,有可能会触发回表查询,尽量避免使用select *;

(可以使用索引覆盖解决MySQL超大分页)

7.4 MySQL超大分页处理

  • 在数据量比较大时,如果进行limit分页查询,在查询时,越往后分页查询的效率越低。

  • 优化思路:一般分页查询时,通过创建覆盖索引能够比较好的提高性能,可以通过覆盖索引加子查询形式进行优化。

7.5 创建索引的原则

  • 针对数据量较大,且查询比较频繁的表建立索引(单表超过10万数据,增加用户体验)[重要]

  • 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引[重要]

  • 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高

  • 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引

  • 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,避免回表,提高查询效率【重要】

  • 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率【重要】

  • 如果索引列不能存储NULL值,在创建表时使用NOT NULL约束。当优化器直到每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询

7.6 什么情况下索引会失效?

  • 违反最左前缀法则
  • 范围查询右边的列,不能使用索引
  • 不要再索引列上进行运算操作,索引将会失效
  • 字符串不加单引号,造成索引失效(类型失效)
  • 以%开头的Like模糊查询,索引失效

8. 优化

SQL优化一般可以从设计、查询、索引和存储四方面进行。

8.1 如何定位慢查询?

  • 采用运维工具(Skywalking),可以检测出是哪个接口,最终找到SQL问题

  • 在数据库中开启慢日志查询,设置阈值(可以设置为2秒),一旦SQL执行超过阈值就hi记录到日志中。

8.2 一个SQL语句执行很慢,如何分析

可以使用MySQL自带的分析工具EXPLAIN或者DESC

  • 通过key和key_len检查是否命中了索引(索引本身存在是否失效的情况)

  • 通过type字段查看sql是否有进一步的优化空间,是否存在全索引扫描或全盘扫描

  • 通过extra建议判断,是否出现了回表的情况,如果出现了,可以尝试添加索引或修改返回字段来修复。

    • possible_key:当前sql可能会使用到的索引
    • key:当前sql实际命中的索引
    • key_len:索引占用的大小
    • Extra:额外的优化建议
    • type:这条SQL的连接类型,性能由好到差依次为:NULL,system,const,eq_ref,ref,range,index,all
      • system:查询系统中的表
      • const:根据主键查询
      • eq_ref:主键索引查询或唯一索引查询
      • ref:索引查询
      • range:范围查询
      • index:索引扫描
      • all:全盘扫描
    extra 含义
    Using where;Using index 查找使用了索引,需要的数据都在索引列中能找到,不需要回表查询数据
    Using index condition 查找使用了索引,但是需要回表查询数据

8.3 SQL优化的经验

  • 表的设计优化

    参考阿里开发手册《嵩山版》

    • 比如设置合适的数值(tinyint int bigint),要根据实际情况选择
    • 比如设置合适的字符串类型(char和varchar)char定长效率高,varchar可变长度,效率稍低
  • 索引优化[参考优化创建原则和索引失效]

  • SQL语句优化

    • SELECT语句无比指定字段名称(避免直接使用SELECT *)
    • SQL语句要避免造成索引失效的写法
    • 尽量用UNION ALL代替UNION UNION会多一次过滤,效率低
    • 避免在where子句中对字段进行表达式操作
    • Join优化:能用INNERJOIN就不用LEFT JOIN RIGHT JOIN,如必须使用一定要以小表为驱动,内连接会对两个表进行优化,优先把小表放到外面,把大表放到里面。LEFT JOIN或RIGHT JOIN,不会重新调整顺序。
  • 主从复制、读写分离

    如果数据库的使用场景读的操作比较多的时候,为了避免写的操作所造成的性能影响,可以采用读写分离的架构,读写分离解决的是数据库的写入,影响了查询的效率。

  • 分库分表

8.4 主从同步原理

MySQL主从复制的核心是二进制bin log(记录DDL和DML)

  • 主从在事务提交时,会把数据变更记录在二进制日志文件bin log中
  • 从库读取主库的二进制日志文件bin log,写入到从库的中继日志Relay log
  • 从库重做中继日志中的事件,将改变反应它自己的数据

8.5 分库分表

  • 时机

    • 前提:项目业务数据逐渐增多,或业务发展比较迅速(单表的数据量达1000W或20G以后)
    • 优化已解决不了性能问题(主从读写分离,查询索引...)
    • IO瓶颈(磁盘IO,网络IO)、CPU瓶颈(聚合查询、连接数太多)
  • 拆分策略

    • 垂直拆分

      • 垂直分库:以表为依据,根据业务将不同表拆分到不同库中

        特点

        • 将业务对数据分级管理、维护、监控、扩展
        • 在高并发下,提高磁盘IO和数据量连接数
      • 垂直分表:以字段为依据,根据字段属性将不同字段拆分到不同表中

        特点

        • 冷数据分离
        • 减少IO过度争抢,两表互不影响
    • 水平拆分

      • 水平分库:将一个库的数据拆分到多个库中

        特点

        • 解决了单库大数量,高并发的性能瓶颈问题
        • 提高了系统的稳定性和可用性
      • 水平分表:将一个表的数据拆分到多个表中(可以在同一个库内),按照记录分表

        特点

        • 优化单一表数量过大而产生的性能问题
        • 避免IO争抢并减少锁表的几率
相关推荐
Python私教1 小时前
model中能定义字段声明不存储到数据库吗
数据库·oracle
mqiqe4 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
工业甲酰苯胺4 小时前
MySQL 主从复制之多线程复制
android·mysql·adb
BestandW1shEs4 小时前
谈谈Mysql的常见基础问题
数据库·mysql
重生之Java开发工程师4 小时前
MySQL中的CAST类型转换函数
数据库·sql·mysql
教练、我想打篮球4 小时前
66 mysql 的 表自增长锁
数据库·mysql
Ljw...4 小时前
表的操作(MySQL)
数据库·mysql·表的操作
哥谭居民00014 小时前
MySQL的权限管理机制--授权表
数据库
wqq_9922502774 小时前
ssm旅游推荐系统的设计与开发
数据库·旅游