Java面试准备之MySQL系列一

hresh 468 0

Java面试准备之MySQL系列一

存储引擎

Windows 打开 MySQL 命令行,直接搜索程序,如下图所示:

Java面试准备之MySQL系列一

打开之后输入密码即可。

或者进入 MySQL 安装目录,在 bin 目录下打开命令行窗口,输入 mysql -u 用户名 -p,如下图所示:

Java面试准备之MySQL系列一

查看MySQL提供的所有存储引擎

mysql> show engines;

Java面试准备之MySQL系列一

从上图我们可以查看出 MySQL 当前默认的存储引擎是 InnoDB,并且在5.7版本所有的存储引擎中只有 InnoDB 是事务性存储引擎,也就是说只有 InnoDB 支持事务。

查看MySQL当前默认的存储引擎

我们也可以通过下面的命令查看默认的存储引擎。

mysql> show variables like '%storage_engine%';

查看表的存储引擎

show table status like "table_name" ;

Java面试准备之MySQL系列一

MyISAM和InnoDB区别

MyISAM 是 MySQL 的默认数据库引擎(5.5版之前)。虽然性能极佳,而且提供了大量的特性,包括全文索引、压缩、空间函数等,但 MyISAM 不支持事务和行级锁,而且最大的缺陷就是崩溃后无法安全恢复。不过,5.5版本之后,MySQL 引入了 InnoDB(事务性数据库引擎),MySQL 5.5版本后默认的存储引擎为 InnoDB。

大多数时候我们使用的都是 InnoDB 存储引擎,但是在某些情况下使用 MyISAM 也是合适的比如读密集的情况下。(如果你不介意 MyISAM 崩溃恢复问题的话)。

区别:

  1. 是否支持行级锁 : MyISAM 只有表级锁(table-level locking),而InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。
  2. 是否支持事务和崩溃后的安全恢复: MyISAM 强调的是性能,每次查询具有原子性,其执行速度比 InnoDB 类型更快,但是不提供事务支持。但是InnoDB 提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。
  3. 是否支持外键: MyISAM 不支持,而 InnoDB 支持。
  4. 是否支持MVCC :仅 InnoDB 支持。应对高并发事务, MVCC 比单纯的加锁更高效;MVCC只在 READ COMMITTEDREPEATABLE READ 两个隔离级别下工作;MVCC可以使用 乐观(optimistic)锁 和 悲观(pessimistic)锁来实现;各数据库中MVCC实现并不统一。推荐阅读:MySQL-InnoDB-MVCC多版本并发控制

《MySQL高性能》上面有一句话这样写到:

不要轻易相信“MyISAM比InnoDB快”之类的经验之谈,这个结论往往不是绝对的。在很多我们已知场景中,InnoDB的速度都可以让MyISAM望尘莫及,尤其是用到了聚簇索引,或者需要访问的数据都可以放入内存的应用。

一般情况下我们选择 InnoDB 都是没有问题的,但是某些情况下你并不在乎可扩展能力和并发能力,也不需要事务支持,也不在乎崩溃后的安全恢复问题的话,选择MyISAM也是一个不错的选择。但是一般情况下,我们都是需要考虑到这些问题的。

总体来说,MyISAM 适合读密集型的表,而 InnoDB 适合写密集型的表。 在数据库做主从分离的情况下,经常选择 MyISAM 引擎作为主库的存储引擎。

引擎的应用场景

InnoDB

适用场景:用于事务处理,具有ACID事物支持,应用于执行大量的insert和update操作的表

MyISAM

用于管理非事务表,提供高速检索及全文检索能力,适用于有大量的select操作的表,如 日志表

Memory

主要用于内容变化不频繁的表,或者作为中间的查找表。对表的更新要谨慎因为数据没有被写入到磁盘中,服务关闭前要考虑好数据的存储

索引

MySQL 索引使用的数据结构主要有BTree索引哈希索引 。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择 BTree 索引。

MySQL 的 BTree 索引使用的是B树中的 B+Tree,但对于主要的两种存储引擎的实现方式是不同的。

  • MyISAM: B+Tree叶节点的 data 域存放的是数据记录的地址。在索引检索的时候,首先按照B+Tree搜索算法搜索索引,如果指定的 Key 存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。
  • InnoDB: 其数据文件本身就是索引文件。相比 MyISAM 索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。而其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值而不是地址,这也是和MyISAM不同的地方。在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再走一遍主索引。 因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。 PS:整理自《Java工程师修炼之道》

推荐阅读:MySQL索引完全解读

索引类型

主键索引(Primary Key)

数据表的主键列使用的就是主键索引。以 InnoDB 作为存储引擎的表,表中的数据都会有一个主键,即使你不创建主键,InnoDB 会选择一个唯一的非空索引代替。如果没有这样的索引,则 InnoDB 会选择内置6字节长的 ROWID 作为隐含的主键索引(ROWID随着行记录的写入而主键递增,这个ROWID不像ORACLE的ROWID那样可引用,是隐含的)。

二级索引(辅助索引)

二级索引又称为辅助索引,是因为二级索引的叶子节点存储的数据是主键。也就是说,通过二级索引,可以定位主键的位置。

唯一索引,普通索引,前缀索引等索引属于二级索引。

  1. 唯一索引(Unique Key) :唯一索引的属性列不能出现重复的数据,允许为空,一张表允许创建多个唯一索引。建立唯一索引的目的大部分时候都是为了该属性列的数据的唯一性,而不是为了查询效率。
  2. 普通索引(Index)普通索引的唯一作用就是为了快速查询数据,一张表允许创建多个普通索引,并允许数据重复和NULL。
  3. 前缀索引(Prefix) :前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小, 因为只取前几个字符。

聚集索引与非聚集索引

聚集索引

聚集索引即索引结构和数据一起存放的索引。主键索引属于聚集索引。

在 MySQL 中,InnoDB 引擎的表的 .ibd文件就包含了该表的索引和数据,因为 InnoDB 是把数据存放在B+树中的,而B+树的键值就是主键,在B+树的叶子节点中,存储了表中所有的数据。

优点:

数据访问更快,因为整个B+树本身就是一颗多叉平衡树,叶子节点也都是有序的,定位到索引的节点,就相当于定位到了数据。

缺点:

  1. 依赖于有序的数据 :因为B+树是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整型还好,否则类似于字符串或UUID这种又长又难比较的数据,插入或查找的速度肯定比较慢。
  2. 更新代价大 : 如果索引列的数据被修改时,那么对应的索引也将会被修改, 而且聚集索引的叶子节点还存放着数据,修改代价肯定是较大的, 所以对于主键索引来说,主键一般都是不可被修改的。

非聚集索引

非聚集索引即索引结构和数据分开存放的索引。二级索引属于非聚集索引。

MYISAM 引擎的表的.MYI文件包含了表的索引, 该表的索引(B+树)的每个非叶子节点存储索引, 叶子节点存储索引和索引对应数据的指针,指向.MYD文件的数据。

非聚集索引的叶子节点并不一定存放数据的指针, 因为二级索引的叶子节点就存放的是主键,根据主键再回表查数据。

优点:

更新代价比聚集索引要小 。

缺点:

  1. 跟聚集索引一样,非聚集索引也依赖于有序的数据
  2. 可能会二次查询(回表) :这应该是非聚集索引最大的缺点了。 当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。

覆盖索引

如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为“覆盖索引”。我们知道 InnoDB 存储引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次。这样就会比较慢,覆盖索引就是把要查询出的列和索引是对应的,不做回表操作!

如下述 SQL 代码所示:

select name,age from user where name='hresh' and age=24

创建索引(name,age),查询数据时,就不用做回表操作。

不是所有类型的索引都可以成为覆盖索引。覆盖索引必须要存储索引列的值,而哈希索引、空间索引和全文索引等不存储索引列的值,所以 MySQL 只能使用 B-Tree 索引做覆盖索引。

全文索引

在一般情况下,模糊查询都是通过 like 的方式进行查询。但是,对于海量数据,这并不是一个好办法,在 like "value%" 可以使用索引,但是对于 like "%value%" 这样的方式,执行全表查询,这在数据量小的表,不存在性能问题,但是对于海量数据,全表扫描是非常可怕的事情,所以 like 进行模糊匹配性能很差。 这种情况下,需要考虑使用全文搜索的方式进行优化。(可能存在精度问题)

全文搜索在 MySQL 中是一个 FULLTEXT 类型索引。

  1. MySQL 5.6 以前的版本,只有 MyISAM 存储引擎支持全文索引;
  2. MySQL 5.6 及以后的版本,MyISAM 和 InnoDB 存储引擎均支持全文索引;
  3. 只有字段的数据类型为 char、varchar、text 及其系列才可以建全文索引。

和常用的模糊匹配使用 like + % 不同,全文索引有自己的语法格式,使用 match 和 against 关键字,比如

select * from fulltext_test 
    where match(content,tag) against('xxx xxx');

MySQL 的全文索引最开始仅支持英语,因为英语的词与词之间有空格,使用空格作为分词的分隔符是很方便的。亚洲文字,比如汉语、日语、汉语等,是没有空格的,这就造成了一定的限制。不过 MySQL 5.7.6 开始,引入了一个 ngram 全文分析器来解决这个问题,并且对 MyISAM 和 InnoDB 引擎都有效。

对于全文搜索场景,更专业的做法是使用全文搜索引擎,例如 ElasticSearch 或 Solr。

推荐阅读:MySQL 之全文索引

关于唯一索引和普通索引的选择问题?

我们首先需要了解一个新的概念:写缓存(change buffer)。它有什么作用呢?

它应用在非唯一普通索引页(non-unique secondary index page),对页进行了写操作,并不会立刻将磁盘页加载到缓冲池,而仅仅记录缓冲变更(buffer changes),等未来数据被读取时,再将数据合并(merge)恢复到缓冲池中的技术。写缓冲的目的是降低写操作的磁盘IO,提升数据库性能

写缓存是否会出现一致性问题?

答案是不会的。原因有以下三点:

(1)数据库异常奔溃,能够从redo log中恢复数据;

(2)写缓冲不只是一个内存结构,它也会被定期刷盘到写缓冲系统表空间;

(3)数据读取时,有另外的流程,将数据合并到缓冲池;

通过上述介绍我们可以发现写缓存仅适用于普通索引,即唯一索引不合适,这是为什么呢?

对于唯一索引来说,所有的更新操作都要先判断这个操作是否违反唯一性约束。

要判断表中是否存在这个数据,而这必须要将数据页读入内存才能判断,如果都已经读入到内存了,那直接更新内存会更快,就没必要使用 change buffer 了。

更多详情讲解推荐阅读:写缓冲(change buffer),这次彻底懂了!!!

BTree索引和哈希索引区别?

  1. 哈希索引适合等值查询,但是无法进行范围查询;
  2. 哈希索引每次都要全表查询;
  3. 哈希索引没办法利用索引完成排序;
  4. 哈希索引不支持多列联合索引的最左匹配规则;
  5. 如果有大量重复键值的情况下,哈希索引的效率会很低,因为存在哈希碰撞问题。

为什么选择B+数而非B树结构?

  1. 内部节点不保存数据, 只存储关键字和指向下个节点的指针,那么就可以保存更多的索引,减少数据库磁盘IO的次数。
  2. 因为内部节点不保存数据,所以每一次的查找都会命中到叶子节点,而叶子节点是处在同一层的,因此查询的性能更加的稳定。
  3. 所有的叶子节点按顺序链接成了链表,因此可以方便进行范围查询。

B+树中一个节点到底存多少个元素合适?

也可以换个角度思考 B+树中一个节点到底多大合适?

B+树中一个节点为一页或页的倍数最为合适。因为如果一个节点的大小小于1页,那么读取这个节点的时候其实也会读出1页,造成资源的浪费;如果一个节点的大小大于1页,比如1.2页,那么读取这个节点的时候会读出2页,也会造成资源的浪费;所以为了不造成浪费,所以最后把一个节点的大小控制在1页、2页、3页、4页等倍数页大小最为合适。

那么页的大小是多少呢? 在计算机中磁盘存储数据最小单元是扇区,一个扇区的大小是512字节,而文件系统(例如XFS/EXT4)它的最小单元是块,一个块的大小是4k,而对于我们的InnoDB存储引擎也有自己的最小储存单元——页(Page),一个页的大小是16K。

拓展:InnoDB一棵B+树可以存放多少行数据? 答案:约2千万。感兴趣的朋友可以阅读:面试题:InnoDB中一棵B+树能存多少行数据?

为什么要使用索引?

  1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
  2. 可以大大加快数据的检索速度(大大减少的检索的数据量), 这也是创建索引的最主要的原因。
  3. 帮助服务器避免排序和临时表。
  4. 将随机IO变为顺序IO
  5. 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。

为什么不对表中的每一个列创建一个索引呢?

  1. 当对表中的数据进行增删改操作的时候,索引也要动态的维护,这样会增加时耗,从而降低维护速率。
  2. 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。

如何选择字段创建索引及相关注意事项?

1、在经常使用在 WHERE 子句中的列上面创建索引,加快搜索速率;

2、在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间;

3、对于中到大型表索引都是非常有效的,但是特大型表的话维护开销会很大,不适合建索引;

4、索引经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度;

5、避免 WHERE 子句中对字段使用函数,如 to_date(create_time) >xxxxx,这会造成无法命中索引。

6、在使用 InnoDB 引擎时使用与业务无关的自增主键作为主键,即使用逻辑主键,而不要使用业务主键;

7、索引列是否设置 null 是不影响性能的。 但是,还是不建议列上允许为空。最好限制 not null,因为 null 需要更多的存储空间并且 null 值无法参与某些运算。

8、删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗;(MySQL 5.7 可以通过查询 sys 库的 chema_unused_indexes 视图来查询哪些索引从未被使用)

9、在使用 limit offset 查询缓慢时,可以借助索引来提高性能 。

10、合理使用索引覆盖。

11、避免冗余索引。冗余索引指的是指多个索引的前缀列相同,或者在联合索引中包含了主键的索引。如果创建了索引(a,b),再创建索引(a)就是冗余索引,因为这只是前面一个索引的前缀索引,因此(a,b)也可以当作(a)来使用,但是(b,a)就不是冗余索引,索引(b)也不是,因为b不是索引(a,b)的最左前缀列。(MySQLS.7版本后,可以通过查询 sys 库的 schemal_redundant_indexes 表来查看冗余索引)

为什么索引能提高查询速度

首先 MySQL 的基本存储结构是(记录都存在页里边):

Java面试准备之MySQL系列一

Java面试准备之MySQL系列一

  • 各个数据页可以组成一个双向链表(在 B树的介绍中,节点的插入与删除可以发现这点)
  • 每个数据页中的记录又可以组成一个单向链表
    • 每个数据页都会为存储在它里边儿的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录
    • 其他列(非主键)作为搜索条件:只能从最小记录开始依次遍历单链表中的每条记录

所以说,如果我们写select * from user where username = 'xxx'这样没有进行任何优化的 sql 语句,默认会这样做:

  • 定位到记录所在的页
    • 需要遍历双向链表,找到所在的页
  • 从所在的页内中查找相应的记录
    • 由于不是根据主键查询,只能遍历所在页的单链表了。

使用索引之后

索引做了些什么可以让我们查询加快速度呢?其实就是将无序的数据变成有序(相对):

Java面试准备之MySQL系列一

要找到id为8的记录简要步骤:

Java面试准备之MySQL系列一

很明显的是:没有用索引我们是需要遍历双向链表来定位对应的页,现在通过 “目录” 就可以很快地定位到对应的页上了!(二分查找,时间复杂度近似为O(logn))

讲一下MySQL查找数据的过程

如果未使用索引,首先需要遍历双向链表来找到记录所在的页,然后再遍历所在页的单链表,找到对应的数据记录。

如果使用聚簇索引,此时非叶子节点存放主键值和指向子节点的指针,叶子节点中存放的主键值和数据,首先通过二分法来定位到记录所在的页,然后再遍历所在页的单链表,找到对应的数据记录。

如果使用非聚簇索引,此时非叶子节点存放非主键值和指向子节点的指针,叶子节点中存放的非主键值和主键值,首先通过二分法来定位到记录所在的页,然后再遍历所在页的单链表,找到对应的主键值。之后再按照聚簇索引的方式查找一遍。

索引最左匹配原则

MySQL中的索引可以以一定顺序引用多列,这种索引叫作联合索引。如User表的name和city加联合索引就是(name,city),在创建联合索引时,索引字段的顺序需要考虑字段值去重之后的个数,较多的放前面。而最左前缀原则指的是,如果查询的时候查询条件精确匹配索引的左边连续一列或几列,则此列就可以被用到。如下:

select * from user where name=xx and city=xx ; //可以命中索引
select * from user where name=xx ; // 可以命中索引
select * from user where city=xx ; // 无法命中索引            

这里需要注意的是,查询的时候如果两个条件都用上了,但是顺序不同,如 city= xx and name =xx,那么现在的查询引擎会自动优化为匹配联合索引的顺序,这样是能够命中索引的。

由于最左前缀原则,在创建联合索引时,索引字段的顺序需要考虑字段值去重之后的个数,较多的放前面。ORDER BY子句也遵循此规则。

Mysql如何为表字段添加索引?

1.添加PRIMARY KEY(主键索引)

ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` ) 

2.添加UNIQUE(唯一索引)

ALTER TABLE `table_name` ADD UNIQUE ( `column` ) 

3.添加INDEX(普通索引)

ALTER TABLE `table_name` ADD INDEX index_name ( `column` )

4.添加FULLTEXT(全文索引)

ALTER TABLE `table_name` ADD FULLTEXT ( `column`) 

5.添加多列索引

ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` )

如何查看 SQL 语句是否能够命中索引?

执行 expalin 命令,此命令能够打印出 SQL 语句的执行计划,从而判断要执行的 SQL 语句是否能够命中索引,并做进一步调整。 在优化 SQL 查询时,最好的方式就是使用此命令来判断执行计划是否合理。 如下:

explain select * from test_user where id=1;

Java面试准备之MySQL系列一

这里需要注意的是type、 key和l extra这3列,分别介绍如下。

  • type: 显示了连接使用了哪种类别,有元使用索引。 如果为ALL,那么说明要进行全表扫描。
  • key: 此列显示 MySQL 实际决定使用的键(索引)。 如果没有选择索引,键是 NULL。 要想强制 MySQL 使用或忽视 possible_keys 列中的索引,则在查询中使用 FORCE INDEX、 USEINDEX 或 者IGNOREINDEX。 如果这里为 NULL,则说明没有命中索引。
  • Extra:此列如果出现 Usingfilesort (需要额外的步骤来发现如何对返回的行排序)或者Usingtemporary (需要创建一个临时表来存储结果),说明查询需要优化。

关于 expalin 命令的详细讲解,推荐阅读:面试前必须知道的MySQL命令【expalin】

什么情况下会发生明明创建了索引,但是执行的时候并没有通过索引呢?

MySQL 自身是有查询优化器的,优化器的作用就是在一个查询所有可能的执行方式中找到其中最好的执行计划。 因此 explain 获取到的执行计划并非是固定的,它会随着数据分布情况而变动,执行计划也有可能改变。 而且当数据库计算出使用索引所耗费的时间长于全表扫描或其他操作时(比如当表中索引字段数据重复率太高),将
不会使用索引。

索引规约

摘抄之《阿里巴巴开发手册》

1、业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。

2、超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致;多表关联查询时,保证被关联的字段需要有索引。
说明:即使双表 join 也要注意表索引、SQL 性能。

3、在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可,即建立前缀索引。
说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度来确定。

4、页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。

5、利用延迟关联或者子查询优化超多分页场景。

说明: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

6、SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。

说明:
1) consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
2) ref 指的是使用普通的索引(normal index)。
3) range 对索引进行范围检索。

反例:explain 表的结果,type=index,索引物理文件全扫描,速度非常慢,这个 index 级别比较 range 还低,与全表扫描是小巫见大巫。

7、防止因字段类型不同造成的隐式转换,导致索引失效。

未完待续。。。

发表评论 取消回复
表情 图片 链接 代码

分享