1、概述
Mysql可分为Server层和存储引擎层。server层包括连接器、查询缓存、分析器、执行器等,涵盖mysql的大多数核心服务功能,以及所有的内置函数(如日期、时间、数字和加密函数),所有跨存储引擎的功能都在这一层,比如存储过程、触发器、视图等
而存储引擎负责数据的存储和提取,其架构模式是插件式的,支持InnoDB、MyIsam、Memory等多个存储引擎,现在最常用的存储引擎是InnoDB,他从Mysql5.5.5版本开始成为了默认存储引擎。
连接器,进行数据库连接
查询缓存:拿到语句,会从缓存里看,是否执行过这条语句,之前执行过的语句及其结果可能会以key-value对的形式,(如果有表更新的话,跟这个表有关的查询缓存会失效,清空)
分析器:对sql语句进行解析,将字符串T识别表名T,字符串ID识别成列ID,将select识别为查询语句
2、sql更新语句执行
重要的日志模块:binlog
Mysql整体来看,一块是server层,主要做的是MySQL功能层面的事情,还有一块是引擎层,负责存储相关的具体事宜。,redo log是InnoDB 引擎特有的日志,而server层也有自己的日志,称为binlog(归档日志)
why两份日志
因为最开始MySQL里并没有InnoDB引擎,mysql自带的引擎是MyISAM,但MyISAM没有crash-safe能力,binlog日志只能用于归档,而InnoDB是另一个公司以插件形式引入mysql,既然只依靠binlog是没有crash-safe能力的,所以InnoDB使用另外一套日志系统,也就是redo-log来实现crash-safe能力。
两阶段提交:为了两份日志之间的逻辑一致
3、事务
SQL隔离级别:读未提交、读提交、可重复读和串行化
读未提交:一个事务还没提交时,他做的变更就能被别的事务看到
读提交:一个事务提交之后,他做的变更才会被其他事务看到
可重复读:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的诗句是一直的。
串行化:对于同一个记录,写会加写锁,读会加读锁,当出现读写冲突时,后访问的事务必须等前一个事务执行完成,才能继续执行
每条记录在更新的是否都会同时记录一条回滚操作,同一条记录在系统中可以存在多个版本,这就是数据库的多版本并发控制(MVCC)
回滚日志什么时候删除?系统会判断当没有事务需要用到这些回滚日志的时候,回滚日志会被删除。
什么时候不需要了?当系统要么有比这个回滚日志更早的read-view的时候
为什么尽量不使用长书屋?长事务意味着系统里面会存在很老的事务视图,在这个事务提交之前,回滚记录都要保留,这会导致大量占用存储空间,除此之外,长事务还占用锁资源,可能会拖垮库
4.索引
(1)哈希作用:提高数据查询效率
(2)常见索引模型:哈希表、有序数组、搜索树
(3)哈希表:键-值(key-value)
冲突处理方法:链表
使用场景:只有等值查询的场景
(4)有序数组:按顺序存储,查找用二分法就可以快速查询,时间复杂度O(log(N))
查询效率高,更新效率低
适用场景:静态存储引擎
(5)二叉搜索树:每个节点的左儿子小于父节点,父节点又小于右儿子
查询时间复杂度O(log(N)),更新时间复杂度O(log(N))
InnoDB中的索引模型:B+tree
索引类型:主键索引、非主键索引
主键索引的叶子节点是整行的数据(聚簇索引),非主键索引的叶子节点内容是主键的值 (二级索引)
两者区别:主键索引只要搜索这个B+tree即可拿到数据,普通索引先搜索索引拿到主键值,再到主键索引数搜索一次(回表)
一个数据页满了,按照B+tree算法,新增加一个数据页,叫做页分裂,会导致性能下降,空间利用率降低大概50%,当相邻的两个数据页利用率很低的时候做数据页合并,合并的过程是分裂过程的逆过程
数据库底层存储的核心是基于这些数据模型的,每碰到一个新数据库,我们需要先关注他 数据模型,这样才能从理论上分析出这个数据库的适用场景
回到主键索引树搜索的过程,称为回表
如何通过索引优化,避免回表过程?
1)覆盖索引
select ID from T where k between 3 and 5,此时只需要查ID值,而ID值已经在k索引树上了,因此可以直接提供查询结果,不需要回表,也就是说,在这个查询里面,索引k已经覆盖了我么的查询需求,称为覆盖索引。
覆盖索引可以减少树的搜索次数,显著提升查询性能
2)最左前缀原则
3)索引下推
mysql5.6引入的索引下推优化,可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数
5、锁
数据库锁设计的初衷是处理并发问题。
根据加锁的范围,mysql里面的锁大致可以分为全局锁、表级锁和行锁三类。
全局锁,对整个数据库实例加锁。命令是Flush tables with read lock(FTWRL),当你需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞;数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构)和更新类事务的提交语句。
全局锁的典型使用场景是,做全表逻辑备份,对于全部是InnoDB引擎的库,可以使用single-transaction参数,对应用会更友好
表锁一般在数据库引擎不支持行锁的时候才会用到
表级锁,语法是lock tables ... read/write,可以用unlock tables主动释放锁,也可以在客户端断开的时候自动释放
另一类表级锁是MDL(metadata lock),MDL不需要显式使用,在访问一个表的时候会被自动加上,MDL的作用是保证读写的正确性
MDL,当对一个表做增删改查时,加MDL读锁,当要对表做结构变更操作时,加MDL写锁
读锁之间不互斥,可以多个线程同时对一张表增删改查
读写锁之间、写锁之间是互斥的,用来保证变更结构操作的安全性。
6、行锁
行锁是针对数据表中行记录的锁
在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放,这是两阶段锁协议
死锁:当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态,称为死锁
解决两种策略:
一种策略是,直接进入等待,直到超时,这个超时时间可以通过参数innodb_lock_wait_timeout来设置
另一种策略,发起死锁检测,发现死锁后,主动回滚死锁链条中的某个事物,让其他事务得以继续执行,将参数innodb_deadlock_detect设置为on,表示开启这个逻辑
How解决由这种热点行更新导致的性能问题
Re:一种思路 如果能确保这个业务一定不会出现死锁,可以临时把死锁检测关掉。
另一种思路:控制并发量。做在数据库服务端,
7、事务到底是隔离还是不隔离?
begin/start transaction命令并不是一个事务的起点,在执行到他们之后的第一个操作InnoDB表的语句,事务才真正启动,如果你想要马上启动一个事务,可以使用start transaction with consistent snapshot这个命令
第一种启动方式,一致性视图是在执行第一个快照读语句时创建的
第二种启动方式,一致性视图是在执行 start transaction with consistent snapshot创建的
视图的概念
view,用查询语句定义的虚拟表,在调用的时候执行查询语句并生成结果,创建输入的语法是create view,,而他的查询方法与表一样
另一个是InnoDB在实现MVCC时用到的一致性读视图,即consistent read view ,用于支持RC(read committed 读提交)和RR(Repeatable Read 可重复读)隔离级别的实现
视图的作用是事务执行期间来定义“我能看到什么数据”
快照在MVCC如何工作?
InnoDB里面每个事务有一个唯一的事务id,叫transaction id,他是在事务开始时向InnoDB的事务系统申请的,是按申请顺序严格递增的
而每行数据也都是有多个版本的,每次事务更新数据的时候,都会生成一个新的数据版本,并把transaction id赋值给这个数据版本的事务id,记为row trx_id,同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它
因此,数据表的一行记录,其实可能有多个版本(row),每个版本有自己的row_trx_id
可重复读的核心是一致性读(consistent read),而事务更新数据的时候,只能用当前读,如果当前的记录的行锁被其他事务占用的话,就需要进入锁等待。
读提交和可重复读区别:
在可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图
在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图。
InnoDB的行数据有多个版本,每个版本都有自己的row_trx_id,每个事务或语句有自己的一致性视图,普通查询语句是一致性读,一致性读会根据row_trx_id和一致性视图确定数据版本的可见性。
对于可重复读,查询只承认在事务启动前就已经提交完成的数据
对于读提交,查询值承认在语句启动前已经提交完成的数据
而当前读,总是读取已经提交完成的最新版本