1. 事务的 ACID 特性,隔离级别
ACID 特性:原子性、一致性、隔离性、持久性
原子性(automicity):事务是一个原子操作单元,其对数据的修改,要么全部执行,要么全部不执行 -- undo log
一致性(consistent):在事务开始和完成时,数据都必须保持一致状态
隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作的环境下独立运行 -- 锁机制、MVCC
持久性(durable):事务完成后,它对数据的修改是永久性的,即使出现系统故障也能保持 -- redo log
事务的状态放大了看其实不在是原子的了,包含 active(初始状态,事务正在执行)、partially committed(最后一条语句执行完)、failed(发现事物无法正常执行后)、aborted(事务被回滚并且数据库恢复到了事务开始之前的状态之后)、committed(成功执行整个事务)
隔离级别:read uncommitted,read commited,repeatable-read,serializable
read uncommitter -- 读未提交级别,事务的修改,即使没有修改,对其他事务也是可见的,其他事务可以读取到未提交的数据,这种情况称为脏读
read commiter -- 读已提交级别,事务读取已提交的数据,当一个事务执行过程中,数据被其他事务修改,造成本次事务前后读取的数据不一致,这种情况称为不可重复读
repeatable-reda -- 可重复读级别,mysql 的默认隔离级别,解决了脏读和不可重复读的问题,但是会有幻读问题(幻读:查询某记录不存在,准备插入,插入是报记录已存在)
serializable -- 序列化,最高隔离级别,完全服从 ACID 的隔离级别,所有的事务依次逐个执行,完全不可能干扰,该级别可防止脏读,不可重复读和幻读
2. mysql 锁机制
mysql 为什么要加锁 --- 锁是计算机协调多个进程或线程并发访问某一资源的机制,用于管理对共享资源的并发访问,是对数据库的一种保护机制,也是数据库在事务操作中保证事务一致性和完整性的一种机制。当有多个用户并发的去存取数据时,在数据库中就可能会产生多个事务同时去操作一行数据的情况,如果我们不对此类并发操作加以控制的话,就可能会读取、存储不正确的数据,最终破坏了数据的一致性
mysql锁类型:
按锁的粒度来分:
表锁:使用的是一次性锁技术,在会话开始的地方使用 lock 命令将后续需要用到的表都加上锁,在锁释放前,只能访问这些加锁的表,不能访问其他表,直到最后通过 unlock tables 释放所有的表锁。开销小,加锁快;不会出现死锁,锁定粒度大,发生锁冲突的概率最高,并发度最低,适合以查询为主,少量按索引条件更新数据的应用,myISAM、innodb、BDB 支持表锁
行锁:行锁是通过给索引上的索引项加锁来实现的,意味着只用通过索引条件检索数据,才能使用行锁,否则,将使用表所;由于行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果使用相同的索引键值,是会出现锁冲突的。开销大,加锁慢,会出现死锁,锁定粒度小,发生锁 冲突的概率最低,并发度也最高 。适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,innodb 支持
页锁:是BDB特有的锁机制,开销和加锁时间介于表所和行锁之间,会出现死锁,锁定的粒度介于表所和行锁之间,并发度一般
行锁:
共享锁 -- 加了锁的记录,所有事务都能去读取,但不能修改,并且当前事务不能读取其他未加锁的记录,会报错,同时能阻止其他事务获得相同数据集的排他锁,即其他事务可以并发读取数据,但任何事务都不能对数据进行修改,若其他事务对数据进行修改会阻塞,直到已释放所有的共享锁
排他锁 -- 允许已经获得排他锁的事务去查询、更新、插入数据,但是不能操作其他未加锁的表,会报错,同时阻止其他事务取得相同的数据集的共享锁和排他锁,阻塞其他事务的操作,即只能当前事务对其进行读写,在此事务结束之前,其他事务不能对其进行任何加锁操作,需要等待其释放
读锁会阻塞写,但是不会阻塞读;写锁则会把读和写都阻塞
show open tables; --- 查看那些表被锁定;
show status like 'table%';
-- 查看 table_locks_immediate -- 产生表级锁定的次数,表示可以立即获得锁的查询次数,每立即获得锁值加一;
-- 查看 table_locks_waited -- 出现表级锁定争用而发生的等待次数,不能立即获取锁,每等待一次值加一,此值高说明存在严重的表级锁争用情况
行锁(record lock):添加在行索引上的锁
间隙锁(GAP lock):锁定行记录之前的间隙,可重复读以上级别(RR 级别解决当前读的幻读问题)
临健锁(next-key lock):行锁 + 间隙锁
何时使用行锁,何时使用间隙锁
只使用主键索引/唯一索引查询,并且只锁定一条记录时,inndo 会将 next key lock 退化为 record lock;
只使用主键索引/唯一索引查询,但检索条件是范围检索,或者是唯一检索而检索结果不存在时,会产生行锁和间隙锁;
使用普通索引时,不管是何种查询,只要加锁,都会产生临健锁;
同时使用普通索引和唯一索引时,由于数据行是优先根据普通索引排序,再根据唯一索引排序,所以也会产生间隙锁;
产生间隙锁会阻塞其他事务阻塞再间隙内的数据操作(间隙内插入或将已存在的数据更新成间隙内的数据)
产生临健锁、间隙锁还是针对行记录添加锁,取决于是否有建立了索引、建立的是普通索引还是唯一索引,以及检索条件是否命中索引。
RR + 无显式主键无索引
RR + 无显式主键有索引(普通索引)
RR + 无显式主键有索引(唯一索引)
RR + 有显式主键无索引
RR + 有显式主键有索引(普通索引)
RR + 有显式主键有索引(唯一索引)
show engine innodb status G -- 查看事务加锁的情况,前提:需开启 innodb_status_output_locks
查看事务加锁的情况需了解 lock modle 标识所对应的具体含义:
IX -- 意向排他锁
IS -- 意向共享锁
X -- 排他锁标识,但在这里查看表示的是添加的临健锁,锁定记录和记录之间的间隙
X, REC_NOT_GAP -- 行锁,锁定记录本身
X, GAP -- 间隙锁,锁定记录之间的间隙,不锁定记录本身
S -- 共享锁标识,但在这里查看表示的是添加的临健锁,锁定记录和记录之间的间隙
S, REC_NOT_GAP -- 行锁,锁定记录本身
S, GAP -- 间隙锁,锁定记录之间的间隙,不锁定记录本身
---------------------------
乐观锁:乐观的任务每次获取数据都不会发生冲突,不加锁去完成某项操作,只在提交操作的时候检查是否违反数据完整性。适合读取操作比较频繁的场景
悲观锁:每次获取数据的时候都认为别人会修改,所以每次在获取数据的时候都会上锁,这样其他事务获取这个数据就会 block 直到它获取到锁,如行锁,表锁。适合写操作比较频繁的场景
什么是死锁 --- 所谓死锁,是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去,此时称系统处于死锁状态或系统产生了死锁。
死锁产生的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞,对已获得的资源保持不放
- 不剥夺条件 :进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
3. 什么是MVCC?mysql mvcc 的实现原理
mvcc Multi-Version Concurrency Control多版本并发控制,在数据库管理系统中,实现对数据库的并发访问控制。MVCC 是有三个隐藏字段 (DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID)、undo log 和 read view 三个组件来实现的
undo log :又称回滚日志,行记录的每一次操作都会记录到回滚日志中,每一个旧版本记录都会有一个 db_roll_ptr 指针指向上一个版本记录,形成一个链表进行线性存储,链表的表头是最新的旧数据,链表的表尾是最旧的旧数据
read view :事务进行快照对操作时产生的读视图,就是说当前事务进行快照读操作时,数据库系统会生成一个当前系统的快照,记录并维护系统当前活跃事务的ID,read view 遵循可见性算法主要是将要被修改的数据的最新记录的事务ID拿出来,和系统当前活跃事务ID做比较,若不符合可见性,就通过 db_roll_ptr 去 undo log 中遍历 db_trx_id ,找到符合可见性条件的 db_trx_id ,这个 db_trx_id 所在的旧版本记录就是当前事务能看到的最新旧版本数据。所以,read view 的最大作用就是用来做可见性判断的,也就是说当前事务进行快照读的时候,对该记录生成一个 read view 的视图,把它作为条件去判断当前事务能够看到哪个版本的数据,有可能是最新的,也有可能是 undo log 中记录的当前行的某个版本数据
快照读:不加锁的 select 就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行化的,串行化级别下快照读会退化为当前读。快照读的出现是为了提高数据库的并发访问能力,快照读的实现是基于 mvcc 的,可以理解为行锁的一个变种,它在很多情况下避免的加锁操作,降低了开销。由于是基于多版本的,所以快照读可能读到的数据不是最新版本,有可能是历史版本
当前读:像 select ... lock share in model, select ... for update, update, insert, delete 这些都是当前读,能读到版本最新数据,读取时,还能保证其他并发事务不能修改当前记录,会对读取的记录加锁
DB_TRX_ID:6 字节,记录创建或最近修改该记录的事务ID
DB_ROW_ID:6 字节,隐藏主键,若数据表没有主键,innodb 会默认创建一个 6 字节的 row_id
DB_ROLL_PTR:7 字节,配合 undo log,指向这条记录的上一个版本记录
read view 是如何判断可见性的?、
read view 有三个主要属性,trx_list -- 一个数值列表,用来维护 read view 生成时刻,系统正活跃的事务ID,up_limit_id -- 记录 trx_list 中最小的事务ID,low_limit_id -- read view 生成时刻系统尚未分配的下一个事务ID
具体比较比较规则: 1)首先比较 db_trx_id < up_limit_id,即小于活跃事务的最小ID,则当前事务可以看到 db_trx_id 所在的记录,如果不小于则进行下一步判断;2)接下来判断 db_trx_id >= low_limit_id,如果大于满足则说明 db_trx_id 所在记录是在 read view 创建之后才出现的,那么对于当前事务肯定不可见;如果小于,进入下一步判断;3)判断 db_trx_id 是否在 trx_list 列表中,如果在,说明 read view 生成时刻事务还是活跃的,还没有 commit,修改的数据,当前事务是不可见的;如果不在,则说明这个事务在 read view 生成之前已经 commit 过了,那么修改结果对于当前事务是可见的
4. 什么是索引?mysql 索引是怎么实现的
hash 索引缺点:1)使用 hash 索引需要将数据文件添加到内存中,比较耗费内存空间;2)如果所有的查询使用等值查询 ,hash 确实快,但是在实际的应用场景中,范围查找数据更多;
二叉树/红黑树:这两种都是二叉树结构,都会因为数据量的增加导致数据的深度增加,造成 IO 次数变多,影响读取效率
B数:1)每个节点都有 key,同时也包含数据 data,每个页的存储是有限的,data 比较大会导致每个节点存储的 key 数据减少;2)数据量很大的时候会导致很多变大,增加IO次数,影响查询性能
B+树:1)
表优化
force index
5. 什么是事务?mysql 事务是如何实现的
redo log
redo log 两阶段提交
undo log
6. mysql 主从复制是如何实现的,原理是什么
什么是主从复制
为什么要主从复制
主从复制的原理是什么
主从同步延时性如何处理(MTS并行复制)
7. mysql 读写分离是如何实现的
mysql-proxy
amoeba
mycat
mmm mha mgr
8. mysql 三范式 反范式
第一范式:表的列具有原子性,不可再分解
第二范式:满足第一范式前提下,表中的实例或行必须可以被唯一区分
第三范式:满足第二范式前提下,表中 不包含已在其他表存在的非主键字段
反范式:没有冗余的数据库未必是最好的数据库 ,有时为了提高运行效率,就需要降低范式标准,增加冗余字段,减少查询时的关联。但反范式一定要适度,要在满足三范式的前提下做调整
keepalived -- 实现真机之间的故障隔离、负载均衡失败之间的转换,提高系统的可用性
keepalive 遵循 vrrp 协议,完全遵从权重大小来进行主节点的选举,权重最大的为 master。
9. mysql 架构:
客户端 -》 连接器 -〉查询缓存 -》分析器 -〉优化器 -》执行器 -〉存储引擎
连接器:负责和客户端建立连接,获取权限、维持和管理链接
查询缓存:当执行查询语句的时候,会先去查询缓存中查看结果,之前执行过的 sql 语句及其结果可能以 key-value 的形式存储在缓存中,如果能找到则直接返回,如果找不到,就继续执行后续的阶段。但是不推荐使用查询缓存,查询缓存的失效比较频繁,只要表更新,缓存就会清空;缓存对应新更新的数据命中率比较低
分析器:词法分析和语法分析
优化器:在具体执行 sql 语句之前,要先经过优化器的处理,如有多个索引时决定使用哪个索引,多表关联时决定表的连接顺序,等;不同的执行方式对 sql 语句的 执行效率影响很大;RBO - 基于规则的优化;CBO - 基于成本的优化
10. redo.log、undo.log、binlog
redolog 是innodb存储引擎的日志文件,当发生数据修改的时候,innodb 引擎会先讲记录写到 redo log 中,并更新内存,此时更新就算是完成了,同时innodb 引擎会在合适的时机将记录操作到磁盘中
undolog 是为了实现事务的原子性,在 mysql 数据库 innodb 存储引擎中,还用 undo log 来实现多版本并发控制;在操作任何数据之前 ,首先讲述备份到 undo log ,然后进行数据的修改 ,如果出现了错误或执行了 ROLLBACK 语句,系统可以利用 undo log 将数据恢复到事务开始前的状态;undo log 是逻辑 日志,可以理解为 delete -》 insert;insert -> delete; update -> ~update
binlog 是 server 层的日志,记录这个语句的原始逻辑;binlog 是追加写的 ,不会覆盖之前的日志信息
11. 数据更新流程
1)执行器先从引擎从找到数据,如果在内存中直接返回,如果不在内存中,从磁盘读取(查询)返回;
2)执行器拿到数据之后会先修改数据,然后调用引擎接口重新载入数据 ;
3)引擎将数据更新到内存,同时写数据到 redo log 中,此时处于 prepare 阶段,并通知执行器完成,随时可以操作;
4)执行器生成这个操作的 binlog
5)执行器调用引擎的事物提交接口,引擎把刚刚写完的 redo 改成 commit 状态,更新完成
12. BST数:二叉搜素树,又称二叉排序树,属于树的一种,通过二叉树将数据组织起来,树的每个节点都包含了键值 key、数据值 data、左子节点指针、右子节点指针;
特点:
1)左子树的所有节点 key 值都小于它的根节点的 key 值;
2)右子树的所有节点 key值都大于它的根节点的 key 值;
3)左右子树也都是二叉搜索树
13 AVL 树:平衡二叉搜素树,通过一定的机制能保证二叉搜素树的平衡,平衡的二叉搜素树的查询效率更高
特点:
1)左右子树也是AVL树;
2)每个节点的左右子节点的高度之差的不超过 1 ,即平衡因子范围为:[-1, 1]
14. 红黑树:自平衡二叉搜索树,与 AVL 树类似,在插入和删除操作时能通过自旋操作保证二叉搜索树的平衡,以便获得更高效的查找性能。红黑树牺牲了部分平衡性换取插入删除操作时少量的旋转操作
特点:
1)根节点是黑色的;
2)每个叶子节点是黑色的
3)每个红色节点的 两个叶子结点都为黑色,即从每个叶子到根节点的所有路径不能有两个连续的红色节点
4)从任一节点到其每个叶子结点的所有路径都包含相同的黑色节点;
5)最长路径不能超过最短路径的 2 倍