@boothsun
2018-04-01T16:39:19.000000Z
字数 6910
阅读 1130
面试题
MyISAM不支持事务,而InnoDB是支持事务的,InnoDB的事务是通过锁 + redo和undo log实现的。(这里可以展开讲一下InnoDB对事务的实现原理)
MyISAM只支持表级锁,而InnoDB支持行级锁+表级锁。(这里可以讲一下InnoDB中行级锁的实现原理和意向锁、共享锁、排他锁,讲一下MVCC的实现原理)
MyISAM不支持外键,但是InnoDB是支持的。
但是互联网公司的实际项目中,还是比较少用外键的。外键使表与表之间太耦合了,update, delete等操作都会涉及相关的表操作。影响sql 的性能,甚至会造成死锁。高并发的情况下,当存在外键约束的时候,每次要去扫描此记录是否合格,扫描的数量很可能指数级增长,常见的解决办法都是通过应用程序来维护这种关联关系,开启事务去处理这些具有外键关联关系的表,有需要还可以进行异步操作来提供性能或者是补充机制。(具体参见为什么很多mysql课程不推荐用物理外键?)
MyISAM通过内置计数器保存具体行数,但InnoDB是通过全表扫描来计算得出的。但是这只是对select count(*) from tableName
这种不带where条件的查询才有效,对于有where条件的count(*),MyISAM也只能和InnoDB一样做全表扫描。
每个MyISAM在磁盘上存储为三个文件,文件名字都是以表名开头。 主要分为以下三种: .frm
(表定义)和 .MYD
(MyData 表数据)和 .MYI
(My Index 索引文件),从这里就可以看出MyISAM中索引和数据是分开的,并且MyISAM有索引的前缀压缩技术,使得索引会很小,更适合做索引的内存缓存。(这里可以对前缀压缩技术进行展开讲解)
InnoDB则使用了聚簇索引,并且没有使用前缀压缩从而使得InnoDB文件相对MyISAM体积更庞大。(这里可以对聚簇索引展开讲)
MyISAM索引文件中存储的是数据行的指针,但是InnoDB存储的是主键。
存储指针的缺点:
存储指针的优点:
存储主键的缺点:
存储主键的优点:
还有就是经常有很多应用部门需要我给他们定期某些表的数据,MyISAM的话很方便,只要发给他们对应那表的frm.MYD,MYI的文件,让他们自己在对应版本的数据库启动就行,而Innodb就需要导出xxx.sql了,因为光给别人文件,受字典数据文件的影响,对方是无法使用的。
InnoDB中隔离性是通过数据库锁实现的。共享锁、排他锁、意向共享锁、意向排他锁。共享锁和排他锁非常简单,意向锁的出现是因为InnoDB既支持表锁也支持行锁,所以为了表锁和行锁存在时,表锁更快地感知到行锁的存在,更快地判定表锁和意向锁是否兼容,如果不能兼容,则表锁将会被阻塞。
InnoDB加锁是针对于索引进行加锁的。只有通过索引条件进行操作或者查询数据才会使用行级锁,否则InnoDB也将使用表锁。另外,如果InnoDB存储引擎表建立的时候没有设置任何一个索引,这时InnoDB存储引擎会使用隐式的主键来进行锁定。
InnoDB中行锁的实现算法设计有:Record Lock
、Gap Lock
、Next-Key Lock
三种,而Gap Lock
和Next-Key Lock
的出现主要是为了解决幻行的问题。InnoDB默认的事务隔离级别为Repeatable Read
,而Repeatable Read
模式下,Next-Key Lock
是默认的行记录锁定算法。比如,针对于查询:select * from t where a < 6
,锁定的数据将是(-无穷大,6)。
Redo log:执行过程日志(是新数据的备份)
undo log:旧数据备份(回滚时使用)
在事务COMMIT操作完成之前,InnoDB会要求必须先将该事务的所有操作日志都写入到redo log中,而不需要将数据持久化,如果此时MySQL 服务崩溃,虽然数据还没有持久化,但是redo log已经持久化了,MySQL服务恢复后会根据redo log 将数据恢复到最新的状态。这样就保证了数据的持久性、一致性。
undo log记录了事务修改前的数据状态,是对旧数据的一种备份,异常或者事务回滚时,可以根据undo log将数据还原到之前的状态。这样保证了数据的原子性、持久性、一致性。
Undo log主要分为 Insert_undo log和update_undo log(update和delete);insert_undo log记录插入的唯一键,update_undo log 记录修改的唯一键值以及old column记录。当事务回滚时,InnoDB将执行相反的操作。对于每个insert,会从undo日志中获取唯一键值来完成delete操作;对于每个delete,InnoDB存储引擎会执行一个insert将 old column记录再次插入进去。对于每个update,InnoDB存储引擎会执行一个相反的update,将修改前的行放回去。
值得一提的是InnoDB的恢复策略,我们能想到的恢复策略一般都是下面两种:
InnoDB采用的是第二种,虽然看起来比较笨,但是最简单,在重做redo log时,不需要关心事务性。恢复时,也没有BEGIN,也没有COMMIT、ROLLBACK的行为,也不关心每条日志是属于哪个事务的。当然,这样可能会造成重新执行了被回滚的事务,但是这对数据完整性并没有任何影响。
Undo log + redo log的过程如下:
A. 事务开始.
B. 记录A=1到undo log.
C. 修改A=3.
D. 记录A=3到redo log.
E. 记录B=2到undo log.
F. 修改B=4.
G. 记录B=4到redo log.
H. 将redo log写入磁盘。
I. 事务提交
MVCC是一种多版本并发访问控制机制,是为了实现非锁定读操作而产生的。InnoDB进行读操作时,如果发现读取的行被其他线程加上了排他锁(DELETE、UPDATE),这时读取操作不会因此而等待行上锁的释放。相反,InnoDB存储引擎会去读取正在加锁的事务的undo log日志,读该行被加锁前的快照数据。
可以看到,非锁定读的机制大大提高了数据读取的并发性,它也是REPEATABLE READ隔离级别(InnoDB存储引擎默认隔离级别)的默认读取方式,即读取不会占用和等待表上的锁,但是在不同事务隔离级别下,读取的方式不同,并不是每个事务隔离级别下读取的都是一致性读。同样,即使都是使用一致性读,但是对于快照数据的定义也不同。
快照数据其实就是当前行数据之前的历史版本,可能有多个版本。一个行可能有不止一个快照数据。Read Committed和Repeatable Read两种存储引擎下,都使用了MVCC这种非锁定的一致性读,但是对于快照数据的定义却不相同。在Read Committed事务隔离级别下,对于快照数据,非一致性读总是读取被锁定行的最新一份快照数据。但是在Reapeatable事务隔离级别下,对于快照数据,非一致性读总是读取事务开始时的行数据版本。
具体的执行过程:begin->用排他锁锁定该行->记录redo log->记录undo log->修改当前行的值,写事务编号,回滚指针指向undo log中的修改前的行。
现有MySQL主从复制的方式都是在主库上记录二进制日志、在从库重放日志的方式来实现异步的数据复制。因为是异步复制,所以可能会存在延迟问题。(这里可以讲一下canal)。
基于语句的复制就是 主库会记录那些造成数据更改的语句,当备库读取并重放这些事件时,实际上只是把主库上执行过的SQL再执行一遍。
好处:
缺点:
基于行的复制,这种方式会将实际增删改数据记录在二进制日志中,跟其他数据库的实现比较相像。
好处:
缺点:
分类:共享锁 排他锁 意向共享锁 意向排他锁。 行锁和表锁
意向锁是表级锁,是为了表锁和行锁冲突时,表锁快速感知到行锁的存在,从而判定表锁和行锁是否冲突。
锁算法:
1.Record Lock、Gap Lock、Next-Key Lock 解决幻读问题。
隔离性是通过锁实现的。原子性、一致性、持久性是通过redo log、undo log实现的。
事务要么都执行,要么都回滚,就是我们最常使用的事务。
事务执行过程中,会形成很多的保存点,事务回滚时,可以回滚到指定的保存点,从而实现有针对性的回滚,而非全部回滚。
带有保存点的扁平事务过程大致如下:
begin --> 隐含保存点1(save work 1) --> A --> B(save work2)--> C --> D(rollback work2) --> commit。
链式事务是指多个事务之间传递必要的上下文,这就意味着下一个事务可以看到上一个事务的结果,就好像在一个事务中。链式事务中会把提交事务操作和开始下一个事务操作合并为一个原子操作。
可以理解为一棵事务树,顶层事务控制着下面各个层次的子事务,所有的叶子节点都是扁平事务,实际工作是由叶子节点完成的。子事务既可以提交也可以回滚。但是它的提交操作并不马上生效,除非其父事务已经提交。
也就是说,任何子事务都在顶层事务提交后才真正的提交。树中的任意一个事务的回滚会引起它的所有子事务一同回滚,故子事务仅保留A、C、I特性,不具有D的特性。
分布式环境下运行的扁平事务。
建立合适的索引:索引可以减少扫描的行数,加快order by 和group by等的速度。
遵循索引的最左前缀原则:避免使用like ‘%Query%’、!=、<>。
尽量避免在where子句中对字段进行表达式计算和函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:select id from t where num/2=100应改为:select id from t where num=100*2。如:select id from t where substring(name,1,3)=’abc’
,name以abc开头的id应改为:select id from t where name like ‘abc%’
只查询需要的行和列,比如:select * from t
尽量避免在where子句中使用or来连接查询,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from t where num=10 or num=20可以这样查询:select id from t where num=10 union all select id from t where num=20。
In和not in 也要慎用,否则会导致全表扫描,如:select id from t where num in(1,2,3),对于连续的数值,能用 between 就不要用 in 了:select id from t where num between 1 and 3。
如果在 where子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:select id from t where num=@num可以改为强制查询使用索引:
select id from t with(index(索引名)) where num=@num
不要在 where子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
Limit分页:MySQL的做法是查询前n条,然后截取指定数量的条数。类似于游标,设置起始点,然后进行分页,尽可能减少查询的数据量。还有一种方法是尽可能地使用索引覆盖扫描,而不是查询所有的列。然后根据需要与原表做一次关联操作返回需要的列。对于偏移量很大的时候,这样的效率会提升非常。考虑下面的查询:SELECT film_id, description FROM sakila.film ORDER BY title LIMIT 50, 5;
如果这个表非常大,那么这个查询最好改写成下面的这样子:
SELECT film.film_id, film.description FROM sakila.film
INNER JOIN
(SELECT film_id FROM sakila.film ORDER BY title LIMIT 50,5) AS lim
USING(film_id);
主键尽量单调,否则可能造成页分裂问题。
二叉排序树:
平衡二叉树:
B+树:
B+树 Plus版:
带有顺序访问指针的B+Tree。在B+Tree的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的B+Tree
聚簇索引和非聚簇索引。辅助索引保存指针还是主键。
B-Tree中一次检索最多需要h-1次I/O(根节点常驻内存),渐进复杂度为。一般实际应用中,出度d是非常大的数字,通常超过100,因此h非常小(通常不超过3)