大家好,又见面了,我是你们的朋友全栈君。
目录
1.事务的基本要素
2.事务隔离级别(必考)
3.如何解决事务的并发问题(脏读,幻读)(必考)
脏读的表现和具体解决并发问题
不可重复读/ 幻读 的表现和具体解决并发问题
4.MVCC多版本并发控制(必考)
什么是多版本并发控制呢?
InnoDB的MVCC实现机制
5.为什么选择B 树作为索引结构(必考)
补充问题:
为什么平衡二叉树(或红黑树)不适合作为索引?
补充知识:
索引相关原理和知识
一、索引原理
二、磁盘IO与预读
三、索引的数据结构
四、B 树的查找过程
五、B 树性质
索引使用注意事项与数据类型选择
一、索引使用注意事项
二、选择索引的数据类型
三、MySQL常见索引有:主键索引、唯一索引、普通索引、全文索引、组合索引
6.索引B 树的叶子节点都可以存哪些东西(或问聚簇索引与非聚簇索引的区别?)(必考)
补充问题:
InnoDB一棵B 树可以存放多少行数据?
MyisAM索引与InnoDB索引相比较
7.查询在什么时候不走(预期中的)索引(必考)
8.sql如何优化
补充问题:
SQL执行顺序是什么样的?
9.explain是如何解析sql的
10.order by原理
11.InnoDB的行锁/表锁
12.myisam和innodb的区别,什么时候选择myisam
13.binlog,redolog,undolog都是什么,起什么作用
14.数据库的乐观锁与悲观锁的区别是什么?乐观锁常用的两种实现方式是什么?
补充两个基本的问题:
15.当前读和快照读
16.一条sql的执行过程?
参考书籍、文献和资料
备注:针对基本问题做一些基本的总结,不是详细解答!
1.事务的基本要素
数据库事务(Transanction)正确执行的四个基本要素:
- 原子性(Atomicity):事务开始后所有操作,要么全部完成,要么全部不完成,不可能停滞在中间环节。事务执行过程中出错,会回滚(Rollback)到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位
- 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到
- 隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账
- 持久性(Durability):事务完成后,该事务所对数据库所作的更改将被保存到数据库之中,不能回滚
2.事务隔离级别(必考)
前提知识:事务的并发问题
- 脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
- 不可重复读:事务A多次读取同一数据,事务B在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。
- 幻读:A事务读取了B事务已经提交的新增数据。注意和不可重复读的区别,这里是新增,不可重复读是更改(或删除)。select某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读。
结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。
隔离级别
事务的隔离级别分为:未提交读(read uncommitted)、已提交读(read committed)、可重复读(repeatable read)、串行化(serializable)。
- 未提交读:A事务已执行,但未提交;B事务查询到A事务的更新后数据;A事务回滚;—出现脏数据
- 已提交读:A事务执行更新;B事务查询;A事务又执行更新;B事务再次查询时,前后两次数据不一致;—不可重复读
- 可重复读:A事务无论执行多少次,只要不提交,B事务查询值都不变;B事务仅查询B事务开始时那一瞬间的数据快照;
- 串行化:不允许读写并发操作,写执行时,读必须等待;
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read-uncommitted) | 是 | 是 | 是 |
不可重复读(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
在MySQL可重复读的隔离级别中并不是完全解决了幻读的问题,而是解决了读数据情况下的幻读问题。而对于修改的操作依旧存在幻读问题,就是说MVCC对于幻读的解决时不彻底的。 通过索引加锁,间隙锁,next key lock可以解决幻读的问题。
3.如何解决事务的并发问题(脏读,幻读)(必考)
注意:要想解决脏读、不可重复读、幻读等读现象,那么就需要提高事务的隔离级别。但与此同时,事务的隔离级别越高,并发能力也就越低。
为了解决上述问题,数据库通过锁机制解决并发访问的问题。
- 根据锁定对象不同:分为行级锁和表级锁;
- 根据并发事务锁定的关系上看:分为共享锁定和独占锁定,共享锁定会防止独占锁定但允许其他的共享锁定。而独占锁定既防止共享锁定也防止其他独占锁定。为了更改数据,数据库必须在进行更改的行上施加行独占锁定,insert、update、delete和select for update语句都会隐式采用必要的行锁定。
但是直接使用锁机制管理是很复杂的,基于锁机制,数据库给用户提供了不同的事务隔离级别。
脏读的表现和具体解决并发问题
一个事务读取到了另外一个事务没有提交的数据。
详细解释:脏读就是指:当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
解决:
修改时加排他锁,直到事务提交后才释放;
读取时加共享锁,读取完释放事务1读取数据时加上共享锁后(这样在事务1读取数据的过程中,其他事务就不会修改该数据),不允许任何事物操作该数据,只能读取,之后1如果有更新操作,那么会转换为排他锁,其他事务更无权参与进来读写,这样就防止了脏读问题。
但是当事务1读取数据过程中,有可能其他事务也读取了该数据,读取完毕后共享锁释放,此时事务1修改数据,修改完毕提交事务,其他事务再次读取数据时候发现数据不一致,就会出现不可重复读问题,所以这样不能够避免不可重复读问题。
不可重复读/ 幻读 的表现和具体解决并发问题
不可重复读:在同一事务中,两次读取同一数据,得到内容不同
幻读:同一事务中,用同样的操作读取两次,得到的记录数不相同
解决:
读取数据时加共享锁,写数据时加排他锁,都是事务提交才释放锁。读取时候不允许其他事物修改该数据,不管数据在事务过程中读取多少次,数据都是一致的,避免了不可重复读问题
4.MVCC多版本并发控制(必考)
MVCC的全称就是多版本并发控制。这项技术使得InnoDB的事务隔离级别下执行一致性读操作有了保证。换言之,为了查询在一些正在被另外一个事务更新的行,并且可以看到它们被更新之前的值。这是一个可以用来增强并发性的强大技术,因为这样一来的话查询就不用等待另一个事务释放锁。这项技术在数据库领域并不是普遍使用的。MVVC最大的好处是:读不加锁,读写不冲突。在读多写少的应用中,读写不冲突非常重要,极大的增加了系统的并发性。InnoDB使用的是行锁,并且采用了多版本并发控制来提高读操作的性能。
什么是多版本并发控制呢?
其实就是在每一行记录的后面增加了两个隐藏列,记录创建版本号和删除版本号。而每一个事务开启的时候,都会有唯一的递增版本号,被操作的数据会生成一条新的数据行(临时),但是在提交前对其他事务是不可见的,对于数据的更新(包括增删改)操作成功,会将这个版本号更新到数据的行中,事务提交成功,将新的版本号更新到此数据行中。这样保证了每个事务操作的数据,都是互不影响,也不存在锁的问题。
InnoDB的MVCC实现机制
MVCC可以认为是行级锁的一个变种,它可以在很多情况下避免加锁操作,因此开销更低。MVCC的实现大都都实现了非阻塞的读操作,写操作也只锁定必要的行。InnoDB的MVCC实现,是通过保存数据在某个时间点的快照来实现的。一个事务,不管其执行多长时间,其内部看到的数据是一致的,也就是事务在执行的过程中不会相互影响。
简述一下MVCC在InnoDB中的实现:
InnoDB的MVCC,通过在每行记录后面保存两个隐藏的列来实现:一个保存了行的创建时间,一个保存行的过期时间(删除时间),当然,这里的时间并不是时间戳,而是系统版本号,每开始一个新的事务,系统版本号就会递增。在RR隔离级别下,MVCC的操作如下:
- select操作:
- InnoDB只查找版本早于(包含等于)当前事务版本的数据行。可以确保事务读取的行,要么是事务开始前就已存在,或者事务自身插入或修改的记录。
- 行的删除版本要么未定义,要么大于当前事务版本号。可以确保事务读取的行,在事务开始之前未删除。
- insert操作:将新插入的行保存当前版本号为行版本号。
- delete操作:将删除的行保存当前版本号为删除标识。
- update操作:变为insert和delete操作的组合,insert的行保存当前版本号为行版本号,delete则保存当前版本号到原来的行作为删除标识。
由于旧数据并不真正的删除,所以必须对这些数据进行清理,innodb会开启一个后台线程执行清理工作,具体的规则是将删除版本号小于当前系统版本的行删除,这个过程叫做purge。
5.为什么选择B 树作为索引结构(必考)
Inodb存储引擎 默认是 B Tree索引;MyISAM 存储引擎 默认是Fulltext索引;Memory 存储引擎 默认 Hash索引;
- B树:有序数组 平衡多叉树;
- B 树:有序数组链表 平衡多叉树;
B 树(叶节点保存数据,其他的节点 全部存放索引),数据库索引采用B 树的主要原因是B树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。正是为了解决这个问题,B 树应运而生。 B 树只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作(或者说效率太低)。
正如上面所说,在数据库中基于范围的查询是非常频繁的,因此MySQL最终选择的索引结构是B 树而不是B树。
注:与其他数据结构的对比:
- Hash索引:Hash索引底层是哈希表,哈希表是一种以key-value存储数据的结构,所以多个数据在存储关系上是完全没有任何顺序关系的,所以,对于区间查询是无法直接通过索引查询的,就需要全表扫描。所以,哈希索引只适用于等值查询的场景。而B 树是一种多路平衡查询树,所以他的节点是天然有序的(左子节点小于父节点、父节点小于右子节点),所以对于范围查询的时候不需要做全表扫描
- 二叉查找树:解决了排序的基本问题,但是由于无法保证平衡,可能退化为链表。
- 平衡二叉树:通过旋转解决了平衡的问题,但是旋转操作效率太低。
- 红黑树:通过舍弃严格的平衡和引入红黑节点,解决了 AVL旋转效率过低的问题,但是在磁盘等场景下,树仍然太高,IO次数太多。
- B 树:在B树的基础上,将非叶节点改造为不存储数据纯索引节点,进一步降低了树的高度;此外将叶节点使用指针连接成链表,范围查询更加高效。
补充问题:
为什么平衡二叉树(或红黑树)不适合作为索引?
索引是存在于索引文件中,是存在于磁盘中的。因为索引通常是很大的,因此无法一次将全部索引加载到内存当中,因此每次只能从磁盘中读取一个磁盘页的数据到内存中。而这个磁盘的读取的速度较内存中的读取速度而言是差了好几个级别。
注意,我们说的平衡二叉树结构,指的是逻辑结构上的平衡二叉树,其物理实现是数组。然后由于在逻辑结构上相近的节点在物理结构上可能会差很远。因此,每次读取的磁盘页的数据中有许多是用不上的。因此,查找过程中要进行许多次的磁盘读取操作。
而适合作为索引的结构应该是尽可能少的执行磁盘IO操作,因为执行磁盘IO操作非常的耗时。因此,平衡二叉树并不适合作为索引结构。B树的查询,主要发生在内存中,而平衡二叉树的查询,则是发生在磁盘读取中。因此,虽然B树查询查询的次数不比平衡二叉树的次数少,但是相比起磁盘IO速度,内存中比较的耗时就可以忽略不计了。
补充知识:
索引相关原理和知识
详细将查看:https://www.cnblogs.com/aspirant/p/9214485.html
一、索引原理
索引的目的在于提高查询效率,与我们查阅图书所用的目录是一个道理:先定位到章,然后定位到该章下的一个小节,然后找到页数。相似的例子还有:查字典,查火车车次,飞机航班等
本质都是:通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,我们可以总是用同一种查找方式来锁定数据。
索引(Index)是帮助数据库高效获取数据的数据结构。索引是在基于数据库表创建的,它包含一个表中某些列的值以及记录对应的地址,并且把这些值存储在一个数据结构中。最常见的就是使用哈希表、B 树作为索引。
一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,在生产环境中,我们遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,因此对查询语句的优化显然是重中之重。说起加速查询,就不得不提到索引了。
数据库查询是数据库最主要的功能之一。而查询速度当然是越快越好。而当数据量越来越大的时候,查询花费的时间会随之增长。而索引,可以加速数据的查询。因为索引是有序排列的。
二、磁盘IO与预读
考虑到磁盘IO是非常高昂的操作,计算机操作系统做了一些优化,当一次IO时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,因为局部预读性原理告诉我们,当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到。每一次IO读取的数据我们称之为一页(page)。具体一页有多大数据跟操作系统有关,一般为4k或8k,也就是我们读取一页内的数据时候,实际上才发生了一次IO,这个理论对于索引的数据结构设计非常有帮助。
局部性原理与磁盘预读:由于存储介质的特性,磁盘本身存取就比主存慢很多,再加上机械运动耗费,磁盘的存取速度往往是主存的几百分分之一,因此为了提高效率,要尽量减少磁盘I/O。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。这样做的理论依据是计算机科学中著名的局部性原理: 当一个数据被用到时,其附近的数据也通常会马上被使用。 程序运行期间所需要的数据通常比较集中。 由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间),因此对于具有局部性的程序来说,预读可以提高I/O效率。
三、索引的数据结构
任何一种数据结构都不是凭空产生的,一定会有它的背景和使用场景,现在总结一下,我们需要这种数据结构能够做些什么,其实很简单,那就是:每次查找数据时把磁盘IO次数控制在一个很小的数量级,最好是常数数量级。那么我们就想到如果一个高度可控的多路搜索树是否能满足需求呢?就这样,b 树应运而生。
如上图,是一颗b 树,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1、P2、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。
四、B 树的查找过程
如图所示,如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。真实的情况是,3层的b 树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。
五、B 树性质
- 索引字段要尽量的小
通过上面的分析,我们知道IO次数取决于b 数的高度h,假设当前数据表的数据为N,每个磁盘块的数据项的数量是m,则有h=㏒(m 1)N,当数据量N一定的情况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比如int占4字节,要比bigint8字节少一半。这也是为什么b 树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下降,导致树增高。当数据项等于1时将会退化成线性表。
- 索引的最左匹配特性(即从左往右匹配)
当b 树的数据项是复合的数据结构,比如(name,age,sex)的时候,b 数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b 树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b 树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b 树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。
这也是经常考察的,比如 我定义了 A,B,C的联合索引,如果 我只传递了 A,B 能走索引吗?答案是能,因为最左侧原理(百度问过)
索引使用注意事项与数据类型选择
一、索引使用注意事项
- 不要滥用索引:索引提高查询速度,却会降低更新表的速度,因为更新表时,mysql不仅要更新数据,保存数据,还要更新索引,保存索引;索引会占用磁盘空间
- 索引不会包含含有NULL值的列:复合索引只要有一列含有NULL值,那么这一列对于此符合索引就是无效的,因此我们在设计数据库设计时不要让字段的默认值为NULL。
- MySQL查询只是用一个索引:如果where字句中使用了索引的话,那么order by中的列是不会使用索引的
- like:like ‘�a%’不会使用索引而like “aaa%”可以使用索引
二、选择索引的数据类型
Mysql支持很多数据类型,选择合适的数据类型存储数据对性能有很大的影响。
- 越小的数据类型通常更好:越小的数据类型通常在磁盘、内存和cpu缓存中都需要更少的空间,处理起来更快。
- 简单的数据类型更好:整形数据比起字符,处理开销更小,因为字符串的比较更复杂。在MySQL中,应用内置的日期和时间数据类型,而不是字符串来存储时间;以及用整形数据存储IP地址。
- 尽量避免NULL:应该制定列为NOT NULL,除非你想存储NULL。在MySQL中,含有空值的列很难进行查询优化,因为他们使得索引、索引的统计信息以及比较运算更加复杂。
三、MySQL常见索引有:主键索引、唯一索引、普通索引、全文索引、组合索引
- INDEX(普通索引):ALTER TABLE ‘table_name’ ADD INDEX index_name(‘col’)最基本的索引,没有任何限制
- UNIQUE(唯一索引):ALTER TABLE ‘table_name’ ADD UNIQUE(‘col’),与“普通索引”类似,不同的就是:索引列的值必须唯一,但允许有空值。
- PRIMARY KEY(主键索引):ALTER TABLE ‘table_name’ ADD PRIMARY KEY(‘col’) 是一种特殊的唯一索引,不允许有空值。
- FULLTEXT(全文索引):ALTER TABLE ‘table_name’ ADD FULLTEXT(‘col’),仅可用于MyISAM和InoDB,针对较大的数据,生成全文索引很耗时耗空间
- 组合索引:ALTER TABLE ‘table_name’ ADD INDEX index_name(‘col1′,’col2′,’col3’)
为了更多的提高mysql效率可建立组合索引,遵循“最左前缀”原则。创建复合索引应该将最常用(频率)做限制条件的列放在最左边,一次递减。组合索引最左字段用in是可以用到索引的。相当于建立了col1,col1col2,col1col2col3三个索引。
6.索引B 树的叶子节点都可以存哪些东西(或问聚簇索引与非聚簇索引的区别?)(必考)
可能存储的是整行数据,也有可能是主键的值。
B 树的叶子节点存储了整行数据的是主键索引,也被称之为聚簇索引。而索引B Tree的叶子节点存储了主键的值的是非主键索引,也被称之为非聚簇索引。
- 聚簇索引
所谓聚簇索引,就是指主索引文件和数据文件为同一份文件,聚簇索引主要用在Innodb存储引擎中。在该索引实现方式中B Tree的叶子节点上的data就是数据本身,key为主键,如果是一般索引的话,data便会指向对应的主索引。
- 非聚簇索引
非聚簇索引就是指B Tree的叶子节点上的data,并不是数据本身,而是数据存放的地址。主索引和辅助索引没啥区别,只是主索引中的key一定得是唯一的,主要用在MyISAM存储引擎中。非聚簇索引比聚簇索引多了一次读取数据的IO操作,所以查找性能上会差。
- 覆盖索引
指一个查询语句的执行只用从索引中就能够取得,不必从数据表中读取。也可以称之为实现了索引覆盖。
注意: MySQL InnoDB一定会建立聚簇索引,把实际数据行和相关的键值保存在一块,这也决定了一个表只能有一个聚簇索引,即MySQL不会一次把数据行保存在二个地方。
- InnoDB通常根据主键值(primary key)进行聚簇
- 如果没有创建主键,则会用一个唯一且不为空的索引列做为主键,成为此表的聚簇索引
- 上面二个条件都不满足,InnoDB会自己创建一个虚拟的聚集索引
- 正因为InnoDB将数据保存在一处,因此其插入速度严重依赖插入顺序。按照主键顺序插入无疑是最快的。如果不是按照主键插入,建议加载完成后最好使用OPTIMIZE TABLE重新组织一下表。
补充问题:
InnoDB一棵B 树可以存放多少行数据?
这个问题的简单回答是:约2千万
在计算机中磁盘存储数据最小单元是扇区,一个扇区的大小是512字节,而文件系统(例如XFS/EXT4)的最小单元是块,一个块的大小是4k。
而对于我们的InnoDB存储引擎也有自己的最小储存单元——页(Page),一个页的大小是16K。InnoDB的所有数据文件(后缀为ibd的文件),他的大小始终都是16384(16k)的整数倍。
假设主键ID为bigint类型,长度为8字节,而指针大小在InnoDB源码中设置为6字节,这样一共14字节,一个页中能存放多少这样的单元,其实就代表有多少指针,即16384/14=1170。
那么可以算出一棵高度为2的B 树,能存放1170*16=18720条这样的数据记录。
根据同样的原理我们可以算出一个高度为3的B 树可以存放:1170*1170*16=21902400条这样的记录。所以在InnoDB中B 树高度一般为1-3层,它就能满足千万级的数据存储。
MyisAM索引与InnoDB索引相比较
- MyisAM支持全文索引(FULLTEXT)、压缩索引,InnoDB不支持;
- InnoDB支持事务,MyisAM不支持;
- MyisAM顺序储存数据,索引叶子节点保存对应数据行地址,辅助索引很主键索引相差无几;InnoDB主键节点同时保存数据行,其他辅助索引保存的是主键索引的值;
- MyisAM键值分离,索引载入内存(key_buffer_size),数据缓存依赖操作系统;InnoDB键值一起保存,索引与数据一起载入InnoDB缓冲池;MyisAM主键(唯一)索引按升序来存储存储,InnoDB则不一定
- MyisAM索引的基数值(Cardinality,show index 命令可以看见)是精确的,InnoDB则是估计值。这里涉及到信息统计的知识,MyisAM统计信息是保存磁盘中,在alter表或Analyze table操作更新此信息,而InnoDB则是在表第一次打开的时候估计值保存在缓存区内;
- MyisAM处理字符串索引时用增量保存的方式,如第一个索引是‘preform’,第二个是‘preformence’,则第二个保存是‘7,ance’,这个明显的好处是缩短索引,但是缺陷就是不支持倒序提取索引,必须顺序遍历获取索引
7.查询在什么时候不走(预期中的)索引(必考)
- order by 和 limit 结合使用,遇到此类状况可以考虑用子查询将order by 和 limit 分开。
- DATE_FORMAT()格式化时间,格式化后的时间再去比较,可能会导致索引失效。
- 子查询中order by的索引会失效,同时可能导致子查询中的where条件索引都不能用。
- 字符集的使用导致不走索引,有时你会发现用一个SQL 条件值不同可能会有天的差别
- like语句
- 列类型为字符串类型,查询时没有用单引号引起来
- 在where查询语句中使用表达式
- 在where查询语句中对字段进行NULL值判断
- 在where查询中使用了or关键字, myisam表能用到索引, innodb不行;(用UNION替换OR,可以使用索引)
- 全表扫描快于索引扫描(数据量小时)
8.sql如何优化
更多的应该是在于数据库应用的优化:
- 创建并使用正确的索引
- 只返回需要的字段
- 减少交互次数(批量提交)
- 设置合理的Fetch Size(数据每次返回给客户端的条数)
具体到sql的优化上有很多,这里只展示部分:
- 对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引
- 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
- 应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描
- 应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描
- in 和 not in 也要慎用,否则会导致全表扫描
- 应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描
- 应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描
- 不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引
- 在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致
- 不要写一些没有意义的查询,如需要生成一个空表结构,这类代码不会返回任何结果集,但是会消耗系统资源的
- 很多时候用 exists 代替 in 是一个好的选择
- 并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
- 索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
- 尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。
- 这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
- 尽可能的使用 varchar 代替 char ,因为首先变长字段存储空间小,可以节省存储空间, 其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
- 任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
- 避免频繁创建和删除临时表,以减少系统表资源的消耗。
- 临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
- 在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
- 如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
- 尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
- 使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
- 与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
- 尽量避免大事务操作,提高系统并发能力。
- 尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
补充问题:
SQL执行顺序是什么样的?
SQL的执行顺序:from—where–group by—having—select—order by
9.explain是如何解析sql的
使用 EXPLAIN 关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。这可以帮你分析你的查询语句或是表结构的性能瓶颈。通过explain命令可以得到:
- 表的读取顺序
- 数据读取操作的操作类型
- 哪些索引可以使用
- 哪些索引被实际使用
- 表之间的引用
- 每张表有多少行被优化器查询
10.order by原理
原理分析:
根据MySQL排序原理划分的话,MySQL排序有两种方式,一个是通过有序索引直接返回数据,另一种是通过Filesort进行排序数据。
- 利用索引的有序性获取有序数据
- 利用内存/磁盘文件排序获取结果
- 1) 双路排序:是首先根据相应的条件取出相应的排序字段和可以直接定位行数据的行指针信息,然后在sort buffer 中进行排序。
- 2)单路排序:是一次性取出满足条件行的所有字段,然后在sort buffer中进行排序。
平时判断一条SQL语句用的是哪种排序可以使用Explain或desc SQL语句来查看,在输出信息中Extra字段会具体显示用了哪种排序,如果Extra显示Using index,则表示是通过有序索引直接返回有序数据的。如果显示Using filesort,则表示SQL是通过Filesort进行排序返回数据的。
MySQL 中的 Filesort 并不一定是在磁盘文件中进行排序的,也有可能在内存中排序,内存排序还是磁盘排序取决于排序的数据大小和 sort_buffer_size 配置的大小。
- 如果 “排序的数据大小” < sort_buffer_size: 内存排序。
- 如果 “排序的数据大小” > sort_buffer_size: 磁盘排序。
Filesort排序模式总共有三种模式:
- 双路排序(又叫回表排序模式)< sort_key, rowid >:是首先根据相应的条件取出相应的排序字段和可以直接定位行数据的行 ID,然后在 sort buffer 中进行排序,排序完后需要再次取回其它需要的字段;
- 单路排序< sort_key, additional_fields >:是一次性取出满足条件行的所有字段,然后在sort buffer中进行排序;
- 打包数据排序模式< sort_key, packed_additional_fields >:打包数据排序模式是单路排序的一种升级模式,与单路排序相似,区别是将 char 和 varchar 字段存到 sort buffer 中时,更加紧缩。
MySQL 通过比较系统变量 max_length_for_sort_data 的大小和需要查询的字段总大小来判断使用哪种排序模式。
- 如果 max_length_for_sort_data 比查询字段的总长度大,那么使用 < sort_key, additional_fields >排序模式;
- 如果 max_length_for_sort_data 比查询字段的总长度小,那么使用 <sort_key, rowid> 排序模式。
优化方式:
- 给order by 字段增加索引,orderby的字段必须在最前面设置
- 去掉不必要的返回字段
- 增大 sort_buffer_size 参数设置
11.InnoDB的行锁/表锁
基本认识:
在mysql 的 InnoDB引擎支持行锁,与Oracle不同,mysql的行锁是通过索引加载的,即是行锁是加在索引响应的行上的,要是对应的SQL语句没有走索引,则会全表扫描,行锁则无法实现,取而代之的是表锁。
行锁分共享锁和排它锁。
- 共享锁:读锁。当一个事务对某几行上读锁时,允许其他事务对这几行进行读操作,但不允许其进行写操作,也不允许其他事务给这几行上排它锁,但允许上读锁。
- 排它锁:写锁。当一个事务对某几个上写锁时,不允许其他事务写,但允许读。更不允许其他事务给这几行上任何锁,包括写锁。
上共享锁的写法:lock in share mode 例如: select math from zje where math>60 lock in share mode;
上排它锁的写法:for update 例如:select math from zje where math >60 for update;
两者的比较:
表锁:不会出现死锁,发生锁冲突几率高,并发低。
行锁:会出现死锁,发生锁冲突几率低,并发高。
- 锁冲突:例如说事务A将某几行上锁后,事务B又对其上锁,锁不能共存否则会出现锁冲突。(但是共享锁可以共存,共享锁和排它锁不能共存,排它锁和排他锁也不可以)
- 死锁:例如说两个事务,事务A锁住了1~5行,同时事务B锁住了6~10行,此时事务A请求锁住6~10行,就会阻塞直到事务B施放6~10行的锁,而随后事务B又请求锁住1~5行,事务B也阻塞直到事务A释放1~5行的锁。死锁发生时,会产生Deadlock错误。
锁是对表操作的,所以自然锁住全表的表锁就不会出现死锁。
行锁的实现注意事项:
行锁必须有索引才能实现,否则会自动锁全表,那么就不是行锁了。
两个事务不能锁同一个索引,例如:
insert ,delete , update在事务中都会自动默认加上排它锁。
12.myisam和innodb的区别,什么时候选择myisam
基本的区别:存储结构/存储空间/事物支持/CURD操作/外键
存储结构
- MyISAM:每个MyISAM在磁盘上存储成三个文件。第一个文件的名字以表的名字开始,扩展名指出文件类型。 .frm文件存储表定义。数据文件的扩展名为.MYD(MYD)。索引文件的扩展名是.MYI(MYIndex)。
- InnoDB:所在的表都保存在同一个数据文件中(也可能是多个文件,或者是独立的表空间),InnoDB表的大小只受限于操作系统文件的大小,一般为2GB。
存储空间
- MyISAM:可被压缩,存储空间较小。支持三种不同的存储格式:静态表(默认,但是注意数据末尾不能有空格,会被去掉)、动态表、压缩表。
- InnoDB:需要更多的内存和存储,它会在主内存中建立其专用的缓冲池用于高速缓冲数据和索引。
事物支持
- MyISAM:强调的是性能,每次查询具有原子性,其执行速度比Innodb类型更快,但是不提供事物支持。
- InnoDB:提供事务支持,外部键等高级数据库功能。具有事务(commit)、回滚(rollback)和崩溃修复能力(crach recovery capabilities)的事务安全(transaction-safe ACID compliant)型表。
CURD操作
- MyISAM: 如果执行大量的select, MyISAM是更好的选择。(因为没有支持行级锁),在增删的时候需要锁定整个表格,效率会低一些。相关的是innoDB支持行级锁,删除插入的时候只需要锁定该行就行,效率较高。
- InnoDB:如果你的数据执行大量的insert或update,出于性能方面的考虑,应该使用InnoDB表。Delete从性能上Innodb更优,但delete from table时,InnoDB不会重新建立表,而是一行一行的删除,在innodb上如果要清空保存有大量数据的表,最好使用truncate table这个命令。
外键
- MyISAM: 不支持。
- InoDB:支持。
选择上的考虑: 因为MyISAM相对简单所以在效率上要优于InnoDB。如果系统读多,写少。对原子性要求低,那么MyISAM最好的选择。且MyISAM恢复速度快,可直接用备份覆盖恢复。 如果系统读少,写多的时候,尤其是并发写入高的时候。InnoDB就是首选了。 两种类型都有自己优缺点,选择那个完全要看自己的实际类弄。
13.binlog,redolog,undolog都是什么,起什么作用
- undoLog
也就是我们常说的回滚日志文件,主要用于事务中执行失败,进行回滚,以及MVCC中对于数据历史版本的查看。由引擎层的InnoDB引擎实现,是逻辑日志,记录数据修改被修改前的值,比如”把id=’B’ 修改为id = ‘B2’ ,那么undo日志就会用来存放id =’B’的记录”。当一条数据需要更新前,会先把修改前的记录存储在undolog中,如果这个修改出现异常,,则会使用undo日志来实现回滚操作,保证事务的一致性。当事务提交之后,undo log并不能立马被删除,而是会被放到待清理链表中,待判断没有事物用到该版本的信息时才可以清理相应undolog。它保存了事务发生之前的数据的一个版本,用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读。
- redoLog
是重做日志文件是记录数据修改之后的值,用于持久化到磁盘中。redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。由引擎层的InnoDB引擎实现,是物理日志,记录的是物理数据页修改的信息,比如“某个数据页上内容发生了哪些改动”。当一条数据需要更新时,InnoDB会先将数据更新,然后记录redoLog 在内存中,然后找个时间将redoLog的操作执行到磁盘上的文件上。不管是否提交成功我都记录,你要是回滚了,那我连回滚的修改也记录。它确保了事务的持久性。
MVCC多版本并发控制是MySQL中基于乐观锁理论实现隔离级别的方式,用于读已提交和可重复读取隔离级别的实现。在MySQL中,会在表中每一条数据后面添加两个字段:最近修改该行数据的事务ID,指向该行(undolog表中)回滚段的指针。Read View判断行的可见性,创建一个新事务时,copy一份当前系统中的活跃事务列表。意思是,当前不应该被本事务看到的其他事务id列表。
- binlog
由Mysql的Server层实现,是逻辑日志,记录的是sql语句的原始逻辑,比如”把id=’B’ 修改为id = ‘B2’。binlog会写入指定大小的物理文件中,是追加写入的,当前文件写满则会创建新的文件写入。 产生:事务提交的时候,一次性将事务中的sql语句,按照一定的格式记录到binlog中。用于复制和恢复在主从复制中,从库利用主库上的binlog进行重播(执行日志中记录的修改逻辑),实现主从同步。业务数据不一致或者错了,用binlog恢复。
14.数据库的乐观锁与悲观锁的区别是什么?乐观锁常用的两种实现方式是什么?
基本理解与区别:
数据的锁定分为两种,第一种叫作悲观锁,第二种叫作乐观锁。
- 悲观锁,就是对数据的冲突采取一种悲观的态度,也就是说假设数据肯定会冲突,所以在数据开始读取的时候就把数据锁定住。【数据锁定:数据将暂时不会得到修改】悲观锁是数据库实现,他阻止一切数据库操作。
- 乐观锁,认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让用户返回错误的信息,让用户决定如何去做。乐观锁是一种思想,具体实现是,表中有一个版本字段,第一次读的时候,获取到这个字段。处理完业务逻辑开始更新的时候,需要再次查看该字段的值是否和第一次的一样。如果一样更新,反之拒绝。之所以叫乐观,因为这个模式没有从数据库加锁。
实现方式对比:
悲观锁:大多数情况下依靠数据库的锁机制实现,排它锁(当事务在操作数据时把这部分数据进行锁定,直到操作完毕后再解锁,其他事务操作才可操作该部分数据。这将防止其他进程读取或修改表中的数据。)。一般使用 select …for update 对所选择的数据进行加锁处理,例如select * from account where name=”Max” for update, 这条sql 语句锁定了account 表中所有符合检索条件(name=”Max”)的记录。本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。
乐观锁:大多数基于数据版本(Version)记录机制实现,具体可通过给表加一个版本号或时间戳字段实现,当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断当前版本信息与第一次取出来的版本值大小,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据,拒绝更新,让用户重新操作。
补充两个基本的问题:
15.当前读和快照读
当前读:读取的是最新版本。UPDATE、DELETE、INSERT、SELECT … LOCK IN SHARE MODE、SELECT … FOR UPDATE是当前读。并且对读取的记录加锁, 阻塞其他事务同时改动相同记录,避免出现安全问题。
快照读:读取的是快照版本,也就是历史版本。普通的SELECT就是快照读
- Read Committed隔离级别:每次select都生成一个快照读。
- Read Repeatable隔离级别:开启事务后第一个select语句才是快照读的地方,而不是一开启事务就快照读。
16.一条sql的执行过程?
第一步:连接器(负责跟客户端建立连接、获取权限、维持和管理连接)
第二步:查询缓存
- 之前执行过的查询,MySQL以”Key – Value”的形式存在内存(key为SQL,value为结果集);
- 只要对该表有一个更新,则这个表上的查询缓存都会被清空;
- 手动创建命令:select SQL_CACHE * from T(MySQL8中已经彻底废除此功能);
第三步:分析器(词法分析 —— 语法分析)
- 词法分析:通过“select”,识别出为查询;通过“T”,识别出表名;通过条件“ID”,识别出ID那一列;等等;
- 语法分析:如果语法有误,则提示“You have an error in your SQL syntax”;
第四步:优化器
- 决定用哪个索引;联查表连接顺序;条件执行优先级 ,等等;
第五步:执行器 (执行SQL)
第六步:存储引擎(提供读写接口,供执行器调用并获取结果集)
- 首先会判断你是否有该权限;
- 如果命中查询缓存,则会在返回结果的时候进行权限验证;
参考书籍、文献和资料
1.https://blog.csdn.net/tian_ci/article/details/85697442
2.https://www.cnblogs.com/huasky/p/11190086.html
3.https://www.cnblogs.com/wyaokai/p/10921323.html
4.https://www.cnblogs.com/kyoner/p/11305204.html
5.https://www.e-learn.cn/content/qita/804492
6.https://www.jianshu.com/p/4318ca15fb81
7.https://www.liangzl.com/get-article-detail-126292.html
8.https://www.cnblogs.com/myseries/p/10930910.html
9.https://www.jianshu.com/p/8845ddca3b23
10.https://www.cnblogs.com/aspirant/p/9214485.html
11.http://blog.codinglabs.org/articles/theory-of-mysql-index.html
12.https://cloud.tencent.com/developer/article/1494725
13.https://blog.csdn.net/u010727189/article/details/79399384
14.https://blog.csdn.net/tanga842428/article/details/79530065
15.https://blog.csdn.net/qq_38789941/article/details/83744271
16.https://www.cnblogs.com/deverz/p/11066043.html
17.https://baijiahao.baidu.com/s?id=1645183969622435609&wfr=spider&for=pc
18.https://blog.csdn.net/u013308135/article/details/76796770
19.https://blog.csdn.net/luzhensmart/article/details/81675527
20.http://blog.itpub.net/21374452/viewspace-2136284
21.https://blog.csdn.net/frycn/article/details/70158313
22.https://blog.csdn.net/woshiyeguiren/article/details/80277475
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/129963.html原文链接:https://javaforall.cn