标题有点标题党的意思,看了文章以后但愿你们不会有这个想法,绝对干货!!!这篇花文章是我花了几天时间对以前总结的MySQL知识点作了完善后的产物,这篇文章能够用来回顾MySQL基础知识以及备战MySQL常见面试问题。java
文末有公众号二维码,欢迎关注获取笔主最新更新文章,并可免费获取笔主总结的《Java面试突击》以及Java工程师必备学习资源。mysql
MySQL 是一种关系型数据库,在Java企业级开发中很是经常使用,由于 MySQL 是开源免费的,而且方便扩展。阿里巴巴数据库系统也大量用到了 MySQL,所以它的稳定性是有保障的。MySQL是开放源代码的,所以任何人均可以在 GPL(General Public License) 的许可下下载并根据个性化的须要对其进行修改。MySQL的默认端口号是3306。面试
事务是逻辑上的一组操做,要么都执行,要么都不执行。算法
事务最经典也常常被拿出来讲例子就是转帐了。假如小明要给小红转帐1000元,这个转帐会涉及到两个关键操做就是:将小明的余额减小1000元,将小红的余额增长1000元。万一在这两个操做之间忽然出现错误好比银行系统崩溃,致使小明余额减小而小红的余额没有增长,这样就不对了。事务就是保证这两个关键操做要么都成功,要么都要失败。sql
在典型的应用程序中,多个事务并发运行,常常会操做相同的数据来完成各自的任务(多个用户对统一数据进行操做)。并发虽然是必须的,但可能会致使如下的问题。数据库
不可重复度和幻读区别:编程
不可重复读的重点是修改,幻读的重点在于新增或者删除。segmentfault
例1(一样的条件, 你读取过的数据, 再次读取出来发现值不同了 ):事务1中的A先生读取本身的工资为 1000的操做还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读本身的工资时工资变为 2000;这就是不可重复读。安全
例2(一样的条件, 第1次和第2次读出来的记录数不同 ):假某工资单表中工资大于3000的有4人,事务1读取了全部工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就致使了幻读。性能优化
SQL 标准定义了四个隔离级别:
隔离级别 | 脏读 | 不可重复读 | 幻影读 |
---|---|---|---|
READ-UNCOMMITTED | √ | √ | √ |
READ-COMMITTED | × | √ | √ |
REPEATABLE-READ | × | × | √ |
SERIALIZABLE | × | × | × |
MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)。咱们能够经过SELECT @@tx_isolation;
命令来查看
mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
复制代码
这里须要注意的是:与 SQL 标准不一样的地方在于InnoDB 存储引擎在 **REPEATABLE-READ(可重读)事务隔离级别下使用的是Next-Key Lock 锁算法,所以能够避免幻读的产生,这与其余数据库系统(如 SQL Server)是不一样的。因此说InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) 已经能够彻底保证事务的隔离性要求,即达到了 SQL标准的SERIALIZABLE(可串行化)**隔离级别。
由于隔离级别越低,事务请求的锁越少,因此大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,可是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)**并不会有任何性能损失。
InnoDB 存储引擎在 分布式事务 的状况下通常会用到**SERIALIZABLE(可串行化)**隔离级别。
如下内容整理自: 地址: juejin.im/post/5b55b8… 做者 :Java3y
先从 MySQL 的基本存储结构提及
MySQL的基本存储结构是页(记录都存在页里边):
因此说,若是咱们写select * from user where indexname = 'xxx'这样没有进行任何优化的sql语句,默认会这样作:
很明显,在数据量很大的状况下这样查找会很慢!这样的时间复杂度为O(n)。
索引作了些什么可让咱们查询加快速度呢?其实就是将无序的数据变成有序(相对):
要找到id为8的记录简要步骤:
很明显的是:没有用索引咱们是须要遍历双向链表来定位对应的页,如今经过 “目录” 就能够很快地定位到对应的页上了!(二分查找,时间复杂度近似为O(logn))
其实底层结构就是B+树,B+树做为树的一种实现,可以让咱们很快地查找出对应的记录。
如下内容整理自:《Java工程师修炼之道》
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子句也遵循此规则。
冗余索引指的是索引的功能相同,可以命中就确定能命中 ,那么 就是冗余索引如(name,city )和(name )这两个索引就是冗余索引,可以命中后者的查询确定是可以命中前者的 在大多数状况下,都应该尽可能扩展已有的索引而不是建立新索引。
MySQLS.7 版本后,能够经过查询 sys 库的 schema_redundant_indexes
表来查看冗余索引
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` )
复制代码
查看MySQL提供的全部存储引擎
mysql> show engines;
复制代码
从上图咱们能够查看出 MySQL 当前默认的存储引擎是InnoDB,而且在5.7版本全部的存储引擎中只有 InnoDB 是事务性存储引擎,也就是说只有 InnoDB 支持事务。
查看MySQL当前默认的存储引擎
咱们也能够经过下面的命令查看默认的存储引擎。
mysql> show variables like '%storage_engine%';
复制代码
查看表的存储引擎
show table status like "table_name" ;
复制代码
MyISAM是MySQL的默认数据库引擎(5.5版以前)。虽然性能极佳,并且提供了大量的特性,包括全文索引、压缩、空间函数等,但MyISAM不支持事务和行级锁,并且最大的缺陷就是崩溃后没法安全恢复。不过,5.5版本以后,MySQL引入了InnoDB(事务性数据库引擎),MySQL 5.5版本后默认的存储引擎为InnoDB。
大多数时候咱们使用的都是 InnoDB 存储引擎,可是在某些状况下使用 MyISAM 也是合适的好比读密集的状况下。(若是你不介意 MyISAM 崩溃回复问题的话)。
二者的对比:
READ COMMITTED
和 REPEATABLE READ
两个隔离级别下工做;MVCC可使用 乐观(optimistic)锁 和 悲观(pessimistic)锁来实现;各数据库中MVCC实现并不统一。推荐阅读:MySQL-InnoDB-MVCC多版本并发控制《MySQL高性能》上面有一句话这样写到:
不要轻易相信“MyISAM比InnoDB快”之类的经验之谈,这个结论每每不是绝对的。在不少咱们已知场景中,InnoDB的速度均可以让MyISAM可望不可即,尤为是用到了聚簇索引,或者须要访问的数据均可以放入内存的应用。
通常状况下咱们选择 InnoDB 都是没有问题的,可是某事状况下你并不在意可扩展能力和并发能力,也不须要事务支持,也不在意崩溃后的安全恢复问题的话,选择MyISAM也是一个不错的选择。可是通常状况下,咱们都是须要考虑到这些问题的。
老是假设最坏的状况,每次去拿数据的时候都认为别人会修改,因此每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了不少这种锁机制,好比行锁,表锁等,读锁,写锁等,都是在作操做以前先上锁。Java中synchronized
和ReentrantLock
等独占锁就是悲观锁思想的实现。
老是假设最好的状况,每次去拿数据的时候都认为别人不会修改,因此不会上锁,可是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样能够提升吞吐量,像数据库提供的相似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic
包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
从上面对两种锁的介绍,咱们知道两种锁各有优缺点,不可认为一种好于另外一种,像乐观锁适用于写比较少的状况下(多读场景),即冲突真的不多发生的时候,这样能够省去了锁的开销,加大了系统的整个吞吐量。但若是是多写的状况,通常会常常产生冲突,这就会致使上层应用会不断的进行retry,这样反却是下降了性能,因此通常多写的场景下用悲观锁就比较合适。
乐观锁通常会使用版本号机制或CAS算法实现。
通常是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,不然重试更新操做,直到更新成功。
举一个简单的例子: 假设数据库中账户信息表中有一个 version 字段,当前值为 1 ;而当前账户余额字段( balance )为 $100 。
这样,就避免了操做员 B 用基于 version=1 的旧数据修改的结果覆盖操做员A 的操做结果的可能。
即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的状况下实现多线程之间的变量同步,也就是在没有线程被阻塞的状况下实现变量的同步,因此也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操做数
当且仅当 V 的值等于 A时,CAS经过原子方式用新值B来更新V的值,不然不会执行任何操做(比较和替换是一个原子操做)。通常状况下是一个自旋操做,即不断的重试。
关于自旋锁,你们能够看一下这篇文章,很是不错:《 面试必备之深刻理解自旋锁》
ABA 问题是乐观锁一个常见的问题
若是一个变量V初次读取的时候是A值,而且在准备赋值的时候检查到它仍然是A值,那咱们就能说明它的值没有被其余线程修改过了吗?很明显是不能的,由于在这段时间它的值可能被改成其余值,而后又改回A,那CAS操做就会误认为它历来没有被修改过。这个问题被称为CAS操做的 "ABA"问题。
JDK 1.5 之后的 AtomicStampedReference 类
就提供了此种能力,其中的 compareAndSet 方法
就是首先检查当前引用是否等于预期引用,而且当前标志是否等于预期标志,若是所有相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
自旋CAS(也就是不成功就一直循环执行直到成功)若是长时间不成功,会给CPU带来很是大的执行开销。 若是JVM能支持处理器提供的pause指令那么效率会有必定的提高,pause指令有两个做用,第一它能够延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它能够避免在退出循环的时候因内存顺序冲突(memory order violation)而引发CPU流水线被清空(CPU pipeline flush),从而提升CPU的执行效率。
CAS 只对单个共享变量有效,当操做涉及跨多个共享变量时 CAS 无效。可是从 JDK 1.5开始,提供了AtomicReference类
来保证引用对象之间的原子性,你能够把多个变量放在一个对象里来进行 CAS 操做.因此咱们可使用锁或者利用AtomicReference类
把多个共享变量合并成一个共享变量来操做。
MyISAM和InnoDB存储引擎使用的锁:
表级锁和行级锁对比:
详细内容能够参考: Mysql锁机制简单了解一下
InnoDB存储引擎的锁的算法有三种:
相关知识点:
当MySQL单表记录数过大时,数据库的CRUD性能会明显降低,一些常见的优化措施以下:
务必禁止不带任何限制数据范围条件的查询语句。好比:咱们当用户在查询订单历史的时候,咱们能够控制在一个月的范围内;
经典的数据库拆分方案,主库负责写,从库负责读;
根据数据库里面数据表的相关性进行拆分。 例如,用户表中既有用户的登陆信息又有用户的基本信息,能够将用户表拆分红两个单独的表,甚至放到单独的库作分库。
简单来讲垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。 以下图所示,这样来讲你们应该就更容易理解了。
保持数据表结构不变,经过某种策略存储数据分片。这样每一片数据分散到不一样的表或者库中,达到了分布式的目的。 水平拆分能够支撑很是大的数据量。
水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时能够把一张的表的数据拆成多张表来存放。举个例子:咱们能够将用户信息表拆分红多个用户信息表,这样就能够避免单一表数据量过大对性能形成影响。
水平拆分能够支持很是大的数据量。须要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但因为表的数据仍是在同一台机器上,其实对于提高MySQL并发能力没有什么意义,因此 水平拆分最好分库 。
水平拆分可以 支持很是大的数据量存储,应用端改造也少,但 分片事务难以解决 ,跨节点Join性能较差,逻辑复杂。《Java工程师修炼之道》的做者推荐 尽可能不要对数据进行分片,由于拆分会带来逻辑、部署、运维的各类复杂度 ,通常的数据表在优化得当的状况下支撑千万如下的数据量是没有太大问题的。若是实在要分片,尽可能选择客户端分片架构,这样能够减小一次和中间件的网络I/O。
下面补充一下数据库分片的两种常见方案:
详细内容能够参考: MySQL大表优化方案
腾讯面试:一条SQL语句执行得很慢的缘由有哪些?---不看后悔系列
若是你们想要实时关注我更新的文章以及分享的干货的话,能够关注个人公众号。
《Java面试突击》: 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本公众号后台回复 "Java面试突击" 便可免费领取!
Java工程师必备学习资源: 一些Java工程师经常使用学习资源公众号后台回复关键字 “1” 便可免费无套路获取。