MySQL 三万字精华总结 + 面试100 问,吊打面试官绰绰有余(收藏系列)

Posted JavaKeeper

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL 三万字精华总结 + 面试100 问,吊打面试官绰绰有余(收藏系列)相关的知识,希望对你有一定的参考价值。

ALTER TABLE 不能使用默认存储引擎时,都会生成一个警告。为了防止在所需的引擎不可用时出现令人困惑的意外行为,可以启用 NO_ENGINE_SUBSTITUTION SQL 模式。如果所需的引擎不可用,则此设置将产生错误而不是警告,并且不会创建或更改表

文件,.frm 文件是用来保存每个数据表的元数据(meta)信息,包括表结构的定义等,与数据库存储引擎无关,也就是任何存储引擎的数据表都必须有.frm文件,命名方式为 数据表名.frm,如user.frm。

查看mysql 数据保存在哪里:show variables like \'data%\'

MyISAM 物理文件结构为:

  • .frm文件:与表相关的元数据信息都存放在frm文件,包括表结构的定义信息等
  • .MYD (MYData) 文件:MyISAM 存储引擎专用,用于存储MyISAM 表的数据
  • .MYI (MYIndex)文件:MyISAM 存储引擎专用,用于存储MyISAM 表的索引相关信息
  • InnoDB 物理文件结构为:

  • .frm 文件:与表相关的元数据信息都存放在frm文件,包括表结构的定义信息等

  • .ibd 文件或 .ibdata 文件:这两种文件都是存放 InnoDB 数据的文件,之所以有两种文件形式存放 InnoDB 的数据,是因为 InnoDB 的数据存储方式能够通过配置来决定是使用共享表空间存放存储数据,还是用独享表空间存放存储数据。

    独享表空间存储方式使用.ibd文件,并且每个表一个.ibd文件 共享表空间存储方式使用.ibdata文件,所有表共同使用一个.ibdata文件(或多个,可自己配置)

  • 时需要全表扫描。而 MyISAM 用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快;
  • InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁。一个更新语句会锁住整张表,导致其他查询和更新都会被阻塞,因此并发访问受限。这也是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一;
  • 对比项MyISAMInnoDB
    主外键不支持支持
    事务不支持支持
    行表锁表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作行锁,操作时只锁某一行,不对其它行有影响,适合高并发的操作
    缓存只缓存索引,不缓存真实数据不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响
    表空间
    关注点性能事务
    默认安装
                --可以通过添加 \\G 来格式化输出信息。

  • 使用ALERT命令

  • ALTER TABLE tbl_name ADD PRIMARY KEY (column_list): 该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。
  • ALTER TABLE tbl_name ADD UNIQUE index_name (column_list 这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。
  • ALTER TABLE tbl_name ADD INDEX index_name (column_list) 添加普通索引,索引值可出现多次。
  • ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list)该语句指定了索引为 FULLTEXT ,用于全文索引。
  • 将页的大小设置为 4K、8K、16K,在 MySQL 中可通过如下命令查看页的大小:show variables like \'innodb_page_size\';

    而系统一个磁盘块的存储空间往往没有这么大,因此 InnoDB 每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的大小 16KB。InnoDB 在把磁盘数据读入到磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘I/O次数,提高查询效率。

    B-Tree 结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述 B-Tree,首先定义一条记录为一个二元组[key, data] ,key为记录的键值,对应表中的主键值,data 为一行记录中除主键外的数据。对于不同的记录,key值互不相同。

    一棵m阶的B-Tree有如下特性:

    1. 每个节点最多有m个孩子
    2. 除了根节点和叶子节点外,其它每个节点至少有Ceil(m/2)个孩子。
    3. 若根节点不是叶子节点,则至少有2个孩子
    4. 所有叶子节点都在同一层,且不包含其它关键字信息
    5. 每个非终端节点包含n个关键字信息(P0,P1,…Pn, k1,…kn)
    6. 关键字的个数n满足:ceil(m/2)-1 <= n <= m-1
    7. ki(i=1,…n)为关键字,且关键字升序排序
    8. Pi(i=1,…n)为指向子树根节点的指针。P(i-1)指向的子树的所有节点关键字均小于ki,但都大于k(i-1)

    B-Tree 中的每个节点根据实际情况可以包含大量的关键字信息和分支,如下图所示为一个 3 阶的 B-Tree:

    图片:DobbinSoong

    每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为17和35,P1指针指向的子树的数据范围为小于17,P2指针指向的子树的数据范围为17~35,P3指针指向的子树的数据范围为大于35。

    模拟查找关键字29的过程:

    1. 根据根节点找到磁盘块1,读入内存。【磁盘I/O操作第1次】
    2. 比较关键字29在区间(17,35),找到磁盘块1的指针P2。
    3. 根据P2指针找到磁盘块3,读入内存。【磁盘I/O操作第2次】
    4. 比较关键字29在区间(26,30),找到磁盘块3的指针P2。
    5. 根据P2指针找到磁盘块8,读入内存。【磁盘I/O操作第3次】
    6. 在磁盘块8中的关键字列表中找到关键字29。

    分析上面过程,发现需要3次磁盘I/O操作,和3次内存查找操作。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率。而3次磁盘I/O操作是影响整个B-Tree查找效率的决定因素。B-Tree相对于AVLTree缩减了节点个数,使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率。

    或 MCC),也称为多版本数据库:不用加任何锁, 通过一定机制生成一个数据请求时间点的一致性数据快照 (Snapshot), 并用这个快照来提供一定级别 (语句级或事务级) 的一致性读取。从用户的角度来看,好象是数据库可以提供同一数据的多个版本。
    最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • READ-COMMITTED(读已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
  • SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读
  • 查看当前数据库的事务隔离级别:

    show variables like \'tx_isolation\'

    下面通过事例一一阐述在事务的并发操作中可能会出现脏读,不可重复读,幻读和事务隔离级别的联系。

    数据库的事务隔离越严格,并发副作用越小,但付出的代价就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。

    事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有3.6万。这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。

    分析:重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作

    什么时候会出现幻读?

    事例:程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉,这就是幻读。

    那怎么解决幻读问题?Serializable!

     实现一致性

    undo log 主要为事务的回滚服务。在事务执行的过程中,除了记录redo log,还会记录一定量的undo log。undo log记录了数据在每个操作前的状态,如果事务执行过程中需要回滚,就可以根据undo log进行回滚操作。单个事务的回滚,只会回滚当前事务做的操作,并不会影响到其他的事务做的操作。

    Undo记录的是已部分完成并且写入硬盘的未完成的事务,默认情况下回滚日志是记录下表空间中的(共享表空间或者独享表空间)

    二种日志均可以视为一种恢复操作,redo_log是恢复提交事务修改的页操作,而undo_log是回滚行记录到特定版本。二者记录的内容也不同,redo_log是物理日志,记录页的物理修改操作,而undo_log是逻辑日志,根据每行记录进行记录。

    算法,请务必牢记:使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据

    SELECT * FROM table WHERE id BETWEN 1 AND 10 FOR UPDATE;

    即所有在(1,10)区间内的记录行都会被锁住,所有id 为 2、3、4、5、6、7、8、9 的数据行的插入会被阻塞,但是 1 和 10 两条记录行并不会被锁住。

    GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况

  • 临键锁(Next-key Locks)临键锁,是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间。(临键锁的主要目的,也是为了避免幻读(Phantom Read)。如果把事务的隔离级别降级为RC,临键锁则也会失效。)

    Next-Key 可以理解为一种特殊的间隙锁,也可以理解为一种特殊的算法。通过临建锁可以解决幻读的问题。每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。需要强调的一点是,InnoDB 中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。

    对于行的查询,都是采用该方法,主要目的是解决幻读的问题。

  • 关键字可以模拟优化器执行SQL查询语句,从而知道 MySQL 是如何处理你的 SQL 语句的。分析你的查询语句或是表结构的性能瓶颈

    能干吗:

  • 表的读取顺序
  • 数据读取操作的操作类型
  • 哪些索引可以使用
  • 哪些索引被实际使用
  • 表之间的引用
  • 每张表有多少行被优化器查询
  • 怎么玩:

  • Explain + SQL语句
  • 执行计划包含的信息(如果有分区表的话还会有partitions
  • expalin

    各字段解释

  • id(select 查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序)

  • id相同,执行顺序从上往下
  • id全不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
  • id部分相同,执行顺序是先按照数字大的先执行,然后数字相同的按照从上往下的顺序执行
  • select_type(查询类型,用于区别普通查询、联合查询、子查询等复杂查询)

  • SIMPLE :简单的select查询,查询中不包含子查询或UNION
  • PRIMARY:查询中若包含任何复杂的子部分,最外层查询被标记为PRIMARY
  • SUBQUERY:在select或where列表中包含了子查询
  • DERIVED:在from列表中包含的子查询被标记为DERIVED,MySQL会递归执行这些子查询,把结果放在临时表里
  • UNION:若第二个select出现在UNION之后,则被标记为UNION,若UNION包含在from子句的子查询中,外层select将被标记为DERIVED
  • UNION RESULT:从UNION表获取结果的select
  • table(显示这一行的数据是关于哪张表的)

  • type(显示查询使用了那种类型,从最好到最差依次排列 system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

    tip: 一般来说,得保证查询至少达到range级别,最好到达ref

  • system:表只有一行记录(等于系统表),是 const 类型的特例,平时不会出现
  • const:表示通过索引一次就找到了,const 用于比较 primary key 或 unique 索引,因为只要匹配一行数据,所以很快,如将主键置于 where 列表中,mysql 就能将该查询转换为一个常量
  • eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描
  • ref:非唯一性索引扫描,范围匹配某个单独值得所有行。本质上也是一种索引访问,他返回所有匹配某个单独值的行,然而,它可能也会找到多个符合条件的行,多以他应该属于查找和扫描的混合体
  • range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引,一般就是在你的where语句中出现了between、<、>、in等的查询,这种范围扫描索引比全表扫描要好,因为它只需开始于索引的某一点,而结束于另一点,不用扫描全部索引
  • index:Full Index Scan,index于ALL区别为index类型只遍历索引树。通常比ALL快,因为索引文件通常比数据文件小。(也就是说虽然all和index都是读全表,但index是从索引中读取的,而all是从硬盘中读的
  • ALL:Full Table Scan,将遍历全表找到匹配的行
  • possible_keys(显示可能应用在这张表中的索引,一个或多个,查询涉及到的字段若存在索引,则该索引将被列出,但不一定被查询实际使用)

  • key

  • 实际使用的索引,如果为NULL,则没有使用索引

  • 查询中若使用了覆盖索引,则该索引和查询的 select 字段重叠,仅出现在key列表中

  • explain-key
  • key_len

  • 表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好
  • key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的
  • ref(显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值)

  • rows(根据表统计信息及索引选用情况,大致估算找到所需的记录所需要读取的行数)

  • Extra(包含不适合在其他列中显示但十分重要的额外信息)

    1. 值的 SQL,则会被记录到慢查询日志中。

    2. long_query_time 的默认值为10,意思是运行10秒以上的语句
    3. 默认情况下,MySQL数据库没有开启慢查询日志,需要手动设置参数开启
    4. 查看开启状态

      SHOW VARIABLES LIKE \'%slow_query_log%\'

      开启慢查询日志

    5. 临时配置:
    6. mysql> set global slow_query_log=\'ON\';
      mysql> set global slow_query_log_file=\'/var/lib/mysql/hostname-slow.log\';
      mysql> set global long_query_time=2;

      也可set文件位置,系统会默认给一个缺省文件host_name-slow.log

      使用set操作开启慢查询日志只对当前数据库生效,如果MySQL重启则会失效。

    7. 永久配置

      修改配置文件my.cnf或my.ini,在[mysqld]一行下面加入两个配置参数

    8. [mysqld]
      slow_query_log = ON
      slow_query_log_file = /var/lib/mysql/hostname-slow.log
      long_query_time = 3

      注:log-slow-queries 参数为慢查询日志存放的位置,一般这个目录要有 MySQL 的运行帐号的可写权限,一般都将这个目录设置为 MySQL 的数据存放目录;long_query_time=2 中的 2 表示查询超过两秒才记录;在my.cnf或者 my.ini 中添加 log-queries-not-using-indexes 参数,表示记录下没有使用索引的查询。

      可以用 select sleep(4) 验证是否成功开启。

      在生产环境中,如果手工分析日志,查找、分析SQL,还是比较费劲的,所以MySQL提供了日志分析工具mysqldumpslow

      通过 mysqldumpslow --help 查看操作帮助信息

    9. 得到返回记录集最多的10个SQL

      mysqldumpslow -s r -t 10 /var/lib/mysql/hostname-slow.log

    10. 得到访问次数最多的10个SQL

      mysqldumpslow -s c -t 10 /var/lib/mysql/hostname-slow.log

    11. 得到按照时间排序的前10条里面含有左连接的查询语句

      mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/hostname-slow.log

    12. 也可以和管道配合使用

      mysqldumpslow -s r -t 10 /var/lib/mysql/hostname-slow.log | more

    13. 也可使用 pt-query-digest 分析 RDS MySQL 慢查询日志

      参数的设置,增大sort_buffer_size参数的设置
    14. where高于having,能写在where限定的条件就不要去having限定了
    15. 数据类型优化

      MySQL 支持的数据类型非常多,选择正确的数据类型对于获取高性能至关重要。不管存储哪种类型的数据,下面几个简单的原则都有助于做出更好的选择。

    16. 更小的通常更好:一般情况下,应该尽量使用可以正确存储数据的最小数据类型。

      简单就好:简单的数据类型通常需要更少的CPU周期。例如,整数比字符操作代价更低,因为字符集和校对规则(排序规则)使字符比较比整型比较复杂。

    17. 尽量避免NULL:通常情况下最好指定列为NOT NULL


    18. 九、分区、分表、分库MySQL分区

      一般情况下我们创建的表对应一组存储文件,使用MyISAM存储引擎时是一个.MYI.MYD文件,使用Innodb存储引擎时是一个.ibd.frm(表结构)文件。

      当数据量较大时(一般千万条记录级别以上),MySQL的性能就会开始下降,这时我们就需要将数据分散到多组存储文件,保证其单个文件的执行效率

      能干嘛

    19. 逻辑数据分割
    20. 提高单一的写和读应用速度
    21. 提高分区范围读查询的速度
    22. 分割数据能够有多个不同的物理文件路径
    23. 高效的保存历史数据
    24. 怎么玩

      首先查看当前数据库是否支持分区

    25. MySQL5.6以及之前版本:

      SHOW VARIABLES LIKE \'%partition%\';
    26. MySQL5.6:

      show plugins;
    27. 分区类型及操作

    28. RANGE分区:基于属于一个给定连续区间的列值,把多行分配给分区。mysql将会根据指定的拆分策略,,把数据放在不同的表文件上。相当于在文件上,被拆成了小块.但是,对外给客户的感觉还是一张表,透明的。

      按照 range 来分,就是每个库一段连续的数据,这个一般是按比如时间范围来的,比如交易表啊,销售表啊等,可以根据年月来存放数据。可能会产生热点问题,大量的流量都打在最新的数据上了。

      range 来分,好处在于说,扩容的时候很简单。

    29. LIST分区:类似于按RANGE分区,每个分区必须明确定义。它们的主要区别在于,LIST分区中每个分区的定义和选择是基于某列的值从属于一个值列表集中的一个值,而RANGE分区是从属于一个连续区间值的集合。

    30. HASH分区:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL 中有效的、产生非负整数值的任何表达式。

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

    31. KEY分区:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值。

    32. 看上去分区表很帅气,为什么大部分互联网还是更多的选择自己分库分表来水平扩展咧?

    33. 分区表,分区键设计不太灵活,如果不走分区键,很容易出现全表锁
    34. 一旦数据并发量上来,如果在分区表实施关联,就是一个灾难
    35. 自己分库分表,自己掌控业务场景与访问模式,可控。分区表,研发写了一个sql,都不确定mysql是怎么玩的,不太可控
    36. 随着业务的发展,业务越来越复杂,应用的模块越来越多,总的数据量很大,高并发读写操作均超过单个数据库服务器的处理能力怎么办?

      这个时候就出现了数据分片,数据分片指按照某个维度将存放在单一数据库中的数据分散地存放至多个数据库或表中。数据分片的有效手段就是对关系型数据库进行分库和分表。

      区别于分区的是,分区一般都是放在单机里的,用的比较多的是时间范围分区,方便归档。只不过分库分表需要代码实现,分区则是mysql内部实现。分库分表和分区并不冲突,可以结合使用。

      说说分库与分表的设计

      MySQL分表

      分表有两种分割方式,一种垂直拆分,另一种水平拆分。

    37. 垂直拆分

      垂直分表,通常是按照业务功能的使用频次,把主要的、热门的字段放在一起做为主要表。然后把不常用的,按照各自的业务属性进行聚集,拆分到不同的次要表中;主要表和次要表的关系一般都是一对一的。

    38. 水平拆分(数据分片)

      单表的容量不超过500W,否则建议水平拆分。是把一个表复制成同样表结构的不同表,然后把数据按照一定的规则划分,分别存储到这些表中,从而保证单表的容量不会太大,提升性能;当然这些结构一样的表,可以放在一个或多个数据库中。

      水平分割的几种方法:

    39. 使用MD5哈希,做法是对UID进行md5加密,然后取前几位(我们这里取前两位),然后就可以将不同的UID哈希到不同的用户表(user_xx)中了。
    40. 还可根据时间放入不同的表,比如:article_201601,article_201602。
    41. 按热度拆分,高点击率的词条生成各自的一张表,低热度的词条都放在一张大表里,待低热度的词条达到一定的贴数后,再把低热度的表单独拆分成一张表。
    42. 根据ID的值放入对应的表,第一个表user_0000,第二个100万的用户数据放在第二 个表user_0001中,随用户增加,直接添加用户表就行了。
    43. MySQL分库

      为什么要分库?

      数据库集群环境后都是多台 slave,基本满足了读取操作;  但是写入或者说大数据、频繁的写入操作对master性能影响就比较大,这个时候,单库并不能解决大规模并发写入的问题,所以就会考虑分库。

      分库是什么?

      一个库里表太多了,导致了海量数据,系统性能下降,把原本存储于一个库的表拆分存储到多个库上, 通常是将表按照功能模块、关系密切程度划分出来,部署到不同库上。

      优点:

    44. 减少增量数据写入时的锁对查询的影响

    45. 由于单表数量下降,常见的查询操作由于减少了需要扫描的记录,使得单表单次查询所需的检索行数变少,减少了磁盘IO,时延变短

    46. 但是它无法解决单表数据量太大的问题

      分库分表后的难题

      分布式事务的问题,数据的完整性和一致性问题。

      数据操作维度问题:用户、交易、订单各个不同的维度,用户查询维度、产品数据分析维度的不同对比分析角度。跨库联合查询的问题,可能需要两次查询 跨节点的count、order by、group by以及聚合函数问题,可能需要分别在各个节点上得到结果后在应用程序端进行合并 额外的数据管理负担,如:访问数据表的导航定位 额外的数据运算压力,如:需要在多个节点执行,然后再合并计算程序编码开发难度提升,没有太好的框架解决,更多依赖业务看如何分,如何合,是个难题。

      配主从,正经公司的话,也不会让 Javaer 去搞的,但还是要知道

      十、主从复制复制的基本原理
    47. slave 会从 master 读取 binlog 来进行数据同步

    48. 三个步骤

      img
      1. master将改变记录到二进制日志(binary log)。这些记录过程叫做二进制日志事件,binary log events;
      2. salve 将 master 的 binary log events 拷贝到它的中继日志(relay log);
      3. slave 重做中继日志中的事件,将改变应用到自己的数据库中。MySQL 复制是异步且是串行化的。
      复制的基本原则
    49. 每个 slave只有一个 master
    50. 每个 salve只能有一个唯一的服务器 ID
    51. 每个master可以有多个salve
    52. 复制的最大问题
    53. 延时

    54. 十一、其他问题说一说三个范式
    55. 第一范式(1NF):数据库表中的字段都是单一属性的,不可再分。这个单一属性由基本类型构成,包括整型、实数、字符型、逻辑型、日期型等。
    56. 第二范式(2NF):数据库表中不存在非关键字段对任一候选关键字段的部分函数依赖(部分函数依赖指的是存在组合关键字中的某些字段决定非关键字段的情况),也即所有非关键字段都完全依赖于任意一组候选关键字。
    57. 第三范式(3NF):在第二范式的基础上,数据表中如果不存在非关键字段对任一候选关键字段的传递函数依赖则符合第三范式。所谓传递函数依赖,指的是如 果存在"A → B → C"的决定关系,则C传递函数依赖于A。因此,满足第三范式的数据库表应该不存在如下依赖关系:关键字段 → 非关键字段 x → 非关键字段y
    58. 百万级别或以上的数据如何删除

      关于索引:由于索引需要额外的维护成本,因为索引文件是单独存在的文件,所以当我们对数据的增加,修改,删除,都会产生额外的对索引文件的操作,这些操作需要消耗额外的IO,会降低增/改/删的执行效率。所以,在我们删除数据库百万级别数据的时候,查询MySQL官方手册得知删除数据的速度和创建的索引数量是成正比的。

      1. 所以我们想要删除百万数据的时候可以先删除索引(此时大概耗时三分多钟)
      2. 然后删除其中无用数据(此过程需要不到两分钟)
      3. 删除完成后重新创建索引(此时数据较少了)创建索引也非常快,约十分钟左右。
      4. 与之前的直接删除绝对是要快速很多,更别说万一删除中断,一切删除会回滚。那更是坑了。

      参考与感谢:

      https://zhuanlan.zhihu.com/p/29150809 https://juejin.im/post/5e3eb616f265da570d734dcb#heading-105 https://blog.csdn.net/yin767833376/article/details/81511377

    「直击面试」- 搞定计算机网络,这些问题还没有我答不出来的


    「我是大厂面试官」 Java 集合,你肯定也会被问到这些


    MySQL 三万字精华总结,和面试官扯皮绰绰有余(收藏系列)

    写在之前:不建议那种上来就是各种面试题罗列,然后背书式的去记忆,对技术的提升帮助很小,对正经面试也没什么帮助,有点东西的面试官深挖下就懵逼了。

    个人建议把面试题看作是费曼学习法中的回顾、简化的环节,准备面试的时候,跟着题目先自己讲给自己听,看看自己会满意吗,不满意就继续学习这个点,如此反复,好的offer离你不远的,奥利给

    文章收录在 GitHub JavaKeeper ,N线互联网开发必备技能兵器谱,笔记自取。

    一、MySQL架构

    和其它数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。

    画出 MySQL 架构图,这种变态问题都能问的出来

    MySQL 的查询流程具体是?or 一条SQL语句在MySQL中如何执行的?

    客户端请求 —> 连接器(验证用户身份,给予权限) —> 查询缓存(存在缓存则直接返回,不存在则执行后续操作) —> 分析器(对SQL进行词法分析和语法分析操作) —> 优化器(主要对执行的sql优化选择最优的执行方案方法) —> 执行器(执行时会先看用户是否有执行权限,有才去使用这个引擎提供的接口) —> 去引擎层获取数据返回(如果开启查询缓存则会缓存查询结果)


    说说MySQL有哪些存储引擎?都有哪些区别?

    二、存储引擎

    存储引擎是MySQL的组件,用于处理不同表类型的SQL操作。不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能,使用不同的存储引擎,还可以获得特定的功能。

    使用哪一种引擎可以灵活选择,一个数据库中多个表可以使用不同引擎以满足各种性能和实际需求,使用合适的存储引擎,将会提高整个数据库的性能 。

    MySQL服务器使用可插拔的存储引擎体系结构,可以从运行中的 MySQL 服务器加载或卸载存储引擎 。

    查看存储引擎

    -- 查看支持的存储引擎
    SHOW ENGINES
    
    -- 查看默认存储引擎
    SHOW VARIABLES LIKE 'storage_engine'
    
    --查看具体某一个表所使用的存储引擎,这个默认存储引擎被修改了!
    show create table tablename
    
    --准确查看某个数据库中的某一表所使用的存储引擎
    show table status like 'tablename'
    show table status from database where name="tablename"
    

    设置存储引擎

    -- 建表时指定存储引擎。默认的就是INNODB,不需要设置
    CREATE TABLE t1 (i INT) ENGINE = INNODB;
    CREATE TABLE t2 (i INT) ENGINE = CSV;
    CREATE TABLE t3 (i INT) ENGINE = MEMORY;
    
    -- 修改存储引擎
    ALTER TABLE t ENGINE = InnoDB;
    
    -- 修改默认存储引擎,也可以在配置文件my.cnf中修改默认引擎
    SET default_storage_engine=NDBCLUSTER;
    

    默认情况下,每当 CREATE TABLEALTER TABLE 不能使用默认存储引擎时,都会生成一个警告。为了防止在所需的引擎不可用时出现令人困惑的意外行为,可以启用 NO_ENGINE_SUBSTITUTION SQL 模式。如果所需的引擎不可用,则此设置将产生错误而不是警告,并且不会创建或更改表

    存储引擎对比

    常见的存储引擎就 InnoDB、MyISAM、Memory、NDB。

    InnoDB 现在是 MySQL 默认的存储引擎,支持事务、行级锁定和外键

    文件存储结构对比

    在 MySQL中建立任何一张数据表,在其数据目录对应的数据库目录下都有对应表的 .frm 文件,.frm 文件是用来保存每个数据表的元数据(meta)信息,包括表结构的定义等,与数据库存储引擎无关,也就是任何存储引擎的数据表都必须有.frm文件,命名方式为 数据表名.frm,如user.frm。

    查看MySQL 数据保存在哪里:show variables like 'data%'

    MyISAM 物理文件结构为:

    InnoDB 物理文件结构为:

    ps:正经公司,这些都有专业运维去做,数据备份、恢复啥的,让我一个 Javaer 搞这的话,加钱不?

    面试这么回答

    1. InnoDB 支持事务,MyISAM 不支持事务。这是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一;
    2. InnoDB 支持外键,而 MyISAM 不支持。对一个包含外键的 InnoDB 表转为 MYISAM 会失败;
    3. InnoDB 是聚簇索引,MyISAM 是非聚簇索引。聚簇索引的文件存放在主键索引的叶子节点上,因此 InnoDB 必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。而 MyISAM 是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
    4. InnoDB 不保存表的具体行数,执行select count(*) from table 时需要全表扫描。而 MyISAM 用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快;
    5. InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁。一个更新语句会锁住整张表,导致其他查询和更新都会被阻塞,因此并发访问受限。这也是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一;
    对比项MyISAMInnoDB
    主外键不支持支持
    事务不支持支持
    行表锁表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作行锁,操作时只锁某一行,不对其它行有影响,适合高并发的操作
    缓存只缓存索引,不缓存真实数据不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响
    表空间
    关注点性能事务
    默认安装

    一张表,里面有ID自增主键,当insert了17条记录之后,删除了第15,16,17条记录,再把Mysql重启,再insert一条记录,这条记录的ID是18还是15 ?

    如果表的类型是MyISAM,那么是18。因为MyISAM表会把自增主键的最大ID 记录到数据文件中,重启MySQL自增主键的最大ID也不会丢失;

    如果表的类型是InnoDB,那么是15。因为InnoDB 表只是把自增主键的最大ID记录到内存中,所以重启数据库或对表进行OPTION操作,都会导致最大ID丢失。

    哪个存储引擎执行 select count(*) 更快,为什么?

    MyISAM更快,因为MyISAM内部维护了一个计数器,可以直接调取。

    InnoDB 中 count(*) 语句是在执行的时候,全表扫描统计总数量,所以当数据越来越大时,语句就越来越耗时了,为什么 InnoDB 引擎不像 MyISAM 引擎一样,将总行数存储到磁盘上?这跟 InnoDB 的事务特性有关,由于多版本并发控制(MVCC)的原因,InnoDB 表“应该返回多少行”也是不确定的。

    三、数据类型

    主要包括以下五大类:

    CHAR 和 VARCHAR 的区别?

    char是固定长度,varchar长度可变:

    char(n) 和 varchar(n) 中括号中 n 代表字符的个数,并不代表字节个数,比如 CHAR(30) 就可以存储 30 个字符。

    存储时,前者不管实际存储数据的长度,直接按 char 规定的长度分配存储空间;而后者会根据实际存储的数据分配最终的存储空间

    相同点:

    1. char(n),varchar(n)中的n都代表字符的个数
    2. 超过char,varchar最大长度n的限制后,字符串会被截断。

    不同点:

    1. char不论实际存储的字符数都会占用n个字符的空间,而varchar只会占用实际字符应该占用的字节空间加1(实际长度length,0<=length<255)或加2(length>255)。因为varchar保存数据时除了要保存字符串之外还会加一个字节来记录长度(如果列声明长度大于255则使用两个字节来保存长度)。
    2. 能存储的最大空间限制不一样:char的存储上限为255字节。
    3. char在存储时会截断尾部的空格,而varchar不会。

    char是适合存储很短的、一般固定长度的字符串。例如,char非常适合存储密码的MD5值,因为这是一个定长的值。对于非常短的列,char比varchar在存储空间上也更有效率。

    列的字符串类型可以是什么?

    字符串类型是:SET、BLOB、ENUM、CHAR、CHAR、TEXT、VARCHAR

    BLOB和TEXT有什么区别?

    BLOB是一个二进制对象,可以容纳可变数量的数据。有四种类型的BLOB:TINYBLOB、BLOB、MEDIUMBLO和 LONGBLOB

    TEXT是一个不区分大小写的BLOB。四种TEXT类型:TINYTEXT、TEXT、MEDIUMTEXT 和 LONGTEXT。

    BLOB 保存二进制数据,TEXT 保存字符数据。


    四、索引

    说说你对 MySQL 索引的理解?

    数据库索引的原理,为什么要用 B+树,为什么不用二叉树?

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

    InnoDB引擎中的索引策略,了解过吗?

    创建索引的方式有哪些?

    聚簇索引/非聚簇索引,mysql索引底层实现,为什么不用B-tree,为什么不用hash,叶子结点存放的是数据还是指向数据的内存地址,使用索引需要注意的几个地方?

    基本语法:

    优势

    劣势

    MySQL索引分类

    数据结构角度

    从物理存储角度

    从逻辑角度

    为什么MySQL 索引中用B+tree,不用B-tree 或者其他树,为什么不用 Hash 索引

    聚簇索引/非聚簇索引,MySQL 索引底层实现,叶子结点存放的是数据还是指向数据的内存地址,使用索引需要注意的几个地方?

    使用索引查询一定能提高查询的性能吗?为什么?

    MySQL索引结构

    首先要明白索引(index)是在存储引擎(storage engine)层面实现的,而不是server层面。不是所有的存储引擎都支持所有的索引类型。即使多个存储引擎支持某一索引类型,它们的实现和行为也可能有所差别。

    B+Tree索引

    MyISAM 和 InnoDB 存储引擎,都使用 B+Tree的数据结构,它相对与 B-Tree结构,所有的数据都存放在叶子节点上,且把叶子节点通过指针连接到一起,形成了一条数据链表,以加快相邻数据的检索效率。

    先了解下 B-Tree 和 B+Tree 的区别

    B-Tree

    B-Tree是为磁盘等外存储设备设计的一种平衡查找树。

    系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。

    InnoDB 存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。InnoDB 存储引擎中默认每个页的大小为16KB,可通过参数 innodb_page_size 将页的大小设置为 4K、8K、16K,在 MySQL 中可通过如下命令查看页的大小:show variables like 'innodb_page_size';

    而系统一个磁盘块的存储空间往往没有这么大,因此 InnoDB 每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的大小 16KB。InnoDB 在把磁盘数据读入到磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘I/O次数,提高查询效率。

    B-Tree 结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述 B-Tree,首先定义一条记录为一个二元组[key, data] ,key为记录的键值,对应表中的主键值,data 为一行记录中除主键外的数据。对于不同的记录,key值互不相同。

    一棵m阶的B-Tree有如下特性:

    1. 每个节点最多有m个孩子
    2. 除了根节点和叶子节点外,其它每个节点至少有Ceil(m/2)个孩子。
    3. 若根节点不是叶子节点,则至少有2个孩子
    4. 所有叶子节点都在同一层,且不包含其它关键字信息
    5. 每个非终端节点包含n个关键字信息(P0,P1,…Pn, k1,…kn)
    6. 关键字的个数n满足:ceil(m/2)-1 <= n <= m-1
    7. ki(i=1,…n)为关键字,且关键字升序排序
    8. Pi(i=1,…n)为指向子树根节点的指针。P(i-1)指向的子树的所有节点关键字均小于ki,但都大于k(i-1)

    B-Tree 中的每个节点根据实际情况可以包含大量的关键字信息和分支,如下图所示为一个 3 阶的 B-Tree:

    每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为17和35,P1指针指向的子树的数据范围为小于17,P2指针指向的子树的数据范围为17~35,P3指针指向的子树的数据范围为大于35。

    模拟查找关键字29的过程:

    1. 根据根节点找到磁盘块1,读入内存。【磁盘I/O操作第1次】
    2. 比较关键字29在区间(17,35),找到磁盘块1的指针P2。
    3. 根据P2指针找到磁盘块3,读入内存。【磁盘I/O操作第2次】
    4. 比较关键字29在区间(26,30),找到磁盘块3的指针P2。
    5. 根据P2指针找到磁盘块8,读入内存。【磁盘I/O操作第3次】
    6. 在磁盘块8中的关键字列表中找到关键字29。

    分析上面过程,发现需要3次磁盘I/O操作,和3次内存查找操作。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率。而3次磁盘I/O操作是影响整个B-Tree查找效率的决定因素。B-Tree相对于AVLTree缩减了节点个数,使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率。

    B+Tree

    B+Tree 是在 B-Tree 基础上的一种优化,使其更适合实现外存储索引结构,InnoDB 存储引擎就是用 B+Tree 实现其索引结构。

    从上一节中的B-Tree结构图中可以看到每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度。

    B+Tree相对于B-Tree有几点不同:

    1. 非叶子节点只存储键值信息;
    2. 所有叶子节点之间都有一个链指针;
    3. 数据记录都存放在叶子节点中

    将上一节中的B-Tree优化,由于B+Tree的非叶子节点只存储键值信息,假设每个磁盘块能存储4个键值及指针信息,则变成B+Tree后其结构如下图所示:

    通常在B+Tree上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,而且所有叶子节点(即数据节点)之间是一种链式环结构。因此可以对B+Tree进行两种查找运算:一种是对于主键的范围查找和分页查找,另一种是从根节点开始,进行随机查找。

    可能上面例子中只有22条数据记录,看不出B+Tree的优点,下面做一个推算:

    InnoDB存储引擎中页的大小为16KB,一般表的主键类型为INT(占用4个字节)或BIGINT(占用8个字节),指针类型也一般为4或8个字节,也就是说一个页(B+Tree中的一个节点)中大概存储16KB/(8B+8B)=1K个键值(因为是估值,为方便计算,这里的K取值为103)。也就是说一个深度为3的B+Tree索引可以维护103 * 10^3 * 10^3 = 10亿 条记录。

    实际情况中每个节点可能不能填充满,因此在数据库中,B+Tree的高度一般都在2-4层。MySQL的InnoDB存储引擎在设计时是将根节点常驻内存的,也就是说查找某一键值的行记录时最多只需要1~3次磁盘I/O操作。

    B+Tree性质

    1. 通过上面的分析,我们知道IO次数取决于b+数的高度h,假设当前数据表的数据为N,每个磁盘块的数据项的数量是m,则有h=㏒(m+1)N,当数据量N一定的情况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比如int占4字节,要比bigint8字节少一半。这也是为什么b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下降,导致树增高。当数据项等于1时将会退化成线性表。
    2. 当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性
    MyISAM主键索引与辅助索引的结构

    MyISAM引擎的索引文件和数据文件是分离的。MyISAM引擎索引结构的叶子节点的数据域,存放的并不是实际的数据记录,而是数据记录的地址。索引文件与数据文件分离,这样的索引称为"非聚簇索引"。MyISAM的主索引与辅助索引区别并不大,只是主键索引不能有重复的关键字。

    在MyISAM中,索引(含叶子节点)存放在单独的.myi文件中,叶子节点存放的是数据的物理地址偏移量(通过偏移量访问就是随机访问,速度很快)。

    主索引是指主键索引,键值不可能重复;辅助索引则是普通索引,键值可能重复。

    通过索引查找数据的流程:先从索引文件中查找到索引节点,从中拿到数据的文件指针,再到数据文件中通过文件指针定位了具体的数据。辅助索引类似。

    InnoDB主键索引与辅助索引的结构

    InnoDB引擎索引结构的叶子节点的数据域,存放的就是实际的数据记录(对于主索引,此处会存放表中所有的数据记录;对于辅助索引此处会引用主键,检索的时候通过主键到主键索引中找到对应数据行),或者说,InnoDB的数据文件本身就是主键索引文件,这样的索引被称为"“聚簇索引”,一个表只能有一个聚簇索引。

    主键索引:

    我们知道InnoDB索引是聚集索引,它的索引和数据是存入同一个.idb文件中的,因此它的索引结构是在同一个树节点中同时存放索引和数据,如下图中最底层的叶子节点有三行数据,对应于数据表中的id、stu_id、name数据项。

    在Innodb中,索引分叶子节点和非叶子节点,非叶子节点就像新华字典的目录,单独存放在索引段中,叶子节点则是顺序排列的,在数据段中。Innodb的数据文件可以按照表来切分(只需要开启innodb_file_per_table),切分后存放在xxx.ibd中,默认不切分,存放在xxx.ibdata中。

    辅助(非主键)索引:

    这次我们以示例中学生表中的name列建立辅助索引,它的索引结构跟主键索引的结构有很大差别,在最底层的叶子结点有两行数据,第一行的字符串是辅助索引,按照ASCII码进行排序,第二行的整数是主键的值。

    这就意味着,对name列进行条件搜索,需要两个步骤:

    ① 在辅助索引上检索name,到达其叶子节点获取对应的主键;

    ② 使用主键在主索引上再进行对应的检索操作

    这也就是所谓的“回表查询

    InnoDB 索引结构需要注意的点

    1. 数据文件本身就是索引文件

    2. 表数据文件本身就是按 B+Tree 组织的一个索引结构文件

    3. 聚集索引中叶节点包含了完整的数据记录

    4. InnoDB 表必须要有主键,并且推荐使用整型自增主键

    正如我们上面介绍 InnoDB 存储结构,索引与数据是共同存储的,不管是主键索引还是辅助索引,在查找时都是通过先查找到索引节点才能拿到相对应的数据,如果我们在设计表结构时没有显式指定索引列的话,MySQL 会从表中选择数据不重复的列建立索引,如果没有符合的列,则 MySQL 自动为 InnoDB 表生成一个隐含字段作为主键,并且这个字段长度为6个字节,类型为整型。

    那为什么推荐使用整型自增主键而不是选择UUID?

    为什么非主键索引结构叶子节点存储的是主键值?

    保证数据一致性和节省存储空间,可以这么理解:商城系统订单表会存储一个用户ID作为关联外键,而不推荐存储完整的用户信息,因为当我们用户表中的信息(真实名称、手机号、收货地址···)修改后,不需要再次维护订单表的用户数据,同时也节省了存储空间。

    Hash索引

    full-text全文索引

    R-Tree空间索引

    空间索引是MyISAM的一种特殊索引类型,主要用于地理空间数据类型

    为什么Mysql索引要用B+树不是B树?

    用B+树不用B树考虑的是IO对性能的影响,B树的每个节点都存储数据,而B+树只有叶子节点才存储数据,所以查找相同数据量的情况下,B树的高度更高,IO更频繁。数据库索引是存储在磁盘上的,当数据量大时,就不能把整个索引全部加载到内存了,只能逐一加载每一个磁盘页(对应索引树的节点)。其中在MySQL底层对B+树进行进一步优化:在叶子节点中是双向链表,且在链表的头结点和尾节点也是循环指向的。

    面试官:为何不采用Hash方式?

    因为Hash索引底层是哈希表,哈希表是一种以key-value存储数据的结构,所以多个数据在存储关系上是完全没有任何顺序关系的,所以,对于区间查询是无法直接通过索引查询的,就需要全表扫描。所以,哈希索引只适用于等值查询的场景。而B+ Tree是一种多路平衡查询树,所以他的节点是天然有序的(左子节点小于父节点、父节点小于右子节点),所以对于范围查询的时候不需要做全表扫描。

    哈希索引不支持多列联合索引的最左匹配规则,如果有大量重复键值得情况下,哈希索引的效率会很低,因为存在哈希碰撞问题。

    哪些情况需要创建索引

    1. 主键自动建立唯一索引

    2. 频繁作为查询条件的字段

    3. 查询中与其他表关联的字段,外键关系建立索引

    4. 单键/组合索引的选择问题,高并发下倾向创建组合索引

    5. 查询中排序的字段,排序字段通过索引访问大幅提高排序速度

    6. 查询中统计或分组字段

    哪些情况不要创建索引

    1. 表记录太少
    2. 经常增删改的表
    3. 数据重复且分布均匀的表字段,只应该为最经常查询和最经常排序的数据列建立索引(如果某个数据类包含太多的重复数据,建立索引没有太大意义)
    4. 频繁更新的字段不适合创建索引(会加重IO负担)
    5. where条件里用不到的字段不创建索引

    MySQL高效索引

    覆盖索引(Covering Index),或者叫索引覆盖, 也就是平时所说的不需要回表操作

    五、MySQL查询

    count(*) 和 count(1)和count(列名)区别 ps:这道题说法有点多

    执行效果上:

    执行效率上:

    MySQL中 in和 exists 的区别?

    SELECT * FROM A WHERE A.id IN (SELECT id FROM B);
    SELECT * FROM A WHERE EXISTS (SELECT * from B WHERE B.id = A.id);
    

    如果查询的两个表大小相当,那么用in和exists差别不大

    如果两个表中一个较小,一个是大表,则子查询表大的用exists,子查询表小的用in:

    UNION和UNION ALL的区别?

    UNION和UNION ALL都是将两个结果集合并为一个,两个要联合的SQL语句 字段个数必须一样,而且字段类型要“相容”(一致);

    SQL执行顺序

    mysql 的内连接、左连接、右连接有什么区别?

    什么是内连接、外连接、交叉连接、笛卡尔积呢?

    Join图


    六、MySQL 事务

    事务的隔离级别有哪些?MySQL的默认隔离级别是什么?

    什么是幻读,脏读,不可重复读呢?

    MySQL事务的四大特性以及实现原理

    MVCC熟悉吗,它的底层原理?

    MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你即需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务!

    ACID — 事务基本要素

    事务是由一组SQL语句组成的逻辑处理单元,具有4个属性,通常简称为事务的ACID属性。

    并发事务处理带来的问题

    幻读和不可重复读的区别:

    并发事务处理带来的问题的解决办法:

    事务隔离级别

    数据库事务的隔离级别有4种,由低到高分别为

    查看当前数据库的事务隔离级别:

    show variables like 'tx_isolation'
    

    下面通过事例一一阐述在事务的并发操作中可能会出现脏读,不可重复读,幻读和事务隔离级别的联系。

    数据库的事务隔离越严格,并发副作用越小,但付出的代价就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。

    Read uncommitted

    读未提交,就是一个事务可以读取另一个未提交事务的数据。

    事例:老板要给程序员发工资,程序员的工资是3.6万/月。但是发工资时老板不小心按错了数字,按成3.9万/月,该钱已经打到程序员的户口,但是事务还没有提交,就在这时,程序员去查看自己这个月的工资,发现比往常多了3千元,以为涨工资了非常高兴。但是老板及时发现了不对,马上回滚差点就提交了的事务,将数字改成3.6万再提交。

    分析:实际程序员这个月的工资还是3.6万,但是程序员看到的是3.9万。他看到的是老板还没提交事务时的数据。这就是脏读。

    那怎么解决脏读呢?Read committed!读提交,能解决脏读问题。

    Read committed

    读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。

    事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候!!程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。程序员就会很郁闷,明明卡里是有钱的…

    分析:这就是读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读

    那怎么解决可能的不可重复读问题?Repeatable read !

    Repeatable read

    重复读,就是在开始读取数据(事务开启)时,不再允许修改操作。 MySQL的默认事务隔离级别

    事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有3.6万。这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。

    分析:重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作

    什么时候会出现幻读?

    事例:程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉,这就是幻读。

    那怎么解决幻读问题?Serializable!

    Serializable 序列化

    Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。简单来说,Serializable会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用问题。这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。

    比较

    事务隔离级别读数据一致性脏读不可重复读幻读
    读未提交(read-uncommitted)最低级被,只能保证不读取物理上损坏的数据
    读已提交(read-committed)语句级
    可重复读(repeatable-read)事务级
    串行化(serializable)最高级别,事务级

    需要说明的是,事务隔离级别和数据访问的并发性是对立的,事务隔离级别越高并发性就越差。所以要根据具体的应用来确定合适的事务隔离级别,这个地方没有万能的原则。

    MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)。我们可以通过SELECT @@tx_isolation;命令来查看,MySQL 8.0 该命令改为SELECT @@transaction_isolation;

    这里需要注意的是:与 SQL 标准不同的地方在于InnoDB 存储引擎在 **REPEATABLE-READ(可重读)**事务隔离级别下使用的是Next-Key Lock 算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)已经可以完全保证事务的隔离性要求,即达到了 SQL标准的 **SERIALIZABLE(可串行化)**隔离级别,而且保留了比较好的并发性能。

    因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读已提交):,但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)**并不会有任何性能损失。

    MVCC 多版本并发控制

    MySQL的大多数事务型存储引擎实现都不是简单的行级锁。基于提升并发性考虑,一般都同时实现了多版本并发控制(MVCC),包括Oracle、PostgreSQL。只是实现机制各不相同。

    可以认为 MVCC 是行级锁的一个变种,但它在很多情况下避免了加锁操作,因此开销更低。虽然实现机制有所不同,但大都实现了非阻塞的读操作,写操作也只是锁定必要的行。

    MVCC 的实现是通过保存数据在某个时间点的快照来实现的。也就是说不管需要执行多长时间,每个事物看到的数据都是一致的。

    典型的MVCC实现方式,分为乐观(optimistic)并发控制和悲观(pressimistic)并发控制。下边通过 InnoDB的简化版行为来说明 MVCC 是如何工作的。

    InnoDB 的 MVCC,是通过在每行记录后面保存两个隐藏的列来实现。这两个列,一个保存了行的创建时间,一个保存行的过期时间(删除时间)。当然存储的并不是真实的时间,而是系统版本号(system version number)。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。

    REPEATABLE READ(可重读)隔离级别下MVCC如何工作:

    保存这两个额外系统版本号,使大多数操作都不用加锁。使数据操作简单,性能很好,并且也能保证只会读取到符合要求的行。不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作和一些额外的维护工作。

    MVCC 只在 COMMITTED READ(读提交)和REPEATABLE READ(可重复读)两种隔离级别下工作。

    事务日志

    InnoDB 使用日志来减少提交事务时的开销。因为日志中已经记录了事务,就无须在每个事务提交时把缓冲池的脏块刷新(flush)到磁盘中。

    事务修改的数据和索引通常会映射到表空间的随机位置,所以刷新这些变更到磁盘需要很多随机 IO。

    InnoDB 假设使用常规磁盘,随机IO比顺序IO昂贵得多,因为一个IO请求需要时间把磁头移到正确的位置,然后等待磁盘上读出需要的部分,再转到开始位置。

    InnoDB 用日志把随机IO变成顺序IO。一旦日志安全写到磁盘,事务就持久化了,即使断电了,InnoDB可以重放日志并且恢复已经提交的事务。

    InnoDB 使用一个后台线程智能地刷新这些变更到数据文件。这个线程可以批量组合写入,使得数据写入更顺序,以提高效率。

    事务

    以上是关于MySQL 三万字精华总结 + 面试100 问,吊打面试官绰绰有余(收藏系列)的主要内容,如果未能解决你的问题,请参考以下文章

    MySQL 三万字精华总结,和面试官扯皮绰绰有余(收藏系列)

    MySQL 三万字精华总结,和面试官扯皮绰绰有余(收藏系列)

    ❤️三万字《C语言面试错题100例》❤️(建议收藏)

    三万字91道MySQL面试题。 附PDF

    三万字,100题!Linux知识汇总!

    人类高质量Java基础面试题大全,又是一篇三万字的总结!