可能是全网最好的MySQL重要知识点、面试题总结

什么是MySQL?

MySQL是一种关系型数据库,在Java企业级开发中非常常用,因为MySQL是开源免费的,并且方便扩展。阿里巴巴数据库系统也大量用到了MySQL,因此它的稳定性是有保障的。MySQL是开放源代码的,因此任何人都可以在GPL(通用公共许可证)的许可下下载并根据个性化的需要进行修改。MySQL的交替端口号是3306

事务相关

什么是事务?

事务是逻辑上的一组操作,要么都执行,或者都不执行。

如果小小要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误范例银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。要失败。

事物的四大特性(ACID)介绍一下?

事物的特性

  1. 原子性:  事务是最小的执行单位,可以分割。事务的原子性确保动作完全完成,从而完全不起作用;

  2. 一致性:  执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;

  3. 隔离性:  并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;

  4. 持久性:  一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该发生任何影响。

并发事务带来什么问题?

在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对统一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。

  • 脏读(Dirty read):  当一个事务正在访问数据和对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”进行的操作可能是不正确的。

  • 丢失修改(丢失):  指在一个事务读取一个数据时,另外一个事务也访问了该数据,然后在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样的第一个事务内部的修改结果就被丢失,因此称为丢失修改。例如:事务1读取某表中的数据A = 20,事务2也读取A = 20,事务1修改A = A- 1,事务2也修改A = A-1,最终结果A = 19,事务1的修改被丢失。

  • 不可重复读(Unrepeatableread):  指在一个事务内部多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的多个读数据之间,由于第二个事务的修改导致第一个事务重新插入的数据可能不太相同。然后发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。

  • 幻读(Phantom read):  幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

不可重复度和幻读区别:

不可重复读的重点是修改,幻读的重点在于添加或删除。

例1(同样的条件,你读取过的数据,再次重新出来发现值不一样了):事务1中的A先生读取自己的工资为1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导致A再读自己的工资时工资换算2000;这就是不可重复读。

例2(同样的条件,第1次和第2次纠正来的记录数不一样):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2又插入了一条工资大于3000的记录,事务1再读时查到的记录就变成了5条,这样就导致了幻读。

事务隔离等级有什么?MySQL的最小隔离等级是?

SQL标准定义了四个隔离等级:

  • READ-UNCOMMITTED(读取未提交):  最低的隔离级别,允许重新读取未提交的数据变更,可能会导致脏读,幻读或不可重复读

  • READ-COMMITTED(读取已提交):  允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读却可能发生

  • REPEATABLE-READ(可重复读):  对同一片段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生

  • SERIALIZABLE(可串行化):  最高的隔离等级,完全服从ACID的隔离等级。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,从而,该等级可以防止脏读,不可重复读以及幻读

6914_101_18uv5wMruvcDkILz

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

mysql> SELECT @@ tx_isolation;

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

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

InnoDB存储引擎在分布式事务的情况下一般将用到SERIALIZABLE(可串行化)隔离等级

索引相关

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

先从MySQL的基本存储结构说起

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

webp


webp

图片

  • 各个数据页可以组成一个双向链表

  • 每个数据页中的记录又可以组成一个单向链表

-每个数据页都会为存储在其里边儿的记录生成一个页面目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的插槽,然后再遍历该插槽对应分组中的记录即可快速找到指定的记录

所以说,如果我们写select * from user where indexname ='xxx'这样没有进行任何优化的sql语句,替换会生成:

  1. 定位到记录所在的页:需要遍历双向链表,找到所在的页

  2. 从所在的页面内中查找相应的记录:由于不是根据主键查询,只能遍历所在页面的单链表了

很明显,在数据量很大的情况下这样查找会很慢!这样的时间复杂度为O(n)。

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

webp


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

webp


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

其实实质上结构就是B +树,B +树作为树的一种实现,能够让我们很快地寻找出对应的记录。

什么是最左预设原则?

MySQL中的索引可以以一定顺序引用多列,这种索引叫作联合索引。如用户表的名称和城市加联合索引就是(name,city),而最左上方原则指的是,如果查询的时候查询条件精确匹配索引的左边连续一列或几列,则此列就可以被用到。如下:

从name = xx和city = xx的用户中选择*;//可以命中索引

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

由于最左上端原则,在创建联合索引时,索引分段的顺序需要考虑分段值去重之后的个数,而不是放的前面。ORDERBY子句也遵循此规则。

请注意避免索引

冗余索引指的是索引的功能相同,能够命中中肯定肯定能命中,然后就是冗余索引如(name,city)和(name)这两个索引就是多余索引,能够命中另外的查询肯定是能够命中中前者的在大多数情况下,都应该尽量扩展现有的索引而不是创建新索引。

MySQLS.7版本后,可以通过查询sys库的schema_redundant_indexes表来查看冗余索引

Mysql如何为表前缀添加索引?

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

ALTER TABLE`table_name`添加主键(`column`)

2.添加UNIQUE(唯一索引)

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

3.添加INDEX(普通索引)

ALTER TABLE`table_name`添加索引index_name(`column`)

4.添加FULLTEXT(全文索引)

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

5.添加多列索引

ALTER TABLE`table_name`添加索引index_name(`column1`,`column2`,`column3`)

存储引擎

一些常用命令

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

mysql>显示引擎;

webp

图片

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

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

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

mysql>显示类似'%storage_engine%'的变量;

查看表的存储引擎

显示表状态,例如“ table_name”;

webp

图片

MyISAM和InnoDB区别

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

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

两者的对比:

  1. 是否支持行级锁:MyISAM只有表级锁(表级锁),而InnoDB支持行级锁(行级锁)和表级锁,可以为行级锁。

  2. 是否确实支持事务和崩溃后的安全恢复:**** MyISAM注释的是性能,每次查询具有原子性,其执行比InnoDB类型转换,但是不提供事务支持。但是InnoDB提供事务支持事务,外部键等高级数据库功能。具有事务(提交),回滚(回滚)和崩溃恢复能力(崩溃恢复功能)的事务安全(事务安全(符合ACID))型表。

  3. 是否支持外键: MyISAM不支持,而InnoDB支持。

  4. 解决高并发事务,MVCC比单纯的加锁更高效; MVCC只在READ COMMITTEDREPEATABLE READ两个隔离等级下工作; MVCC可以使用乐观(乐观)锁和悲观(悲观)锁来实现;各数据库中MVCC实现并不统一。

  5. ......

《 MySQL高级》上面有些句话这样写到:

在很多我们已知场景中,InnoDB的速度都可以让MyISAM望尘莫及,尤其是用到了聚簇索引,或者需要访问的数据都可以加入内存的应用。

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

乐观锁与悲观锁的区别

悲观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会变成直到直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁, Java中synchronizedReentrantLock等独占锁就是悲观锁思想的实现。写锁等,都是在做操作之前先上锁

乐观锁

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java的中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

两种锁的使用场景

从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行重试,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

乐观锁常见的两种实现方式

乐观锁一般会使用版本号机制或CAS算法实现。

1.版本号机制

一般是在数据表中加上一个数据版本号版本划分,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取版本值,在提交更新时,若先前重新到的版本转换到当前数据库中的版本值替代时才更新,否则重试更新操作,直到更新成功。

举一个简单的例子:假设数据库中帐户信息表中有一个版本,则是初始值1;而当前帐户余额分配(余额)为$ 100。

  1. 操作员此时将其纠正(version = 1),并从其帐户余额中扣除50(100- $ 50)。

  2. 在操作员A操作的过程中,操作员B也读入此用户信息(version = 1),并从其帐户余额中扣除20(100- $ 20)。

  3. 操作员A完成了修改工作,将数据版本号加一(version = 2),加上帐户替换后余额(balance = $ 50),提交至数据库更新,此时提交数据版本大于数据库记录当前版本,数据被更新,数据库记录版本更新为2。

  4. 操作员B完成了操作,也将版本号加一(version = 2)试图向数据库提交数据(balance = $ 80),但此时比对数据库记录版本时发现,操作员B提交的数据版本号为2 ,数据库记录当前版本也为2,不满足“提交版本必须大于记录当前版本才能执行更新”的乐观锁策略,因此,操作员B的提交被驳回。

这样,就避免了操作员B用基于version = 1的旧数据修改的结果覆盖操作员A的操作结果的可能。

2. CAS算法

比较和交换(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被大量的情况下实现变量的同步,所以也叫非双重同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数

  • 需要读写的内存值V

  • 进行比较的值A

  • 拟写入的新值B

当且仅当V的值等于A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试

乐观锁的缺点

ABA问题是乐观锁一个常见的问题

1 ABA

如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们可以说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被替换为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的“ ABA”问题。

JDK 1.5以后的AtomicStampedReference 类就提供提供了能力,其中的compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且内部标志是否等于预期标志,如果全部替换,则以原子方式引用和该标志的值设置为给定的更新值。

2循环时间长开支大

自旋CAS(也就是不成功就一直循环执行直到成功)如果持续不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的暂停指令那么效率会有一定的提升,暂停指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间至少具体实现的版本,在某些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(内存顺序冲突)而引起CPU流水线被清空(CPU管道刷新),从而提高CPU的执行效率。

3只能保证一个共享变量的原子操作

但是从JDK 1.5开始,提供了AtomicReference类保证引用引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。所以我们可以使用锁或利用AtomicReference类把多个共享变量合并成一个共享变量来操作。

锁机制与InnoDB锁算法

MyISAM和InnoDB存储引擎使用的锁:

  • MyISAM采用表级锁(表级锁定)。

  • InnoDB支持行级锁(行级锁)和表级锁,替代为行级锁

表级锁和行级锁对比:

  • 表级锁: Mysql中锁定粒度最大的一种锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM和InnoDB引擎都支持表级锁。

  • 行级锁: Mysql中锁定粒度最小的一种锁,仅针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。

InnoDB存储引擎的锁的算法有三种:

  • 记录锁:单个行记录上的锁

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

  • 下一键锁定:记录+间隙锁定一个范围,包含记录本身

相关知识点:

  • innodb对于行的查询使用下键锁定

  • 下一步锁定键解决幻影问题幻读问题

  • 当查询的索引包含唯一属性时,将下一键锁定降级为记录键

  • Gap锁设计的目的是为了阻止多个事务将记录插入到同一范围内,而这会导致幻读问题的产生

  • 有两种方式显式关闭间隙锁:(除了外键约束和唯一性检查外,其余情况仅使用记录锁)A.将事务隔离级别设置为RC B.将参数innodb_locks_unsafe_for_binlog设置为1

大表优化

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

1.限定数据的范围

最小:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内;

2.读/写分离

经典的数据库分解方案,主库负责写,从库负责读;

3.垂直分区

根据数据库里面数据表的相关性进行拆分例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。

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

webp

图片

  • 垂直细分的优点:可以使列数据变小,在查询时减少重新读取的块数,减少I / O次数。体积,垂直分区可以简化表的结构,便于维护。

  • 垂直分割的缺陷:主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。

4.水平分区

****这样的每片数据分散到不同的表或者库中,达到了分散的目的。****水平隔开可以支撑非常大的数据量。

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

webp


需要注意的一点是:分表选定解决了单个表数据过大的问题,但由于表的数据还是在同一台机器上,实际上对于提升MySQL并发能力没有什么意义,所以水平拆分最好分库。拆分水平能够请立即获取iTunes非常大的数据量存储,应用端改造也少,但分片事务难以解决,跨节点加入性能较差,逻辑复杂。“Java的工程师修炼之道》的作者推荐尽量不要对数据进行分片,因为拆分会带来逻辑,部署,运维的各种复杂度,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,请选择客户端分片架构,这样可以减少一次和中间件的网络I / O。下面补充一下数据库分片的两种常见方案:


  • 客户端代理: 分片逻辑在应用端,封装在jar包中,通过修改或封装JDBC层来实现。当当网的Shading -JDBC,阿里的TDDL是两种比较常用的实现。

  • 中间件代理: 。在应用和数据中间加了一个代理层****片分逻辑统一维护在中间件服务中我们现在谈的Mycat,360的阿特拉斯,网易的DDB等等都是这种架构的实现。

话不多说,读者福利!

二,面试合集精选

关注我加微信“ haolagui521”注释脉脉领取以上架构视频,一些电子书籍,面试文档。

2020到来!一到五年Java工程师想跳槽,大环境不好,怎么破?


三,p6〜p7面试合集

关注我加微信“ haolagui521”注释脉脉领取以上架构视频,一些电子书籍,面试文档。

2020到来!一到五年Java工程师想跳槽,大环境不好,怎么破?


四,架构师进阶合集

关注我加微信“ haolagui521”注释脉脉领取以上架构视频,一些电子书籍,面试文档。

2020到来!一到五年Java工程师想跳槽,大环境不好,怎么破?


五,在线技术书籍

关注我加微信“ haolagui521”注释脉脉领取以上架构视频,一些电子书籍,面试文档。

2020到来!一到五年Java工程师想跳槽,大环境不好,怎么破?


关注我加微信“ haolagui521”注释脉脉领取以上架构视频,一些电子书籍,面试文档。



猜你喜欢

转载自blog.51cto.com/14455981/2464476