MySQL面试考点

1. 索引

1.1 了解MySQL的索引吗?它为什么使用B+tree作为底层,而不是其他呢?

这里我们要谈的是其他数据结构的缺点,然后说说B+tree的优点,也就看你对MySQL的B+tree与其他数据结构熟不熟悉。

  • Hash
    (1)Hash 索引仅仅能满足”=”,”IN”和”<=>”查询,不能使用范围查询。
    由于 Hash 索引比较的是进行 Hash 运算之后的 Hash 值,所以它只能用于等值的过滤,不能用于基于范围的过滤,因为经过相应的 Hash 算法处理之后的 Hash 值的大小关系,并不能保证和Hash运算前完全一样。
    (2)Hash 索引无法被用来数据的排序操作。
    由于 Hash 索引中存放的是经过 Hash 计算之后的 Hash 值,而且Hash值的大小关系并不一定和 Hash 运算前的键值完全一样,所以数据库无法利用索引的数据来避免任何排序运算;
    (3)Hash 索引不能利用部分索引键查询,即不适用最左前缀原则。
    对于组合索引,Hash 索引在计算 Hash 值的时候是组合索引键合并后再一起计算 Hash 值,而不是单独计算 Hash 值,所以通过组合索引的前面一个或几个索引键进行查询的时候,Hash 索引也无法被利用。
    (4)Hash 索引在任何时候都不能避免表扫描,不可使用覆盖索引。
    前面已经知道,Hash 索引是将索引键通过 Hash 运算之后,将 Hash运算结果的 Hash 值和所对应的行指针信息存放于一个 Hash 表中,由于不同索引键存在相同 Hash 值,所以即使取满足某个 Hash 键值的数据的记录条数,也无法从 Hash 索引中直接完成查询,还是要通过访问表中的实际数据进行相应的比较,并得到相应的结果。
  • B-tree

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

  • 红黑树
    红黑树(Red Black Tree) 是一种自平衡二叉查找树。红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
    它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目
  • B+tree

B-Tree有许多变种,其中最常见的是B+Tree,例如MySQL就普遍使用B+Tree实现其索引结构。
与B-Tree相比,B+Tree有以下不同点:

  1. 每个节点的指针上限为2d而不是2d+1。
  2. 内节点不存储data,只存储key;叶子节点不存储指针。

由于B+Tree的非叶子节点只存储键值信息不再存储data数据,所有的data数据均存储在叶子节点中,这样使得 内节点出度d值会变得大
由于并不是所有节点都具有相同的域,因此B+Tree中叶节点和内节点一般大小不同。这点与B-Tree不同,虽然B-Tree中不同节点存放的key和指针可能数量不一致,但是每个节点的域和上限是一致的,所以在实现中B-Tree往往对每个节点申请同等大小的空间。
带有顺序访问指针的B+Tree
一般在数据库系统或文件系统中使用的B+Tree结构都在经典B+Tree的基础上进行了优化,增加了顺序访问指针。在B+Tree的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的B+Tree。做这个优化的目的是为了提高区间访问的性能,例如图中如果要查询key为从15到60的所有数据记录,当找到15后,只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点,极大提到了区间查询效率。

1.2 说说你理解的explain

这里我们只看重点需要记住的地方,同时这几点也是我们在做sql语句分析的时候,重点需要看的几个点:

  1. type
    这一列表示关联类型或访问类型,即MySQL决定如何查找表中的行,查找数据行记录的大概范围。 依次从最优到最差分别为:system > const > eq_ref > ref > range > index > ALL 一般来说,得保证查询达到range级别,最好达到ref
    • NULL
      mysql能够在优化阶段分解查询语句,在执行阶段用不着再访问表或索引。例如:在索引列中选取最小值,可以单独查找索引来完成,不需要在执行时访问表
      1. explain select min(id) from tableName;
  • const, system
    mysql能对查询的某部分进行优化并将其转化成一个常量(可以看show warnings 的结果)。用于 primary key 或 unique key 的所有列与常数比较时,所以表最多有一个匹配行,读取1次,速度比较快。system是const的特例,表里只有一条元组匹配时为system
    explain select * from (select * from tableName where id = 1) tableName;
    
  • primary key 或 unique key 索引的所有部分被连接使用 ,最多只会返回一条符合条件的记录。这可能是在 const 之外最好的联接类型了,简单的 select 查询不会出现这种 type。
    explain select * from tableName1 left join tableName2 on tableName1.id = tableName2.id;
    
  • ref
    相比 eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前缀,索引要和某个值相比较,可能会找到多个符合条件的行。
    1. 简单 select 查询,name是普通索引(非唯一索引)
      explain select * from tableName where name = "starsky";
      
  2.  关联表查询,idx_film_actor_id是film_id和actor_id的联合索引,这里使用到了film_actor的左边前缀film_id部分。 
explain select film_id from film left join film_actor on film.id = film_actor.film_id;
  • range
    范围扫描通常出现在 in(), between ,> ,<, >= 等操作中。使用一个索引来检索给定范围的行。
    explain select * from actor where id > 1;
    
  • index
    扫描全表索引,这通常比ALL快一些。(index是从索引中读取的,而all是从硬盘中读取)
    explain select * from film;
    
  • ALL
    即全表扫描,意味着mysql需要从头到尾去查找所需要的行。通常情况下这需要增加索引来进行优化了
    explain select * from actor;
    
  1. possible_keys与key
    • possible_keys列
      这一列显示查询可能使用哪些索引来查找。 explain 时可能出现 possible_keys 有列,而 key 显示 NULL 的情况,这种情况是因为表中数据不多,mysql认为索引对此查询帮助不大,选择了全表查询。 如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查 where 子句看是否可以创造一个适当的索引来提高查询性能,然后用 explain 查看效果。
    • key列
      这一列显示mysql实际采用哪个索引来优化对该表的访问。 如果没有使用索引,则该列是 NULL。如果想强制mysql使用或忽视possible_keys列中的索引,在查询中使用 force index、ignore index。
  2. extra
    • Using index
      查询的列被索引覆盖,并且where筛选条件是索引的前导列,是性能高的表现。一般是使用了覆盖索引(索引包含了所有查询的字段)。对于innodb来说,如果是辅助索引性能会有不少提高
      explain select film_id from film_actor where film_id = 1;
      
  • Using where
    查询的列未被索引覆盖,where筛选条件非索引的前导列
    explain select * from actor where name = 'a';
    
  • Using where Using index
    查询的列被索引覆盖,并且where筛选条件是索引列之一但是不是索引的前导列,意味着无法直接通过索引查找来查询到符合条件的数据
    explain select film_id from film_actor where actor_id = 1;
    
  • NULL
    查询的列未被索引覆盖,并且where筛选条件是索引的前导列,意味着用到了索引,但是部分字段未被索引覆盖,必须通过“回表”来实现,不是纯粹地用到了索引,也不是完全没用到索引
    explain select * from film_actor where film_id = 1;
    
  • Using index condition
    与Using where类似,查询的列不完全被索引覆盖,where条件中是一个前导列的范围;
    explain select * from film_actor where film_id > 1;
    
  • Using temporary
    mysql需要创建一张临时表来处理查询。出现这种情况一般是要进行优化的,首先是想到用索引来优化。
    1. actor.name没有索引,此时创建了张临时表来distinct
      explain select distinct name from actor;
      
  2.  film.name建立了idx_name索引,此时查询时extra是using index,没有用临时表 
explain select distinct name from film;
  • Using filesort
    mysql 会对结果使用一个外部索引排序,而不是按索引次序从表里读取行。此时mysql会根据联接类型浏览所有符合条件的记录,并保存排序关键字和行指针,然后排序关键字并按顺序检索行信息。这种情况下一般也是要考虑使用索引来优化的。
    1. actor.name未创建索引,会浏览actor整个表,保存排序关键字name和对应的id,然后排序name并检索行记录
      explain select * from actor order by name;
      
  2.  film.name建立了idx_name索引,此时查询时extra是using index 
explain select * from film order by name;

1.3 什么情况下设置了索引但无效

① 以“%”开头的LIKE语句,模糊匹配

② OR语句前后没有同时使用索引

③ 数据类型出现隐式转化(如varchar不加单引号的话可能会自动转换为int型)

1.4 说说分组查询与排序查询如何处理

  1. GROUP BY
    1. 为分组字段添加索引,使索引能够给出分组所需的数据
    2. 语句查询不要过于复杂,减少查询的复杂度
  2. ORDER BY
    1. 在添加索引时,尽可能讲排序字段排在最左侧
    2. 不要*查询,尽可能查询索引中有的字段

1.5 索引的目的是什么? 索引对数据库系统的负面影响是什么? 为数据表建立索引的原则有哪些? 什么情况下不宜建立索引?

  • 快速访问数据表中的特定信息,提高检索速度,创建唯一性索引,保证数据库表中每一行数据的唯一性。加速表和表之间的连接,使用分组和排序子句进行数据检索时,可以显著减少查询中分组和排序的时间
  • 负面影响: 创建索引和维护索引需要耗费时间,这个时间随着数据量的增加而增加;索引需要占用物理空间,不光是表需要占用数据空间,每个索引也需要占用物理空间;当对表进行增、删、改、的时候索引也要动态维护,这样就降低了数据的维护速度。
  • 在最频繁使用的、用以缩小查询范围的字段上建立索引。在频繁使用的、需要排序的字段上建立索引
  • 对于查询中很少涉及的列或者重复值比较多的列,不宜建立索引。对于一些特殊的数据类型,不宜建立索引,比如文本字段(text)等

1.6 简单描述mysql中,索引,主键,唯一索引,联合索引的区别,对数据库的性能有什么影响(从读写两方面)

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

2. 事务

2.1 说下你理解的MySQL事务隔离级别,还有对MVCC的使用?

mysql隔离级别分别是读取未提交,读取已提交,可重复读取,序列化四个级别,相对的会产生的问题是脏读,不可重复读,幻读.对于脏读问题,通常出现在读取未提交隔离级别下,在事务2中会查询到事务1还未提交的数据,可以通过提高隔离级别的方式解决该问题,也可以给相应数据加锁解决,以下是加锁演示:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from products where id = 1 for update;
+----+------------+---------+-------+
| id | name       | price   | stock |
+----+------------+---------+-------+
|  1 | HUAWEI P40 | 3299.00 |   100 |
+----+------------+---------+-------+
1 row in set (0.00 sec)

mysql> update products set stock = stock - 1 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.01 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from products where id = 1 for update;
+----+------------+---------+-------+
| id | name       | price   | stock |
+----+------------+---------+-------+
|  1 | HUAWEI P40 | 3299.00 |    99 |
+----+------------+---------+-------+
1 row in set (12.87 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

可以看到的是事务依然是查询到了修改的数据,但是这并不属于脏读,而是mysql事务中查询语句加锁之后的当前磁盘数据读取,如果是未加锁的可能会是快照读取,读取的是事务开启前的数据.

不可重复读取:

对于不可重读读取,是在读取已提交的情况下,事务1修改了数据,并已经提交数据,此时按照事务的一致性特点,事务2是不能读取事务开启前不一样的数据的即,此时事务2只能读取到事务开启前的数据.要保证事务的一致性,我们需要提高事务的隔离级别到可重复读

幻读:

对于幻读问题,主要是在新增的数据情况下,事务1查询数据是否存在,不存在则新增数据,事务2一样查询数据是否存在,不存在则一样新增数据,因为mysql对于新增,修改,删除语句是默认加锁的,事务2必然会进入锁等待,等到事务1提交后会发现数据已存在导致数据新增失败,但是查询明明是不存在的,这就是幻读.这个问题我们可以通过将隔离级别提升到串行化解决亦可以给查询的数据加个间隙锁,即锁住一块区域的数据,使查询阶段就进入到锁等待,锁的查询会是磁盘此时实际的数据读取,事务1提交后是能够查询到数据的,这样就能够不去新增数据了.

下面是演示:

事务1:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from products where id > 1 and id < 10 for update;
+----+------------+----------+-------+
| id | name       | price    | stock |
+----+------------+----------+-------+
|  2 | HUAWEI P50 |  4999.00 |   100 |
|  3 | HUAWEI P60 | 10000.00 |   100 |
|  5 | HUAWEI P60 | 10000.00 |   100 |
+----+------------+----------+-------+
3 rows in set (0.00 sec)

mysql> rollback;
Query OK, 0 rows affected (0.00 sec)

事务2:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from products where id > 1 and id < 10 for update;
+----+------------+----------+-------+
| id | name       | price    | stock |
+----+------------+----------+-------+
|  2 | HUAWEI P50 |  4999.00 |   100 |
|  3 | HUAWEI P60 | 10000.00 |   100 |
|  5 | HUAWEI P60 | 10000.00 |   100 |
+----+------------+----------+-------+
3 rows in set (48.11 sec)

mysql> rollback;
Query OK, 0 rows affected (0.00 sec)

2.2 mysql都有什么锁,死锁判定原理和具体场景,死锁怎么解决?

MySQL有三种锁的级别:页级、表级、行级。

表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
什么是死锁?

死锁: 是指两个或两个以上的进程在执行过程中。因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等竺的进程称为死锁进程。

表级锁不会产生死锁.所以解决死锁主要还是针对于最常用的InnoDB。

死锁的关键在于:两个(或以上)的Session加锁的顺序不一致。

那么对应的解决死锁问题的关键就是:让不同的session加锁有次序。

死锁的解决办法?

1.查出的线程杀死 kill
SELECT trx_MySQL_thread_id FROM information_schema.INNODB_TRX;

2.设置锁的超时时间
Innodb 行锁的等待时间,单位秒。可在会话级别设置,RDS 实例该参数的默认值为 50(秒)。

生产环境不推荐使用过大的 innodb_lock_wait_timeout参数值
该参数支持在会话级别修改,方便应用在会话级别单独设置某些特殊操作的行锁等待超时时间,如下:
set innodb_lock_wait_timeout=1000; —设置当前会话 Innodb 行锁等待超时时间,单位秒。

3. 分库分表

3.1 水平分表与垂直分表

  1. 垂直切分
    垂直切分常见有垂直分库和垂直分表两种。
    垂直分库就是根据业务耦合性,将关联度低的不同表存储在不同的数据库。做法与大系统拆分为多个小系统类似,按业务分类进行独立划分。与”微服务治理”的做法相似,每个微服务使用单独的一个数据库。如图:
    MS2 - 图1
    垂直分表是基于数据库中的”列”进行,某个表字段较多,可以新建一张扩展表,将不经常用或字段长度较大的字段拆分出去到扩展表中。在字段很多的情况下(例如一个大表有100多个字段),通过”大表拆小表”,更便于开发与维护,也能避免跨页问题,MySQL底层是通过数据页存储的,一条记录占用空间过大会导致跨页,造成额外的性能开销。另外数据库以行为单位将数据加载到内存中,这样表中字段长度较短且访问频率较高,内存能加载更多的数据,命中率更高,减少了磁盘IO,从而提升了数据库性能。
    垂直切分的优点:
    • 解决业务系统层面的耦合,业务清晰
    • 与微服务的治理类似,也能对不同业务的数据进行分级管理、维护、监控、扩展等
    • 高并发场景下,垂直切分一定程度的提升IO、数据库连接数、单机硬件资源的瓶颈

缺点:

  • 部分表无法join,只能通过接口聚合方式解决,提升了开发的复杂度
  • 分布式事务处理复杂
  • 依然存在单表数据量过大的问题(需要水平切分)
    1. 水平切分
      当一个应用难以再细粒度的垂直切分,或切分后数据量行数巨大,存在单库读写、存储性能瓶颈,这时候就需要进行水平切分了。
      水平切分分为库内分表和分库分表,是根据表内数据内在的逻辑关系,将同一个表按不同的条件分散到多个数据库或多个表中,每个表中只包含一部分数据,从而使得单个表的数据量变小,达到分布式的效果。如图所示:
      MS2 - 图2
      库内分表只解决了单一表数据量过大的问题,但没有将表分布到不同机器的库上,因此对于减轻MySQL数据库的压力来说,帮助不是很大,大家还是竞争同一个物理机的CPU、内存、网络IO,最好通过分库分表来解决。
      水平切分的优点:
  • 不存在单库数据量过大、高并发的性能瓶颈,提升系统稳定性和负载能力
  • 应用端改造较小,不需要拆分业务模块

缺点:

  • 跨分片的事务一致性难以保证
  • 跨库的join关联查询性能较差
  • 数据多次扩展难度和维护量极大

水平切分后同一张表会出现在多个数据库/表中,每个库/表的内容不同。几种典型的数据分片规则为:

3.2 你们那个xxxx项目的分库分表是根据什么规则做的呢?分完之后的处理做了哪些?

我们的项目是根据业务做了垂直的分库,将整个系统的分为了用户,商品,订单(这里看你们自己的业务可以怎么分,这边参考的是LMRS那个项目),其中我主要负责的是订单模块,在这里我对订单表根据hash取模处理去进行分表,具体是使用雪花算法去生成一个唯一的值之后进行hash的处理,随后在对分片数进行取模,当时我们是分了64张表,这里是对64取模。

在分表完成之后,我遇到了如下困难:

  1. 分表之后新库和老库的数据迁移问题
  2. 订单表数据的查询问题
  3. 日后业务增长之后,扩容的问题

对此我做了如下处理:

  • 数据迁移问题
    1. 导出老库中的数据,同时在业务中新库与老库同时写入新的订单数据
    2. 配置mycat的分库分表规则
    3. 通过mycat在系统的低峰期将数据导入到新库中,当时我们是千万的数据,时间大概在10-15分钟就完成了
    4. 做数据验证,这里我们是通过在用户平时的查询中来做的,在用户查询的同时,会查询两个库的数据,去做对比,如果老库中的数据比新库多,会通过老库去补全新库的数据
  • 查询问题
    对于分库之后的连表查询问题,我们是采用了mycat去配置一个全局表,做数据的冗余,同时有创建一张异构索引表,记录用户的id以及用户的订单id,按照用户的id去进行hash取模分表,然后在用户查询数据的时候,先根据用户id查询出这个用户的订单id,然后在根据订单id去订单库查询数据
  • 扩容问题
    与前面的数据迁移一致,分库分表本质就是在做扩容,从一到多,从多到更多

4. 主从

4.1 主从原理

使用主从的原因:

  1. 实现服务器负载均衡
    即可以通过在主服务器和从服务器之间切分处理客户查询的负荷,从而得到更好的客户相应时间。通常情况下,数据库管理员会有两种思路。
    一是在主服务器上只实现数据的更新操作。包括数据记录的更新、删除、新建等等作业。而不关心数据的查询作业。数据库管理员将数据的查询请求全部 转发到从服务器中。这在某些应用中会比较有用。如某些应用,像基金净值预测的网站。其数据的更新都是有管理员更新的,即更新的用户比较少。而查询的用户数 量会非常的多。此时就可以设置一台主服务器,专门用来数据的更新。同时设置多台从服务器,用来负责用户信息的查询
    二是在主服务器上与从服务器切分查询的作业。在这种思路下,主服务器不单单要完成数据的更新、删除、插入等作业,同时也需要负担一部分查询作业。而从服务器的话,只负责数据的查询。当主服务器比较忙时,部分查询请求会自动发送到从服务器重,以降低主服务器的工作负荷。
  2. 通过复制实现数据的异地备份
    可以定期的将数据从主服务器上复制到从服务器上,这无疑是先了数据的异地备份。在传统的备份体制下,是将数据备份在本地。此时备份 作业与数据库服务器运行在同一台设备上,当备份作业运行时就会影响到服务器的正常运行。有时候会明显的降低服务器的性能。同时,将备份数据存放在本地,也 不是很安全。如硬盘因为电压等原因被损坏或者服务器被失窃,此时由于备份文件仍然存放在硬盘上,数据库管理员无法使用备份文件来恢复数据。这显然会给企业 带来比较大的损失。
  3. 提高数据库系统的可用性
    数据库复制功能实现了主服务器与从服务器之间数据的同步,增加了数据库系统的可用性。当主服务器出现问题时,数据库管理员可以马上让从服务器作为主服务器,用来数据的更新与查询服务。然后回过头来再仔细的检查主服务器的问题。此时一般数据库管理员也会采用两种手段。
    一是主服务器故障之后,虽然从服务器取代了主服务器的位置,但是对于主服务器可以采取的操作仍然做了一些限制。如仍然只能够进行数据的查询,而 不能够进行数据的更新、删除等操作。这主要是从数据的安全性考虑。如现在一些银行系统的升级,在升级的过程中,只能够查询余额而不能够取钱。这是同样的道 理。
    二是从服务器真正变成了主服务器。当从服务器切换为主服务器之后,其地位完全与原先的主服务器相同。此时可以实现对数据的查询、更新、删除等操 作。为此就需要做好数据的安全性工作。即数据的安全策略,要与原先的主服务器完全相同。否则的话,就可能会留下一定的安全隐患

原理:

  1. master服务器将数据的改变都记录到二进制binlog日志中,只要master上的数据发生改变,则将其改变写入二进制日志;
  2. salve服务器会在一定时间间隔内对master二进制日志进行探测其是否发生改变,如果发生改变,则开始一个I/O Thread请求master二进制日志
  3. 同时主节点为每个I/O线程启动一个dump线程,用于向其发送二进制日志,并保存至从节点本地的中继日志中
  4. 从节点将启动SQL线程从中继日志中读取二进制日志,在本地重放,使得其数据和主节点的保持一致
  5. 最后I/O Thread和SQL Thread将进入睡眠状态,等待下一次被唤醒.
    需要理解:
    1)从库会生成两个线程,一个I/O线程,一个SQL线程;
    2)I/O线程会去请求主库的binlog,并将得到的binlog写到本地的relay-log(中继日志)文件中;
    3)主库会生成一个log dump线程,用来给从库I/O线程传binlog;
    4)SQL线程,会读取relay log文件中的日志,并解析成sql语句逐一执行;
    *复制流程:
    1. master将操作语句记录到binlog日志中
    2. salve服务器会在一定时间间隔内对master二进制日志进行探测其是否发生改变,如果发生改变
    3. salave开启两个线程:IO线程和SQL线程
      _1)_IO线程:**负责读取master的binlog内容到中继日志relay log里;
      _2)_SQL线程:**负责从relay log日志里读出binlog内容,并更新到slave的数据库里(保证数据一致)

4.2 主从延迟

5. 其他

5.1MySQL数据库作发布系统的存储,一天五万条以上的增量,预计运维三年,怎么优化?

a. 设计良好的数据库结构,允许部分数据冗余,尽量避免join查询,提高效率。

b. 选择合适的表字段数据类型和存储引擎,适当的添加索引。

c. mysql库主从读写分离。

d. 找规律分表,减少单表中的数据量提高查询速度。

e。添加缓存机制,比如memcached,apc等。

f. 不经常改动的页面,生成静态页面。

g. 书写高效率的SQL。比如 SELECT * FROM TABEL 改为 SELECT field_1, field_2, field_3 FROM TABLE.

5.2 对于大流量的网站,您采用什么样的方法来解决各页面访问量统计问题?

a. 确认服务器是否能支撑当前访问量。

b. 优化数据库访问。

c. 禁止外部访问链接(盗链), 比如图片盗链。

d. 控制文件下载。

e. 使用不同主机分流。

f. 使用浏览统计软件,了解访问量,有针对性的进行优化。

5.3 为什么elasticsearch 的搜索就是比mysql要快一点呢?数据结构上的特性?

Mysql 只有 term dictionary 这一层,是以 b-tree 排序的方式存储在磁盘上的。检索一个 term 需要若干次的 random access 的磁盘操作。而 Lucene 在 term dictionary 的基础上添加了 term index 来加速检索,term index 以树的形式缓存在内存中。从 term index 查到对应的 term dictionary 的 block 位置之后,再去磁盘上找 term,大大减少了磁盘的 random access 次数。

额外值得一提的两点是:term index 在内存中是以 FST(finite state transducers)的形式保存的,其特点是非常节省内存。Term dictionary 在磁盘上是以分 block 的方式保存的,一个 block 内部利用公共前缀压缩,比如都是 Ab 开头的单词就可以把 Ab 省去。这样 term dictionary 可以比 b-tree 更节约磁盘空间。

两者对比:
对于倒排索引,要分两种情况:

1、基于分词后的全文检索

这种情况是es的强项,而对于mysql关系型数据库而言完全是灾难

因为es分词后,每个字都可以利用FST高速找到倒排索引的位置,并迅速获取文档id列表

但是对于mysql检索中间的词只能全表扫(如果不是搜头几个字符)

2、精确检索

这种情况我想两种相差不大,有些情况下mysql的可能会更快些

如果mysql的非聚合索引用上了覆盖索引,无需回表,则速度可能更快

es还是通过FST找到倒排索引的位置并获取文档id列表,再根据文档id获取文档并根据相关度算分进行排序,但es还有个杀手锏,即天然的分布式使得在大数据量面前可以通过分片降低每个分片的检索规模,并且可以并行检索提升效率

用filter时更是可以直接跳过检索直接走缓存

什么是Term Index?

将分词后的term进行排序索引,类似的mysql对于”term”(即主键,或者索引键)只是做了排序, 并且是大部分是放在磁盘上的,只有B+树的上层才是放在内存中的,查询仍然需要logN的访问磁盘,而ES将term分词排序后还做了一次索引,term index,即将term的通用前缀取出,构建成Trie树
通过这个树可以快速的定位到Term dictionary的本term的offset,再经过顺序查找,便可以很快找到本term的posting list。

5.4 解释一下什么是池化设计思想。什么是数据库连接池?为什么需要数据库连接池?

池话设计应该不是一个新名词。我们常见的如java线程池、jdbc连接池、redis连接池等就是这类设计的代表实现。这种设计会初始预设资源,解决的问题就是抵消每次获取资源的消耗,如创建线程的开销,获取远程连接的开销等。就好比你去食堂打饭,打饭的大妈会先把饭盛好几份放那里,你来了就直接拿着饭盒加菜即可,不用再临时又盛饭又打菜,效率就高了。除了初始化资源,池化设计还包括如下这些特征:池子的初始值、池子的活跃值、池子的最大值等,这些特征可以直接映射到java线程池和数据库连接池的成员属性中。

数据库连接本质就是一个 socket 的连接。数据库服务端还要维护一些缓存和用户权限信息之类的 所以占用了一些内存。我们可以把数据库连接池是看做是维护的数据库连接的缓存,以便将来需要对数据库的请求时可以重用这些连接。为每个用户打开和维护数据库连接,尤其是对动态数据库驱动的网站应用程序的请求,既昂贵又浪费资源。在连接池中,创建连接后,将其放置在池中,并再次使用它,因此不必建立新的连接。如果使用了所有连接,则会建立一个新连接并将其添加到池中。连接池还减少了用户必须等待建立与数据库的连接的时间。

5.4 MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据?

redis 提供 6种数据淘汰策略:

  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  • no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!

4.0版本后增加以下两种:

  • volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
  • allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key

使用策略规则:

1、如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru

2、如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random

5.5 如何进行大表优化?

当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下:

  1. 限定数据的范围
    务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内;
  2. 读/写分离
    经典的数据库拆分方案,主库负责写,从库负责读;
  3. 垂直分区
    根据数据库里面数据表的相关性进行拆分。 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。

简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。 如下图所示,这样来说大家应该就更容易理解了。

数据库垂直分区

垂直拆分的优点: 可以使得列数据变小,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。
垂直拆分的缺点: 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂;

  1. 水平分区
    保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。

水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。

数据库水平拆分

水平拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 水平拆分最好分库 。

水平拆分能够 支持非常大的数据量存储,应用端改造也少,但 分片事务难以解决 ,跨节点Join性能较差,逻辑复杂。《Java工程师修炼之道》的作者推荐 尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度 ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。

5.6说说自己对于 MySQL 常见的两种存储引擎:MyISAM与InnoDB的理解?

InnoDB 引擎:InnoDB 引擎提供了对数据库 acid 事务的支持,并且还提供了行级锁和外键的约束,它的设计的目标就是处理大数据容量的数据库系统。MySQL 运行的时候,InnoDB 会在内存中建立缓冲池,用于缓冲数据和索引。但是该引擎是不支持全文搜索,同时启动也比较的慢,它是不会保存表的行数的,所以当进行 select count() from table 指令的时候,需要进行扫描全表。由于锁的粒度小,写操作是不会锁定全表的,所以在并发度较高的场景下使用会提升效率的。

MyIASM 引擎:MySQL 的默认引擎,但不提供事务的支持,也不支持行级锁和外键。因此当执行插入和更新语句时,即执行写操作的时候需要锁定这个表,所以会导致效率会降低。不过和 InnoDB 不同的是,MyIASM 引擎是保存了表的行数,于是当进行 select count() from table 语句时,可以直接的读取已经保存的值而不需要进行扫描全表。所以,如果表的读操作远远多于写操作时,并且不需要事务的支持的,可以将 MyIASM 作为数据库引擎的首选。