mysql16

2023-05-16

常见面试题

MySQL 中有哪些存储引擎?

  • InnoDB 存储引擎
    • InnoDB 是 MySQL 的默认事务型引擎,也是最重要、使用最广泛的存储引擎。 它被设计用来处理大量的短期(short-lived)事务,应该优先考虑 InnoDB 引擎。
  • MylSAM 存储引擎
    • 在 MySQL 5.1 及之前的版本,MyISAM 是默认的存储引擎。MyISAM 提供了 大量的特性,包括全文索引、压缩、空间函数(GIS)等,但 MyISAM 不支持事 务和行级锁,而且崩溃后无法安全恢复。MyISAM 对整张表加锁,很容易因为表 锁的问题导致典型的的性能问题。
  • Mrg_MylSAM
    • Merge 存储引擎,是一组 MyIsam 的组合,也就是说,他将 MyIsam 引擎的 多个表聚合起来,但是他的内部没有数据,真正的数据依然是 MyIsam 引擎的表 中,但是可以直接进行查询、删除更新等操作。
  • Archive 引擎
    • Archive 存储引擎只支持 INSERT 和 SELECT 操作,会缓存所有的写并利用 zlib 对插入的行进行压缩,所以比 MyISAM 表的磁盘 I/O 更少。但是每次 SELECT 查 询都需要执行全表扫描。所以 Archive 表适合日志和数据采集类应用,Archive 引 擎是一个针对高速插入和压缩做了优化的简单引擎。
  • Blackhole 引擎
    • Blackhole 引擎没有实现任何的存储机制,它会丢弃所有插入的数据,不做 任何保存。可以在一些特殊的复制架构和日志审核时发挥作用。但这种引擎在应 用方式上有很多问题,因此并不推荐。
  • CSV 引擎
    • CSV 引擎可以将普通的 CSV 文件(逗号分割值的文件)作为 MySQL 的表来处 理,但这种表不支持索引。因此 CSV 引擎可以作为一种数据交换的机制,非常有 用。
  • Federated 引擎
    • Federated 引擎是访问其他 MySQL 服务器的一个代理,它会创建一个到远程 MySQL 服务器的客户端连接,并将查询传输到远程服务器执行,然后提取或者发 送需要的数据。默认是禁用的。
  • Memory 引擎
    • Memory 表至少比 MyISAM 表要快一个数量级,数据文件是存储在内存中。 Memory 表的结构在重启以后还会保留,但数据会丢失。 Memroy 表在很多场景可以发挥好的作用: 用于查找(lookup)或者映射(mapping)表,例如将邮编和州名映射的表。 用于缓存周期性聚合数据(periodically aggregated data)的结果。 用于保存数据分析中产生的中间数据。Memory 表支持 Hash 索引,因此查找操作非常快。Memroy 表是表级锁, 因此并发写入的性能较低,每行的长度是固定的,可能导致部分内存的浪费。
  • NDB 集群引擎
    • 使用 MySQL 服务器、NDB 集群存储引擎,以及分布式的、share-nothing 的、 容灾的、高可用的 NDB 数据库的组合,被称为 MySQL 集群((MySQL Cluster)。

MyISAM InnoDB 的区别是什么?

  • MyISAM 引擎是 5.1 版本之前的默认引擎,支持全文检索、压缩、空间函数 等,但是不支持事务和行级锁,所以一般用于有大量查询少量插入的场景来使用, 而且 MyISAM 不支持外键,并且索引和数据是分开存储的
  • InnoDB 是基于聚簇索引建立的,和 MyISAM 相反它支持事务、外键,并且 通过 MVCC 来支持高并发索引和数据存储在一起

请概述下数据库的范式设计

  • 目前关系数据库有六种范式,常见范式:第一范式:1NF 是对属性的原子性 约束,要求属性具有原子性,不可再分解;第二范式:2NF 是对记录的惟一性约 束,要求记录有惟一标识,即实体的惟一性;第三范式:3NF 是对字段冗余性的 约束,即任何字段不能由其他字段派生出来,它要求字段没有冗余
  • 范式化设计优缺点:
  • 优点:可以尽量得减少数据冗余,使得更新快,体积小;缺点:对于查询需要 多个表进行关联,减少写得效率增加读得效率,更难进行索引优化
  • 反范式化:
  • 优点:可以减少表得关联,可以更好得进行索引优化;缺点:数据冗余以及数 据异常,数据得修改需要更多的成本,常见的反范式设计有缓存、冗余等等。

数据库表设计时,字段你会如何选择?

  • 更小的通常更好,应该尽量使用可以正确存储数据的最小数据类型。更小的 数据类型通常更快,因为它们占用更少的磁盘、内存和 CPU 缓存,并且处理时 需要的 CPU 周期也更少。但是要确保没有低估需要存储的值的范围
  • 简单就好,简单数据类型的操作通常需要更少的 CPU 周期。例如,整型比字符操作代价更低,比如应该使用 MySQL 内建的类型而不是字符串来存储日期 和时间。
  • 尽量避免 NULL,如果查询中包含可为 NULL 的列,对 MySQL 来说更难优化, 因为可为 NULL 的列使得索引、索引统计和值比较都更复杂。可为 NULL 的列会 使用更多的存储空间,在 MySQL 里也需要特殊处理。当可为 NULL 的列被索引时, 每个索引记录需要一个额外的字节。

mysql 里记录货币用什么字段类型好?

  • MySQL 既支持精确类型的存储 DECIMAL 类型,也支持不精确类型存储 FLOAT 和 DOUBLE 类型。对于货币记录,应该选择 DECIMAL 类型,但是 DECIMAL 类型 是以字符串形式存放的,所以性能会有影响。
  • 作为替代方案,可以在数据量比较大的而且要求精度时,虑使用 BIGINT 代 替 DECIMAL,将需要存储的货币单位根据小数的位数乘以相应的倍数即可。

谈谈 MySQL 里的字符串类型

  • MySQL 里的字符串类型有:SET、BLOB、ENUM、VARCHAR、CHAR、TEXT。 VARCHAR 和 CHAR 是两种最主要的字符串类型。VARCHAR 类型用于存储可变长 字符串,大部分的业务情况下比定长类型更节省空间,CHAR 类型是定长的,CHAR 适合存储很短的字符串,或者所有值定长或都接近同一个长度。
  • 使用 BLOB 和 TEXT 则要慎重,一般把 BLOB 或 TEXT 列分离到单独的表中, 还可以对 BLOB 或 TEXT 列使用合成的(Synthetic)索引,就是根据大文本字段的内 容建立一个散列值并单独存储在数据列中,可以通过检索散列值找到数据行。如 果表中的字段的取值是固定几个字符串,可以使用枚举列代替常用的字符串类型。

**VARCHAR(M)**最多能存储多少数据?

  • 对于 VARCHAR(M)类型的列最多可以定义 65535 个字节。其中的 M 代表该类 型最多存储的字符数量,但在实际存储时并不能放这么多。
  • MySQL 对一条记录占用的最大存储空间是有限制的,除了 BLOB 或者 TEXT 类型的列之外,其他所有的列(不包括隐藏列和记录头信息)占用的字节长度加 起来不能超过 65535 个字节。所以 MySQL 服务器建议我们把存储类型改为 TEXT 或者 BLOB 的类型。这个 65535 个字节除了列本身的数据之外,还包括一些其他 的数据,从行记录格式我们可以得知,为了存储一个 VARCHAR(M)类型的列,其 实需要占用 3 部分存储空间:真实数据、真实数据占用字节的长度、NULL 值标 识,如果该列有 NOT NULL 属性则可以没有这部分存储空间。
  • 我们假设表中只有一个 VARCHAR 字段的情况:
  • 如果该 VARCHAR 类型的列没有 NOT NULL 属性,那最多只能存储 65532 个 字节的数据,因为真实数据的长度可能占用 2 个字节,NULL 值标识需要占用 1 个字节
  • 如果 VARCHAR 类型的列有 NOT NULL 属性,那最多只能存储 65533 个字节 的数据,因为真实数据的长度可能占用 2 个字节,不需要 NULL 值标识
  • 如果 VARCHAR(M)类型的列使用的不是 ascii 字符集,那 M 的最大取值取决 于该字符集表示一个字符最多需要的字节数。在列的值允许为 NULL 的情况下,gbk 字符集表示一个字符最多需要 2 个字节,那在该字符集下,M 的最大取值就 是 32766(也就是:65532/2),也就是说最多能存储 32766 个字符;utf8 字符 集表示一个字符最多需要 3 个字节,那在该字符集下,M 的最大取值就是 21844, 就是说最多能存储 21844(也就是:65532/3)个字符。
  • 不管如何,请牢记:MySQL 一个行中的所有列(不包括隐藏列和记录头信 息)占用的字节长度加起来不能超过 65535 个字节

什么是虚拟生成列?

  • 虚拟生成列又叫 Generated Column,是 MySQL 5.7 引入的新特性,就是数据 库中这一列由其他列计算而得。在 MySQL 5.7 中,支持两种 Generated Column,即 Virtual Generated Column(虚拟生成的列)和 Stored Generated Column(存储 生成的列),二者含义如下:

  • 1、Virtual Generated Column(虚拟生成的列):不存储该列值,即 MySQL 只是将这一列的元信息保存在数据字典中,并不会将这一列数据持久化到磁盘上, 而是当读取该行时,触发触发器对该列进行计算显示

  • 2、Stored Generated Column(存储生成的列): 存储该列值,即该列值在 插入或更新行时进行计算和存储。所以相对于 Virtual Column 列需要更多的磁盘 空间,与 Virtual Column 相比并没有优势。因此,MySQL 5.7 中,不指定 Generated Column 的类型,默认是 Virtual Column

  • 在表中允许 Virtual Column 和 Stored Column 的混 合使用

  • 提高效率:由于 mysql 在普通索引上加函数会造成索引失效,造成查询性能 下降,Generated Column(函数索引)刚好可以解决这个问题,可以在 Generated Column 加上索引来提高效率。但是不能建立虚拟列和真实列的联合索引,同时 虚拟列是不允许创建主键索引和全文索引。

  • 创建虚拟生成列的语法:

  • CREATE TABLE triangle (

    a double DEFAULT NULL,

    b double DEFAULT NULL,

    sidec double GENERATED ALWAYS AS (SQRT(a * a + b * b))

    ) ;

  • alter table triangle add column sided tinyint(1) generated always as (a*b) virtual;

请说下事务的基本特性

  • 事务应该具有 4 个属性:原子性、一致性、隔离性、持久性。这四个属性通 常称为 ACID 特性。
  • 原子性指的是一个事务中的操作要么全部成功,要么全部失败。
  • 一致性指的是数据库总是从一个一致性的状态转换到另外一个一致性的状 态。比如 A 转账给 B100 块钱,假设中间 sql 执行过程中系统崩溃 A 也不会损失 100 块,因为事务没有提交,修改也就不会保存到数据库。
  • 隔离性指的是一个事务的修改在最终提交前,对其他事务是不可见的。
  • 持久性指的是一旦事务提交,所做的修改就会永久保存到数据库中。

事务并发可能引发什么问题?

  • 当一个事务读取到了另外一个事务修改但未提交的数据,被称为脏读
  • 当事务内相同的记录被检索两次,且两次得到的结果不同时,此现象称为不可重复读
  • 在事务执行过程中,事务 2 将新记录添加到正在读取的事务 1 中,导致事务 1 按照某个相同条件多次读取记录时,后读取时读到了之前没有读到的记录,发生幻读
  • 事务 2 中是删除了符合的记录而不是插入新记录,那事务 1 中之后再根据条 件读取的记录变少了,在 MySQL 中这种现象不属于幻读,相当于对每一条记录 都发生了不可重复读的现象。

请描述下 MySQL InnoDB 支持的四种事务隔离和 区别

  • read uncommitted:未提交读,可能发生脏读、不可重复读和幻读问题。
  • read committed:提交读,可能发生不可重复读和幻读问题,但是不会发生脏读问题。
  • repeatable read:可重复读,在 SQL 标准中可能发生幻读问题,但是不会发 生脏读和不可重复读的问题,但是 MySQL 通过 MVCC 基本解决了幻读问题。这也是MySQL 的缺省隔离级别
  • serializable:串行化读,脏读、不可重复读和幻读问题都不会发生。

MySQL 有哪些索引类型

  • 从数据结构角度可分为 B+树索引、哈希索引、以及 FULLTEXT 索引(现在 MyISAM 和 InnoDB 引擎都支持了)和 R-Tree 索引(用于对 GIS 数据类型创建 SPATIAL 索引)
  • 从物理存储角度可分为聚集索引(clustered index)、非聚集索引 (non-clustered index)
  • 从逻辑角度可分为主键索引、普通索引,或者单列索引、多列索引、唯一索 引、非唯一索引等等。

简单描述 MySQL 各个索引的区别

  • 索引是一种特殊的文件(InnoDB 数据表上的索引是表空间的一个组成部分), 它们包含着对数据表里所有记录的引用指针。
  • 普通索引(由关键字 KEY 或 INDEX 定义的索引)的唯一任务是加快对数据的访问速度
  • 普通索引允许被索引的数据列包含重复的值。如果能确定某个数据列将只包 含彼此各不相同的值,在为这个数据列创建索引的时候就应该用关键字 UNIQUE 把它定义为一个唯一索引。也就是说,唯一索引可以保证数据记录的唯一性
  • 主键,是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一条记录,使用关键字 PRIMARY KEY 来创建
  • 索引可以覆盖多个数据列,如像 INDEX(columnA, columnB)索引,这就是联合索引

MySQL 的索引对数据库的性能有什么影响

  • 索引(Index)是帮助MySQL 高效获取数据的数据结构,所以索引可以极大的提高数据的查询速度。
  • 但是每建立一个索引都要为它建立一棵 B+树,一棵很大的 B+树由许多数据页组成会占据很多的存储空间
  • 而且每次对表中的数据进行增、删、改操作时,都需要去修改各个 B+树索 引同时这些操作可能会对节点和记录的排序造成破坏,所以存储引擎需要额外 的时间进行一些记录移位,页面分裂、页面回收的操作来维护好节点和记录的排序。如果我们建了许多索引,每个索引对应的 B+树都要进行相关的维护操作, 这必然会对性能造成影响。

为什么 MySQL 的索引要使用 **B+**树而不是 B 树?

  • 答案见下一小节

InnoDB 一棵 **B+**树可以存放多少行数据?

  • 当然在实际的数据库中,一个节点可以存储的数据可以很多,为什么?
  • 计算机在存储数据的时候,有最小存储单元,这就好比我们今天进行现金 的流通最小单位是一毛。在计算机中磁盘存储数据最小单元是扇区,一个扇区的 大小是 512 字节,而文件系统(例如 XFS/EXT4)他的最小单元是块,一个块的 大小是 4k,而对于我们的 InnoDB 存储引擎也有自己的最小储存单元——页 (Page),一个页的大小是 16K。Innodb 的所有数据文件(后缀为 ibd 的文件), 他的大小始终都是 16384(16k)的整数倍
  • 数据表中的数据都是存储在页中的,所以一个页中能存储多少行数据呢?假设一行数据的大小是 1k,那么一个页可以存放 16 行这样的数据。
  • 对于 B+树而言,只有叶子节点存放数据,非叶子节点存放的是只保存索引 信息和下一层节点的指针信息。一个非叶子节点能存放多少指针?
  • 其实这也很好算,我们假设主键 ID 为 常用的 bigint 类型,长度为 8 字 节,而指针大小在 InnoDB 源码中设置为 6 字节,这样一共 14 字节,我们一 个页中能存放多少这样的单元,其实就代表有多少指针,即 16384/14=1170 个
  • 那么可以算出一棵高度为 2 的 B+树,存在一个根节点和若干个叶子节点能 存放 1170*16=18720 条这样的数据记录。
  • 根据同样的原理我们可以算出一个高度为 3 的 B+ 树可以存放: 1170*1170*16=21902400 条这样的记录。
  • 所以在 InnoDB 中 B+ 树高度一般为 1-3 层,就能满足千万级的数据存储
  • 那么为什么 MySQL 的索引要使用 **B+**树而不是 B 树?
  • 而 B 树和 B+树的最大区别就是,B 树不管叶子节点还是非叶子节点,都 会保存数据,这样导致在非叶子节点中能保存的指针数量变少(有些资料也称为 扇出),指针少的情况下要保存大量数据,只能增加树的高度,导致 IO 操作变 多,查询性能变低

HashMap 适合做数据库索引吗?

  • 1、hash 表只能匹配是否相等,不能实现范围查找
  • 2、当需要按照索引进行 order by 时,hash 值没办法支持排序
  • 3、组合索引可以支持部分索引查询,如(a,b,c)的组合索引,查询中只用到了 阿和 b 也可以查询的,如果使用 hash 表,组合索引会将几个字段合并 hash,没办法支持部分索引
  • 4、当数据量很大时,hash 冲突的概率也会非常大

InnoDB 中只有 **B+**树索引吗?

  • InnoDB 存储引擎不仅仅有 B+树索引,它还支持全文索引、哈希索引
  • InnoDB 存储引擎内部自己去监控索引表,如果监控到某个索引经常用,那 么就认为是热数据,然后内部自己创建一个 hash 索引,称之为自适应哈希索引 ( Adaptive Hash Index,AHI)。使用的哈希函数采用除法散列方式,其冲突机制 采用链表方式。我们对这个自适应哈希索引能够干预的地方很少,只能设定是否 启用和分区个数。
  • 从 MySQL5.6.x 开始,InnoDB 开始支持全文检索,内部的实现机制就是倒排 索引。但是 MySQL 整体架构上对全文检索支持并不好而且限制很多,比如每张表 只能有一个全文检索的索引,不支持没有单词界定符( delimiter)的语言,所 以如果有大批量或者专门的全文检索需求,还是应该选择专门的全文检索引擎。

什么是密集索引和稀疏索引?

  • 密集索引的定义:叶子节点保存的不只是键值,还保存了位于同一行记录里 的其他列的信息,由于密集索引决定了表的物理排列顺序,一个表只有一个物理 排列顺序,所以一个表只能创建一个密集索引。
  • 稀疏索引:叶子节点仅保存了键位信息以及该行数据的地址,有的稀疏索引 只保存了键位信息机器主键。
  • mysam 存储引擎,不管是主键索引,唯一键索引还是普通索引都是稀疏索 引,innodb 存储引擎:有且只有一个密集索引。
  • 所以,密集索引就是 innodb 存储引擎里的聚簇索引,稀疏索引就是 innodb 存储引擎里的普通二级索引

为什么要用自增列作为主键?

  • 1、如果我们定义了主键(PRIMARY KEY),那么 InnoDB 会选择主键作为聚集索 引。
  • 如果没有显式定义主键,则 InnoDB 会选择第一个不包含有 NULL 值的唯一 索引作为主键索引
  • 如果也没有这样的唯一索引,则 InnoDB 会选择内置 6 字节长的 ROWID 作为 隐含的聚集索引(ROWID随着行记录的写入而主键递增,这个 ROWID不像ORACLE 的 ROWID 那样可引用,是隐含的)。
  • 2、数据记录本身被存于主索引(一颗 B+Tree)的叶子节点上,这就要求同 一个叶子节点内(大小为一个内存页或磁盘页)的各条数据记录按主键顺序存放
  • 因此每当有一条新的记录插入时,MySQL 会根据其主键将其插入适当的节点 和位置,如果页面达到装载因子(InnoDB 默认为 15/16),则开辟一个新的页(节 点)
  • 3、如果表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当 前索引节点的后续位置,当一页写满,就会自动开辟一个新的页
  • 4、如果使用非自增主键(如果身份证号或学号等),由于每次插入主键的 值近似于随机,因此每次新纪录都要被插到现有索引页得中间某个位置。此时 MySQL 不得不为了将新记录插到合适位置而移动数据,甚至目标页面可能已经被 回写到磁盘上而从缓存中清掉,此时又要从磁盘上读回来,这增加了很多开销同 时频繁的移动、分页操作造成了大量的碎片,得到了不够紧凑的索引结构,后续 不得不通过 OPTIMIZE TABLE 来重建表并优化填充页面。

主键和唯一键有什么区别?

  • 主键不能重复,不能为空,唯一键不能重复,可以为空
  • 建立主键的目的是让外键来引用。
  • 一个表最多只有一个主键,但可以有很多唯一键

说说对 SQL 语句优化有哪些方法?(选择几条)

  • (1)Where 子句中:where 表之间的连接必须写在其他 Where 条件之前, 那些可以过滤掉最大数量记录的条件必须写在 Where 子句的末尾.HAVING 最后。
  • (2)用 EXISTS 替代 IN、用 NOT EXISTS 替代 NOT IN
  • (3) 避免在索引列上使用计算
  • (4)避免在索引列上使用 IS NULL 和 IS NOT NULL
  • (5)对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引
  • (6)应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
  • (7)应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放 弃使用索引而进行全表扫描。

如何提高 insert 的性能?

  • a)合并多条 insert 为一条,即: insert into t values(a,b,c), (d,e,f) , 原因分析:主要原因是多条 insert 合并后日志量(MySQL 的 binlog 和 innodb 的事务日志) 减少了,降低日志刷盘的数据量和频率,从而提高效率。通过合 并 SQL 语句,同时也能减少 SQL 语句解析的次数,减少网络传输的 IO

  • b)修改参数 bulk_insert_buffer_size, 调大批量插入的缓存;

  • c)设置 innodb_flush_log_at_trx_commit = 0,相对于 innodb_flush_log_at_trx_commit = 1 可以十分明显的提升导入速度; 备注:innodb_flush_log_at_trx_commit 参数对 InnoDB Log 的写入性能有 非常关键的影响。该参数可以设置为 0,1,2,解释如下:

    • 0:log buffer 中的数据将以每秒一次的频率写入到 log file 中,且同时会进行 文件系统到磁盘的同步操作,但是每个事务的 commit 并不会触发任何 log buffer 到 log file 的刷新或者文件系统到磁盘的刷新操作;
    • 1:在每次事务提交的时候将 log buffer 中的数据都会写入到 log file,同时 也会触发文件系统到磁盘的同步;
    • 2:事务提交会触发 log buffer 到 log file 的刷新,但并不会触发磁盘文件系 统到磁盘的同步。此外,每秒会有一次文件系统到磁盘同步操作。)
  • d)手动使用事务 因为 mysql 默认是 autocommit 的,这样每插入一条数据,都会进行一次 commit;所以,为了减少创建事务的消耗,我们可用手工使用事务,即 START TRANSACTION;insert 。。,insert。。 commit;即执行多个 insert 后再一起提交; 一般 1000 条 insert 提交一次

什么是覆盖索引?什么是回表查询?

  • InnoDb 存储引擎有两大类索引聚集索引和普通(辅助/二级)索引,聚簇索 引的叶子节点存储行记录,因此 InnoDb 必须要有聚簇索引且仅有一个聚簇索引, 而普通索引的叶子节点只存储索引值和主键值,所以,通过聚簇索引一次性能获 取所有列的数据,普通索引一般不行。
  • 当我们 SQL 语句的中列无法在普通索引中获得时,就需要主键值到聚簇索引 中获取相关的数据,这个过程就被称为回表。
  • 而如果我们使用联合索引,使得 SQL 所需的所有列数据在这个索引上就能获 得时,我们称为发生了索引覆盖或者覆盖索引。

什么是三星索引?

  • 对于一个查询而言,一个三星索引,可能是其最好的索引。
  • 如果查询使用三星索引,一次查询通常只需要进行一次磁盘随机读以及一次 窄索引片的扫描,因此其相应时间通常比使用一个普通索引的响应时间少几个数 量级。
  • 一个查询相关的索引行是相邻的或者至少相距足够靠近的则获得一星;
  • 如果索引中的数据顺序和查找中的排列顺序一致则获得二星;
  • 如果索引中的列包含了查询中需要的全部列则获得三星。
  • 三星索引在实际的业务中如果无法同时达到,一般我们认为第三颗星最重要, 第一和第二颗星重要性差不多,根据业务情况调整这两颗星的优先度。

大表关联查询优化

  • 一个 6 亿的表 a,一个 3 亿的表 b,通过 tid 关联,你如何最快的查询出满足 条件的第 50000 到第 50200 中的这 200 条数据记录。
  • 1、如果 A 表 TID 是自增长,并且是连续的,B 表的 ID 为索引
  • select * from a,b where a.tid = b.id and a.tid>500000 limit 200;
  • 2、如果 A 表的 TID 不是连续的,那么就需要使用覆盖索引.TID 要么是主键, 要么是辅助索引,B 表 ID 也需要有索引。
  • select * from b , (select tid from a limit 50000,200) a where b.id = a .tid;

[SELECT *] [SELECT 全部字段]有何优缺点?

  • 1>.前者要解析数据字典,后者不需要
  • 2>.结果输出顺序,前者与建表列顺序相同,后者按指定字段顺序。
  • 3>.表字段改名,前者不需要修改,后者需要改
  • 4>.后者可以建立索引进行优化,前者无法优化
  • 5>.后者的可读性比前者要高

请概述下什么是 MySQL 的分区表

  • 表分区,是指根据一定规则,将数据库中的一张表分解成多个更小的,容易 管理的部分。从逻辑上看,只有一张表,但是底层却是由多个物理分区组成
  • 1、表分区与分表的区别
  • 分表:指的是通过一定规则,将一张表分解成多张不同的表。比如将用户订单记录根据时间成多个表。
  • 分表与分区的区别在于:分区从逻辑上来讲只有一张表,而分表则是将一张表分解成多张表
  • 2、表分区的好处?
  • 1)存储更多数据。分区表的数据可以分布在不同的物理设备上,从而高效 地利用多个硬件设备。和单个磁盘或者文件系统相比,可以存储更多数据
  • 2)优化查询在 where 语句中包含分区条件时,可以只扫描一个或多个分 区表来提高查询效率;涉及 sum 和 count 语句时,也可以在多个分区上并行处理, 最后汇总结果。
  • 3)分区表更容易维护。例如:想批量删除大量数据可以清除整个分区。
  • 3、分区表的限制因素
  • 一个表最多只能有 1024 个分区
  • 如果分区字段中有主键或者唯一索引的列,那么多有主键列和唯一索引列都 必须包含进来。即:分区字段要么不包含主键或者索引列,要么包含全部主键和 索引列。
  • 分区表中无法使用外键约束
  • MySQL 的分区适用于一个表的所有数据和索引,不能只对表数据分区而不对 索引分区,也不能只对索引分区而不对表分区,也不能只对表的一部分数据分区。
  • 4、分区表的类型
  • RANGE 分区: 这种模式允许将数据划分不同范围。例如可以将一个表通过 年份划分成若干个分区
  • LIST 分区: 这种模式允许系统通过预定义的列表的值来对数据进行分割。 按照 List 中的值分区,与 RANGE 的区别是,range 分区的区间范围值是连续的。
  • HASH 分区 :这中模式允许通过对表的一个或多个列的 Hash Key 进行计算, 最后通过这个 Hash 码不同数值对应的数据区域进行分区。例如可以建立一个对 表主键进行分区的表。
  • KEY 分区:上面 Hash 模式的一种延伸,这里的 Hash Key 是 MySQL 系统产 生的。
  • Column 分区:需要和 RANGE 和 List 结合,支持字符串和日期的分区,也支 持多列分区。
  • 复合分区/子分区:分区之下还可以再分区。
  • 5、在实际工作中用分区表比较少
  • 1)分区表,分区键设计不太灵活,如果不走分区键,很容易出现全表锁
  • 2)自己分库分表,自己掌控业务场景与访问模式,可控。分区表,研发写 了一个 sql,都不确定 mysql 是怎么操作的,不太可控
  • 3)分区表无论怎么分,都是在一台机器上,天然就有性能的上限

说几条 MySQL SQL 的执行做的优化手段

  • 1、对 SQL 语句的优化,MySQL 会对我们的 SQL 语句做重写,包括条件化简, 比如常量传递、除没用的条件等等;还会将一些外连接转换为内连接,然后选择成本最低的方式执行对 IN 子查询会进行物化物化表转连接查询转换为半连接等方式进行
  • 2、在 SQL 语句的执行过程中,MySQL 引入了索引条件下推。比如 where 后面的多个搜索条件都使用到了一个二级索引列,这些搜索条件中虽然出现了索 引列,有些却不能使用到索引,像 like ‘%…’查询,MySQL 为了避免不必要的回表, 从二级索引取得的索引记录,先做条件判断,如果条件不满足,则该二级索引记 录不会去回表,这样可以大量的减少回表操作的随机 IO 成本。
  • 3、在回表操作上,因为每次执行回表操作时都相当于要随机读取一个聚簇 索引页面,而这些随机 IO 带来的性能开销比较大。MySQL 中提出了一个名为 Disk-Sweep Multi-Range Read (MRR,多范围读取)的优化措施,即先读取一部分二 级索引记录,将它们的主键值排好序之后再统一执行回表操作
  • 4、MySQL 在一般情况下执行一个查询时最多只会用到单个二级索引,但存在有特殊情况,也可能在一个查询中使用到多个二级索引,称之为:索引合并, 比如 Intersection 交集合并、Union 索引合并等等

InnoDB 引擎的三大特性是什么?

  • InnoDB 的三大特性是:Buffer Pool、自适应 Hash 索引、双写缓冲区
  • 自适应 Hash 索引,InnoDB 存储引擎内部自己去监控索引表,如果监控到某 个索引经常用,那么就认为是热数据,然后内部自己创建一个 hash 索引,称之 为自适应哈希索引( Adaptive Hash Index,AHI),创建以后,如果下次又查询到 这个索引,那么直接通过 hash 算法推导出记录的地址,直接一次就能查到数据。
  • InnoDB 存储引擎使用的哈希函数采用除法散列方式,其冲突机制采用链表 方式。
  • Buffer Pool,为了提高访问速度,MySQL 预先就分配/准备了许多这样的空 间,为的就是与 MySQL 数据文件中的页做交换,来把数据文件中的页放到事先 准备好的内存中。数据的访问是按照页(默认为 16KB)的方式从数据文件中读 取到 buffer pool 中。Buffer Pool 按照最少使用算法(LRU),来管理内存中的页
  • ==Buffer Pool 实例允许有多个,==每个实例都有一个专门的 mutex 保护。Buffer Pool 中缓存的数据页类型有: 索引页、数据页、undo 页、插入缓冲(insert buffer)、 自适应哈希索引、InnoDB 存储的锁信息、数据字典信息(data dictionary)等等。
  • 双写缓冲区,是一个位于系统表空间的存储区域,在写入时,InnoDB 先把 从缓冲池中的得到的 page 写入系统表空间的双写缓冲区。之后,再把 page 写 到.ibd 数据文件中相应的位置。如果在 page 写入数据文件的过程中发生意外崩 溃,InnoDB 在稍后的恢复过程中在 doublewrite buffer 中找到完好的 page 副本用 于恢复。
  • doublewrite 是顺序写,开销比较小。所以在正常的情况下, MySQL 写数据 page 时,会写两遍到磁盘上,第一遍是写到 doublewrite buffer,第二遍是从 doublewrite buffer 写到真正的数据文件中
  • 它的主要作用是为了避免 partial page write(部分页写入)的问题。因为 InnoDB 的 page size 一般是 16KB,校验和写入到磁盘是以 page 为单位进行的。 而操作系统写文件是以 4KB 作为单位的,每写一个 page,操作系统需要写 4 个 块,中间发生了系统断电或系统崩溃,只有一部分页面是写入成功的。这时 page 数据出现不一样的情形,从而形成一个"断裂"的 page,使数据产生混乱。

redolog binlog 的区别是什么?

  • 答案见下一题

MySQL 崩溃后的恢复为什么不用 binlog

  • 1、这两者使用方式不一样
  • binlog 会记录表所有更改操作,包括更新删除数据,更改表结构等等,主要 用于人工恢复数据,而 redo log 对于我们是不可见的,它是 InnoDB 用于保证 crash-safe 能力的,也就是在事务提交后 MySQL 崩溃的话,可以保证事务的持久 性,即事务提交后其更改是永久性的。
  • 一句话概括:binlog 是用作人工恢复数据,redo log 是 MySQL 自己使用, 用于保证在数据库崩溃时的事务持久性
  • 2、redo log 是 InnoDB 引擎特有的,binlog 是 MySQL 的 Server 层实现的, 所有引擎都可以使用
  • 3、redo log 是物理日志,记录的是“在某个数据页上做了什么修改”,恢复 的速度更快;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这的 c 字段加 1 ”,binlog 有三种日志记录格式 Row、SQL、混合模式。
  • 4、redo log 是“循环写”的日志文件,redo log 只会记录未刷盘的日志,已 经刷入磁盘的数据都会从 redo log 这个有限大小的日志文件里删除。binlog 是 追加日志,保存的是全量的日志
  • 5、最重要的是,当数据库 crash 后,想要恢复未刷盘但已经写入 redo log 和 binlog 的数据到内存时,binlog 是无法恢复的。虽然 binlog 拥有全量的日志, 但没有一个标志让 innoDB 判断哪些数据已经入表(写入磁盘),哪些数据还没有。
  • 比如,binlog 记录了两条日志:
  • 给 ID=2 这一行的 c 字段加 1
  • 给 ID=2 这一行的 c 字段加 1
  • 在记录 1 入表后,记录 2 未入表时,数据库 crash。重启后,只通过 binlog 数据库无法判断这两条记录哪条已经写入磁盘,哪条没有写入磁盘,不管是两条 都恢复至内存,还是都不恢复,对 ID=2 这行数据来说,都不对。
  • 但 redo log 不一样,只要刷入磁盘的数据,都会从 redo log 中抹掉,数据 库重启后,直接把 redo log 中的数据都恢复至内存就可以了

MySQL 如何实现事务的 ACID

  • 参见下个问题。

InnoDB 事务是如何通过日志来实现的?

  • 总的来说,事务的原子性是通过 undo log 来实现的,事务的持久性性是通 过 redo log 来实现的,事务的隔离性是通过读写锁+MVCC 来实现的
  • 事务的一致性通过原子性、隔离性、持久性来保证。也就是说 ACID 四大特 性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保 证一致性,数据库提供的手段。数据库必须要实现 AID 三大特性,才有可能实现 一致性。同时一致性也需要应用程序的支持,应用程序在事务里故意写出违反约 束的代码,一致性还是无法保证的,例如,转账代码里从 A 账户扣钱而不给 B 账户加钱,那一致性还是无法保证。
  • 至于 InnoDB 事务是如何通过日志来实现的,简单来说,因为事务在修改页 时,要先记 undo,在记 undo 之前要记 undo 的 redo, 然后修改数据页,再记数据页修改的 redo。 Redo(里面包括 undo 的修改) 一定要比数据页先持 久化到磁盘
  • 当事务需要回滚时,因为有 undo,可以把数据页回滚到前镜像的状态,崩 溃恢复时,如果 redo log 中事务没有对应的 commit 记录,那么需要用 undo把该事务的修改回滚到事务开始之前。如果有 commit 记录,就用 redo 前滚到 该事务完成时并提交掉
  • 更详细的回答是:
  • redo 通常是物理日志,记录的是页的物理修改操作,用来恢复提交事务修改的页操作。而 undo 是逻辑日志,根据每行记录进行记录,用来回滚记录到某个特定的版本。
  • 当事务提交之后会把所有修改信息都会存到 redo 日志中。redo 日志由两部分组成,一个是在内存里的 redo log buffer,另一个是在磁盘里的 redo log 文件。
  • mysql 为了提升性能不会把每次的修改都实时同步到磁盘,而是会先存到 Buffer Pool(缓冲池)里头,把这个当作缓存来用。然后使用后台线程去做缓冲池 和磁盘之间的同步。
  • 系统重启后读取 redo log 恢复最新数据。虽然 redo log 会在事务提交前做一 次磁盘写入,但是这种 IO 操作相比于 buffer pool 这种以页(16kb)为管理单位 的随机写入,它做的是几个字节的顺序写入,效率要高得多。
  • redo log buffer 中的数据,会在一个合适的时间点刷入到磁盘中。
  • 这个合适的时间点包括:
  • 1、MySQL 正常关闭的时候;
  • 2、MySQL 的后台线程每隔一段时间定时的讲 redo log buffer 刷入到磁盘, 默认是每隔 1s 刷一次;
  • 3、当 redo log buffer 中的日志写入量超过 redo log buffer 内存的一半时, 即超过 8MB 时,会触发 redo log buffer 的刷盘;
  • 4、当事务提交时,根据配置的参数 innodb_flush_log_at_trx_commit 来决定 是否刷盘。要严格保证数据不丢失,必须得保证 innodb_flush_log_at_trx_commit 配置为 1。
  • redo log 在进行数据重做时,只有读到了 commit 标识,才会认为这条 redo log 日志是完整的,才会进行数据重做,否则会认为这个 redo log 日志不完整, 不会进行数据重做。
  • undo log 和 redo log 记录物理日志不一样,它是逻辑日志。可以认为当 delete 一条记录时,undo log 中会记录一条对应的 insert 记录,反之亦然,当 update 一条记录时,它记录一条对应相反的 update 记录。当执行回滚时,就可以从 undo log 中的逻辑记录读取到相应的内容并进行回滚。
  • 而事务的隔离性,也可以通过 undo log 来实现的:当读取的某一行被其他事 务锁定时,它可以从 undo log 中分析出该行记录以前的数据是什么,从而提供该 行版本信息,帮助用户实现一致性非锁定读取,这也是 MVCC 的实现机制的组成 部分。

什么是当前读和快照读?

  • 当前读
  • 像 select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。是 一种悲观锁的实现
  • 快照读
  • 像不加锁的 select 操作就是快照读,即不加锁的非阻塞读;快照读的前提是 隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制, 即 MVCC。

什么是 MVCC

  • MVCC (Multi-Version Concurrency Control) ,叫做基于多版本的并发控制协议。 他是和 LBCC(Lock-Based Concurrency Control)基于锁的并发控制概念是相对的。 MVCC 是乐观锁的一种实现方式,它在很多情况下,避免了加锁操作,降低了开销**既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有 可能是之前的历史版本。 **
  • MVCC 最大的好处:读不加锁,读写不冲突。在读多写少的 OLTP 应用中, 读写不冲突是非常重要的,极大的增加了系统的并发性能,现阶段几乎所有的 RDBMS 包括 MySQL,都支持了 MVCC。

MVCC 的底层实现原理是什么?

  • MVCC 实现原理主要是依赖记录中的隐式字段,undo 日志 ,Read View 来 实现的
  • MySQL 中每行记录除了我们自定义的字段外,还有数据库隐式定义的 DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID 等字段**DB_TRX_ID 是最近修改(修改/插入) 事务 ID,记录创建这条记录/最后一次修改该记录的事务 ID。DB_ROLL_PTR,回滚指针,用于配合 undo 日志,指向这条记录的上一个版本。 **
  • 不同事务或者相同事务的对同一记录的修改,会导致该记录的 undo log 成为 一条记录版本线性表,也就是版本链
  • 事务进行快照读操作的时候产生一个 Read View,记录并维护系统当前活跃 事务的 ID,因为当每个事务开启时,都会被分配一个 ID, 这个 ID 是递增的,所 以最新的事务,ID 值越大。
  • Read View 主要是将要被修改的数据的最新记录中的 DB_TRX_ID(即当前事 务 ID)取出来,与系统当前其他活跃事务的 ID 去对比(由 Read View 维护),如果 DB_TRX_ID 跟 Read View 的属性做了某些比较,不符合可见性,那就就通过 DB_ROLL_PTR 回滚指针去取出 Undo Log 中的 DB_TRX_ID 再比较,即遍历链表的 DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条 件的 DB_TRX_ID, 那么这个 DB_TRX_ID 所在的旧记录就是当前事务能看见的最新老版本
  • RC,RR 级别下 Read View 生成时机的不同,造成 RC,RR 级别下快照读的结果 的不同RC 隔离级别下,是每个快照读都会生成并获取最新的 Read View,也就 是说事务中,每次快照读都会新生成一个快照和 Read View, 这就是我们在 RC 级 别下的事务中可以看到别的事务提交的更新的原因;而在 RR 隔离级别下,则是 同一个事务中的第一个快照读才会创建 Read View, 之后的快照读获取的都是同一个 Read View,快照读生成 Read View 时,Read View 会记录此时所有其他活动 事务的快照,这些事务的修改对于当前事务都是不可见的。而早于 Read View 创 建的事务所做的修改均是可见。

**什么是锁?**MySQL 中提供了几类锁?

  • 锁是实现数据库并发控制的重要手段,可以保证数据库在多人同时操作时能够正常运行。MySQL 提供了全局锁、行级锁、表级锁。其中 InnoDB 支持表级 锁和行级锁,MyISAM 只支持表级锁。

什么是全局锁、共享锁、排它锁?

  • 全局锁就是对整个数据库实例加锁,它的典型使用场景就是做全库逻辑备份。 这个命令可以使整个库处于只读状态。使用该命令之后,数据更新语句、数据定 义语句、更新类事务的提交语句等操作都会被阻塞。
  • 共享锁又称读锁 (read lock),是读取操作创建的锁。其他用户可以并发读取 数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放 所有共享锁。当如果事务对读锁进行修改操作,很可能会造成死锁。
  • 排他锁 exclusive lock(也叫 writer lock)又称写锁
  • 若某个事物对某一行加上了排他锁,只能这个事务对其进行读写,在此事务 结束之前,其他事务不能对其进行加任何锁,其他进程可以读取,不能进行写操 作,需等待其释放。排它锁是悲观锁的一种实现。
  • 若事务 1 对数据对象 A 加上 X 锁,事务 1 可以读 A 也可以修改 A,其 他事务不能再对 A 加任何锁,直到事物 1 释放 A 上的锁。这保证了其他事务 在事物 1 释放 A 上的锁之前不能再读取和修改 A。排它锁会阻塞所有的排它 锁和共享锁。

MySQL 中的表锁有哪些?

  • MySQL 里表级锁有两种:普通表级锁、元数据锁(meta data lock)简称 MDL 和 AUTO-INC 锁。表锁的语法是 lock tables t read/write。
  • 可以用 unlock tables 主动释放锁,也可以在客户端断开的时候自动释放。 lock tables 语法除了会限制别的线程的读写外,也限定了本线程接下来的操作对 象。
  • 对于 InnoDB 这种支持行锁的引擎,一般不使用 lock tables 命令来控制并 发,毕竟锁住整个表的影响面还是太大。
  • MDL:不需要显式使用,在访问一个表的时候会被自动加上。
  • MDL 的作用:保证读写的正确性。
  • 在对一个表做增删改查操作的时候,加 MDL 读锁;当要对表做结构变更操 作的时候,加 MDL 写锁。
  • 读锁之间不互斥,读写锁之间,写锁之间是互斥的,用来保证变更表结构操 作的安全性。
  • AUTO-INC 锁,也就是在执行插入语句时就在表级别加一个 AUTO-INC 锁,然 后为每条待插入记录的 AUTO_INCREMENT 修饰的列分配递增的值。

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

  • InnoDB 是基于索引来完成行锁,在锁的算法实现上有三种

    · Record lock:单个行记录上的锁

    · Gap lock:间隙锁,锁定一个范围,不包括记录本身

    · Next-key lock:record+gap 锁定一个范围,包含记录本身

  • Gap 锁设计的目的是为了阻止多个事务将记录插入到同一范围内,而这会导 致幻读问题的产生,innodb 对于行的查询使用 next-key lock,Next-locking keying 是 Record lock 和 Gap lock 的组合。当查询的索引含有唯一属性时,将 next-key lock 降级为 record key。

  • 有两种方式显式关闭 gap 锁 ,第一种. 将事务隔离级别设置为 RC ;第二种. 将参数 innodb_locks_unsafe_for_binlog 设置为 1。

谈一下 MySQL 中的死锁

  • 死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象, 若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态 或系统产生了死锁。
  • 如何查看死锁?
  • 使用命令 show engine innodb status 查看最近的一次死锁
  • InnoDB Lock Monitor 打开锁监控,每 15s 输出一次日志。使用完毕后建议 关闭,否则会影响数据库性能。
  • 对待死锁常见的两种策略:
  • 通过 innodblockwait_timeout 来设置超时时间,一直等待直到超时;
  • 发起死锁检测,发现死锁之后,主动回滚死锁中的某一个事务,让其它事务 继续执行。

简述下 MySQL8 中的新增特性有哪些

  • MySQL8 在功能上的我们需要关注增强主要有:1、账户与安全;2、索引 3、 InnoDB 增强
  • 主要表现在:
  • 1、用户的创建与授权需要两条单独的 SQL 语句执行。认证插件更新。密码管理和角色管理发生变化。
  • 2、隐藏索引,被隐藏的索引不会被优化器使用,但依然真实存在,主要用于软删除,可以根据需要后续真正删除或者重新可视化。
  • 开始真正支持降序索引,以往的 MySQL 虽然支持降序索引,但是写盘的时候依然是升序保存。MySQL8.0 中则是真正的按降序保存
  • 不再对 group by 操作进行隐式排序。order by null就可以让group by不再排序
  • 索引中可以使用函数表达式,创建表时创建一个函数索引,查询的时候使用同样的函数就可以利用索引了。
  • 3、原子 ddl 操作,MySQL5.7 执行 drop 命令 drop table t1,t2; 如果 t1 存在, t2 不存在,会提示 t2 表不存在,但是 t1 表仍然会被删除,MySQL8.0 执行同样 的 drop 命令,会提示 t2 表不存在,而且 t1 表不会被删除,保证了原子性。
  • 自增列持久化,解决了之前的版本,主键重复的问题。MySQL5.7 及其以前 的版本,MySQL 服务器重启,会重新扫描表的主键最大值,如果之前已经删除过 id=100 的数据,但是表中当前记录的最大值如果是 99,那么经过扫描,下一条 记录的 id 是 100,而不是 101。MySQL8.0 则是每次在变化的时候,都会将自增 计数器的最大值写入 redo log,同时在每次检查点将其写入引擎私有的系统表。 则不会出现自增主键重复的问题。

实战

小实战

  • 现在有三个表:商品表 goods、订单表 goods_order、订单详情表 goods_order_detail。三个表中的数据有两种方式导入:

  • 1、执行 goods_stru.sql、goods_order_stru.sql、goods_order_detail_stru.sql 建立原始表,然后执行存储过程 create_goods 产生原始数据,再执行存储过程 randon_detail 进行数据更新;

  • 2、执行 SQL 脚本 goods_all.sql、 goods_order_all.sql、 goods_order_detail_all.sql,同时导入表结构和数据。

  • 三个表目前都没有索引:

  • 三个表目前的记录数如下:

  • 现在有这么个业务场景:查找包含赠送商品且商品编号是 666 的订单

  • select o.* from goods_order o where o.id in(select order_id from goods_order_detail od where od.goods_id=666 and od.price=0);

  • 花了 5 秒多,有没有办法改进呢?先看执行计划如下:

  • 从执行计划可以看出,MySQL 对这个查询的处理是:

  • 1、建立了物化表;2、将子查询改为了利用物化表进行连接查询;3、只利 用到了主键;4、全表扫描的情况很严重。

  • 为了验证这一点,我们查看下 MySQL 对这个查询语句的改写:

  • mysql> show warnings;

  • /* select#1 */ select mysqladv.o.id AS id,mysqladv.o.order_no AS

    order_no from mysqladv.goods_order o semi join

    (mysqladv.goods_order_detail od) where ((mysqladv.o.id =

    <subquery2>.order_id) and (mysqladv.od.price = 0) and

    (mysqladv.od.goods_id = 666))

  • 可以看到 MySQL 将这个子查询改造为了半连接 semi join。那么我们要对这 个 SQL 语句的执行进行优化。

  • 分析我们的 SQL 语句,在 in 子查询中,用到了 goods_order_detail 中的 goods_id 字段和 price 字段作为条件,我们考虑增加一个 goods_id 字段和 price 字段的联合索引。

  • ALTER TABLE mysqladv.goods_order_detail ADD INDEX``idx_price_price(goods_id, price`);

  • 再来看看执行效果和执行计划:

  • 可以看到,虽然还有物化表,但是对物化表的扫描行数,已经由 9713504 降到了 6,对物化表也不再是全表扫描,速度提升到了 0.01 秒。

  • 能不能再做提升呢?去除物化的过程和全表扫描呢?审视我们的子查询: select order_id from goods_order_detail od where od.goods_id=666 and od.price=0;

  • 可以利用索引覆盖,在索引扫描的时候直接把 order_id 查找出来,所以我们 修改下索引 idx_price_price,再增加一个字段 order_id。

  • ALTER TABLE mysqladv.goods_order_detail DROP INDEX idx_price_price, ADD INDEX idx_price_price_order(goods_id, price, order_id)

  • 再来看看执行效果和执行计划:

  • 可以看到,已经完全去除了物化的过程和全表扫描。

  • 当然,SQL 语句的执行中,上面的时间没有精确到毫秒,具体执行时长不知 道是多少,没关系,我们用 show profiles; 来观察。

  • set profiling=1;

  • select o.* from goods_order o where o.id in(select order_id from goods_order_detail od where od.goods_id=666 and od.price=0);

  • show profiles;

  • 我们将这个 SQL 语句由 5.07 秒优化到 0.01 秒,提升了 507 倍,再优化到 0.00075 秒,再次提升了 13 倍,和最初相比执行速度提升了 6760 倍。

  • 总结:

  • 1.建立索引很重要

  • 2.合适的索引更重要

  • 3、覆盖索引是个利器

  • 4.学会分析 sql 执行计划,mysql 会对 sql 进行优化,所以分析执行计划很重 要。

啤酒和尿布

  • 有这么个故事:全球零售业巨头沃尔玛在对消费者购物行为分析时发现,男 性顾客在购买婴儿尿片时,常常会顺便搭配几瓶啤酒来犒劳自己,于是尝试推出 了将啤酒和尿布摆在一起的促销手段。没想到这个举措居然使尿布和啤酒的销量 都大幅增加了。如今,“啤酒+尿布”的数据分析成果早已成了大数据技术应用 的经典案例。不过这个故事的真实性是存在着很大的疑问的。

  • 但是这个故事本身反映的是销售商品相关性分析,其中的关联规则是反映一 个事物与其他事物之间的相互依存性和关联性,常用于实体商店或在线电商的推 荐系统:通过对顾客的购买记录数据库进行关联规则挖掘,最终目的是发现顾客 群体的购买习惯的内在共性,例如购买产品 A 的同时也连带购买产品 B 的概率, 根据挖掘结果,调整货架的布局陈列、设计促销组合方案,实现销量的提升。当 然这个属于数据挖掘和大数据的领域,比如实际中使用 ClickHouse 来做这些工作。

  • 那么我们用 MySQL 能做吗?在数据量不是特别大的情况下也是可以的。依 然使用我们前面所用的三张表商品表 goods、订单表 goods_order、订单详情表 goods_order_detail 来试试。

  • 现在的业务场景如下,对我们销售的商品找到关联度最高的商品,也就是在 一张订单表中总是相伴出现的商品,不过考虑到实际情况,购买次数少的商品没 有太大分析的必要。思路如下:

  • 第一步,找到订单详情表中出现的商品明细,按商品在详情表中出现的次数 排序:

  • select order_id,goods_id from goods_order_detail where

    goods_id in (select odo.goods_id

    from goods_order_detail odo

    GROUP BY odo.goods_id

    ORDER BY count(odo.goods_id));

  • 第二步,通过第一步查询后的临时表作为主表,通过单号为关键字段,做连 接(外连接和内连接均可,对结果基本没有影响):

  • select aa.goods_id as src_goods,bb.goods_id as relative_goods,count(*) from

    (select order_id,goods_id from goods_order_detail where

    goods_id in (select odo.goods_id from goods_order_detail odo GROUP BY

    odo.goods_id ORDER BY count(odo.goods_id))) aa

    left join goods_order_detail bb

    on aa.order_id=bb.order_id

    where aa.goods_id != bb.goods_id

    group by aa.goods_id,bb.goods_id

    order by count(*) desc;

  • 这个 SQL 语句的执行是很慢的,我们不去具体执行,看看他的执行计划和执 行成本。执行成本为 1.88E13,从执行计划来看,扫描的行数和访问类型都让人不满意, 需要改进一下。怎么改?

  • 仔细分析我们的的语句和业务,有必要去寻找所有商品的关联商品吗?购 买次数很低的商品没必要进行这种统计与分析,所以我们先把找到订单详情表中 出现的商品明细这个步骤提取出来,限定只寻找售出数量前 10 位的商品。

  • select odo.goods_id,count(odo.goods_id)

    from goods_order_detail odo

    GROUP BY odo.goods_id

    ORDER BY count(odo.goods_id) desc

    limit 10;

  • 这个速度还是可以接受的,我们把查询出来的商品在应用程序中缓存起来, 然后,一个个商品去处理。比如先处理 id 为 470 的商品:

  • select bb.goods_id as relative_goods,count(*) from

    (select order_id from goods_order_detail where

    goods_id=470) aa

    left join goods_order_detail bb

    on aa.order_id=bb.order_id

    where bb.goods_id != 470

    group by bb.goods_id

    order by count(*) desc;

  • 成本变为:

  • 相比原来的 1.88E13,现在的 1.68E12,已经降低了一个数量级,但依然很 大,最重要的是驱动表的扇出数和被驱动表的查询记录数都非常大,需要继续优 化。虽然驱动表 aa 显示了“Using tempopray,Using filesort”,但是驱动表 aa 的 优化余地很小,发力点还在对被驱动表 bb 的优化上,为什么?请同学自行思考。

  • 目前这个 sql 语句利用的是 idx_price_price_order 索引,看起来对我们当前 的业务不是特别适合,从执行计划来看,对被驱动表 bb 的扫描行数达到了 9713504,太大了。我们需要对索引进行调整,分析我们的 SQL 语句:

  • select bb.goods_id as relative_goods,count(*) from (select order_id from goods_order_detail where goods_id=470) aa left join goods_order_detail bb

  • on aa.order_id=bb.order_id where bb.goods_id != 470 group by bb.goods_id order by count(*) desc;

  • 所以,我们考虑,在连接列 order_id 上建立一个索引:

  • ALTER TABLE mysqladv.goods_order_detail ADD INDEX ``idx_order(order_id`);

  • 可以看见,被驱动表 bb 的扫描行数降到了 9,成本变为了:

  • 相比原来的 1.88E13、1.68E12,现在降到了 1.02E7。

  • 继续考虑,我们对被驱动表的访问,能不能再降低点成本?比如回表的成 本?因为我们现在的索引 idx_order 是会引发回表的。所以我们再建立了一个联 合索引:

  • ALTER TABLE mysqladv.goods_order_detail

  • DROP INDEX idx_order,

  • ADD INDEX idx_order_goods(order_id, goods_id)

  • 为什么我们要删除idx_order,建立idx_order_goods,保留idx_order不好吗? 请同学们自行思考。

  • 可以看见,被驱动表 bb 的扫描利用了覆盖索引,成本变为了 2.74E6,实际 的查询时间:

  • 基本上在可以接受的范围内了。如果我们继续增加一个索引:

  • ALTER TABLE mysqladv.goods_order_detail

  • ADD INDEX idx_goods_order(goods_id, order_id);

  • 虽然执行计划中的成本没有太大变化,但是查询时间还能降到 4.70S,基本 上时间再次减半。

  • 为什么?我们不是已经有了一个 idx_goods_price_order(``goods_id, price, order_id`)吗?请同学们自行思考。

  • 最后考虑到业务和网络传输量,960 个结果其实是没有必要的,所以最终 的 SQL 如下:

  • select bb.goods_id as relative_goods,count(*) from

    (select order_id from goods_order_detail where

    goods_id=470) aa

    left join goods_order_detail bb

    on aa.order_id=bb.order_id

    where bb.goods_id != 470

    group by bb.goods_id

    order by count(*) desc

    limit 10;

  • 索引如下:

  • 当然上面的 SQL 如果去除排序,比如变为

  • Select …………

    group by bb.goods_id

    order by null;

  • 执行成本还可降低到 2.55E6 同时执行计划里不再出现 using_filesort,不过 对整体的影响并不大,从实际的执行时间来看,也确实是如此。

  • 在这种情况下,执行成本相比原来的 1.88E13、1.68E12,现在降到了 1.02E7, 再到 2.74E6,实际的一个商品的查询时间为 4.7 秒,算上我们要查询十个商品, 总时间为 4.7X10+2.93=50 秒,基本上查询成本从 1.88E13 变为 2.74E6X10,降低 了 10 万倍,对于我们这种属于非实时统计的业务来说,这个速度相对还是不错的。当然如果你对上面的业务还有更好的优化措施,并且实验通过了,请分享。

  • https://ke.qq.com/comment/index.html?cid=287404

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

mysql16 的相关文章

  • IntelliJ IDEA包层级结构显示

    点开项目结构视图右上角那个齿轮 xff0c 如下图所示 看第二行菜单 xff0c Hide Empty Middle Packages 前面有个勾 xff0c 这表示现在包是不分层级显示的 xff0c 把勾去掉 xff0c 包就会分层级显示
  • RN 多行换行布局

    import React Component from 39 react 39 import AppRegistry StyleSheet Text View from 39 react native 39 export default c
  • PCB布局元器件的便捷操作(PCB批量修改线宽,PCB器件放置背面,PCB板反转)

    1 设置同网络内部的线宽 方法一 xff1a 未布线之前可以设置在规则里面提前设置 新加规则 方法二 xff1a 布线以后使用查找相似方法进行修改 找到该网络右击 找到相似属性 点击 gt OK 修改线宽 gt 回车 完成 2 将元器件放置
  • github pages搭建博客的域名解析(简单有效)

    折腾了一个小时终于把域名解析弄好了 xff0c 还顺带了解了不少关于http的知识 xff0c 亲手实践还是最有效的方法 xff0c 哈哈 这里记录一下自己的步骤 注意 xff1a 如果只需要解析主域名 xff0c 那么只做第一步就行 xf
  • 用nginx实现反向代理,实现外网访问内网的服务

    环境背景 xff0c 服务器为Ubuntu xff1a 一台可以连接公网和内网的服务器A xff0c 公网IP地址 xff1a 61 174 xff0c 另一台内网服务器B上安装jenkins服务 xff0c 内网的IP地址192 168
  • g++ is not recognized as an internal or external command解决方法,初用MinGW碰到的坑

    之前一直用Qt或者CodeBlocks上带的MinGW xff0c 这次自己装了一个 xff0c 碰到一堆问题 一 在用g 43 43 编译的时候报错 xff1a stddef h No such file or directory std
  • Ubuntu 20.04: harfbuzz version too old解决方法

    参考链接 xff1a unix stackexchange com 现象 在ubuntu20 04上安装了Ao xff08 可以看做Microsoft todo的linux版 xff09 xff0c 从终端启动时报错 xff1a Faile
  • 图像处理:理想低通滤波器、butterworth滤波器(巴特沃斯)、高斯滤波器实现(python)

    对图像进行频域滤波的几种常用低通滤波器 xff0c python opencv实现 xff1a 低通滤波器 xff08 Low pass filter xff09 容许低频信号通过 xff0c 但减弱频率高于截止频率的信号的通过 对于不同滤
  • 图像处理:迭代阈值分割算法实现(python)

    使用迭代的方法对图像全局的阈值进行估计 xff0c 效果优于传统的双峰阈值分割 算法流程 xff1a 给定初始值 T 0 T 0 T 0 xff0c 最好是全局灰度平均值根据阈值
  • filter- 条件合并

    发现一个很好用的用法 可以根据前端传来的数据判断 xff0c 数据筛选的条件 可以先新建一个列表 xff1a filter 61 根据条件 xff0c 给这个列表加条件 比如 xff1a if status filter append Us
  • HC-05蓝牙模块主从连接配置(无坑)

    做课程设计要用到两个HC 05连接 xff0c 之前用过HC 06 xff0c 拿原来那个方法配还是有些坑 xff0c 搞了一下午最后还是成功了 尝试了两个方案 xff0c 一是用usb转TTL配置 xff1b 二是用Arduino xff
  • PyQt 自定义气泡弹窗

    效果图 xff1a 用来做弹窗提醒 xff0c 气泡弹窗会自动关闭 xff0c 持续时间1600ms xff0c 在750ms时开始逐渐透明 xff0c 1600ms时消失 窗口继承的是QDialog 设置背景透明 无标题栏 无边框 xff
  • pyinstaller打包PyQt程序 + 制作安装包

    打包发布PyQt5程序 xff0c 制作安装文件 以自己的密码管理器为例 用到的工具 xff1a 平台 xff1a Windows10python 3 7 6 xff08 安装好所需的包 xff09 pyinstallerupx xff08
  • 树莓派开机自动发送邮件脚本

    开机联网后自动获取本机内网IP xff0c 并通过邮件或者server酱发送自己的内网IP span class token keyword import span smtplib span class token keyword from
  • C++ STL Map按照value排序

    xff08 记录一下 xff09 STL的map底层实现一般是红黑树 xff0c 会自动按照key排序 xff0c 按照value排序好像也没有更好的方法了 xff0c 只能将map转成vector lt pair gt 再进行排序了 sp
  • typescript

    TypeScript TypeScript 是一种给 JavaScript 添加特性的语言扩展 支持es6 xff0c 是微软提出的一种编程语言 TypeScript 设计目标是开发大型应用 xff0c 它可以编译成纯 JavaScript
  • 串口打印中途无log出来,显示console:$字样,一段时间后才再显示

    调试时 xff0c 我们在外接串口打印log时 xff0c 发现lk打印完成后跳转到kernel阶段 xff0c 显示console 字样 xff0c 过一段时间后才重新开始打印log xff0c 这样导致我们抓取的log不全 xff0c
  • 【Hexo】域名绑定篇

    关于Hexo的一切 我的Hexo专栏 零 前言 继上篇 xff0c 本篇主要讨论购买域名以及如何绑定并进行解析 一 购买 我这里只推荐两个平台 xff1a 阿里云官网和 腾讯云官网 xff0c 选择你中意的即可 我买的是一个很辣鸡的域名ww
  • 配置一个好看的PowerShell

    工作生活中用到 PowerShell 的时刻其实有很多 xff0c 但是那深蓝色的背景实在让人想吐槽几句 今天我们就来美化一下它 xff0c 几十种花里胡哨的主题任你选择 用到的是oh my posh xff0c 跟oh my zsh类似

随机推荐

  • scikit-learn介绍-非常流行的python机器学习库

    scikit learn是一个建立在Scipy基础上的用于机器学习的Python模块 在不同的应用领域中 xff0c 已经大展出为数众多的基于Scipy的工具包 xff0c 他们统称为Scikits 而在所有的分支版本中 xff0c sci
  • redis

    redis Redis 是一个Key Value 数据库 xff0c 主要用于存储缓存 redis支持的数据类型 xff1a String字符串 xff1a 设置key值 xff1a set key value string类型是二进制安全
  • 多生产者——多消费者问题

    问题背景 假设有四个人 xff1a 父亲 母亲 女儿 儿子 xff0c 和一个空盘子 xff0c 里面最多放一个水果 父亲每次向盘子中放一个苹果 xff0c 女儿只会吃苹果 母亲每次向盘子中放一个橘子 xff0c 儿子只会吃橘子 这个问题可
  • Android-MVVM-Databinding的原理、用法与封装

    前言 说起 DataBinding ViewBinding 的历史 xff0c 可谓是一波三折 xff0c 甚至是比 Dagger Hilt 还要传奇 说起依赖注入框架 Dagger2 Hilt xff0c 也是比较传奇 xff0c 刚出来
  • day03 Python基础

    day03 Python基础 版权声明 xff1a 本博客转载自路飞学城Python全栈开发培训课件 xff0c 仅用于学习之用 xff0c 严禁用于商业用途 xff0c 未经授权 xff0c 严禁转载 欢迎访问路飞学城官网 xff1a h
  • 最新Spire.pdf Spire.Doc Spire.Xls等无水印使用

    Aspose与Spire功能都很强大 xff0c 为什么要选择Spire xff0c Spire支持WPF组件 xff0c Aspose默认没有 新建 net6控制台程序 xff0c 用NuGet包添加Spire PDF引用 添加代码 us
  • 使用Pyinstaller发布带界面的程序(解决找不到文件问题)

    Pyinstaller Pyinstaller可以用来打包python代码 xff0c 生成可执行文件 xff08 主流平台都可以 xff09 xff0c 介绍就不说了 xff0c 可以百度或者去官网看看 xff1a https www p
  • 动态分配内存——new/delete

    动态分配内存 1 使用new分配内存2 使用delete释放内存3 例子 xff1a 数组编译时分配内存和运行时分配内存4 动态数组补充 xff1a 程序的内存分配 1 使用new分配内存 使用格式 xff1a span class tok
  • Spring学习(一) Spring环境配置

    工具原料 xff1a JDK Eclipse IDEA 开始学Spring xff0c 应该已经安好java环境了 xff0c 这里我就不赘述了 xff0c 直接开始开始下一步的教程 配置spring环境需要导入spring相关的jar包
  • vue-lottie动画效果(进阶篇)

    vue lottie动画效果 以下是个人见解部分 个人见解 xff1a 优点 xff1a 简单高效 xff0c 动画文件小 xff0c 丝滑流畅 xff0c 动画可控性强 缺点 xff1a 依赖包非常重 xff0c 对动画要求不高的项目不太
  • Ubuntu18.04设置开机自启动自己的程序、脚本

    Ubuntu18 04设置开机自启动自己的程序 脚本 本文使用的机器是win10 43 Ubuntu18 04双系统 xff0c 虚拟机上的Ubuntu18 04操作一样 xff0c 均可参考此文 参考链接 xff0c 言简意赅 xff0c
  • 【Qt】【QDebug】【日志】实用的Qt日志打印-打印时间-线程-数据等信息

    Qt QDebug 日志 实用的Qt日志打印 打印时间 线程 数据等信息 在开发audio和video相关软件时 xff0c 收发速率很关键 xff0c 我们需要打印时间和线程等相关信息等日志 include lt QDebug gt 获取
  • DNS(域名解析协议)详解

    DNS协议 我们之前已经了解过ARP协议 如果说ARP协议是用来将IP地址转换为MAC地址 xff0c 那么DNS协议则是用来将域名转换为IP地址 xff08 也可以将IP地址转换为相应的域名地址 xff09 我们都知道 xff0c TCP
  • Mybatis之使用注解开发CRUD

    上一篇演示了如何使用XML来操作Mybatis实现CRUD xff0c 但是大量的XML配置文件的编写是非常烦人的 因此 Mybatis也提供了基于注解的配置方式 xff0c 下面我们来演示一下使用接口加注解来实现CRUD的的例子 首先是创
  • 查看windows服务器的I/O的3种方法

    http blog chinaunix net uid 20344928 id 5597137 html 碎碎念 xff1a 感觉第二种简单 windows查看I O的方法有3种 xff1a 1 任务管理器 打开任务管理器 xff0c 点击
  • 【Tensorflow】辅助工具篇——scikit-image介绍

    很多时候我们跑deep learning算法的难点不在于搭建网络 xff0c 而是数据获取与处理 xff0c 当你看到大量的数据却无从下手时该是怎样的心情 xff01 这几篇我将为大家介绍目前很多paper代码复现中比较流行的辅助工具 首先
  • 一劳永逸,wsl2出现“参考的对象类型不支持尝试的操作”的解决办法

    wsl在使用是会出现 参考的对象类型不支持尝试的操作 的故障导致无法使用 出现上述问题原因是使用代理软件 xff0c 或游戏加速服务 xff0c winsock出现问题 可以通过注册表的方式 xff0c 排除从winsock中排除wsl即可
  • Python中的路径获取方法总结

    遍历文件夹下文件 xff1a os walk dir path def getFlist path for root dirs files in os walk file dir print 39 root dir 39 root 当前路径
  • Android Studio设置了断点却无法进入断点调试(多进程调试)

    有的时候在Android Studio中明明设置了断点 xff0c 也确认了代码会走到断点处 xff0c 但是执行Debug后 xff0c 断点处会显示打钩 xff0c 却不能但不调试 xff0c 好像代码已经执行过去了 这种问题大概率就是
  • mysql16

    常见面试题 MySQL 中有哪些存储引擎 xff1f InnoDB 存储引擎 InnoDB 是 MySQL 的默认事务型引擎 xff0c 也是最重要 使用最广泛的存储引擎 它被设计用来处理大量的短期 short lived 事务 xff0c