SQL优化调优是体现程序员分析归纳能力的有效手段,虽然我们不是DBA,但是编码开发时也会涉及许多对数据库的CRUD需求。因此,通过理解Mysql数据库的底层原理,对我们的笔试面试,还有提高业务编码水平是有好处的。
大体来说,MySQL 可以分为 Server 层 和 存储引擎层两部分:
现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。
下面是Mysql的架构图:
数据库的Server 层包括:连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
1、连接器:连接器负责跟客户端建立连接、获取权限、维持和管理连接。
数据库长连接:指连接成功后,如果客户端持续有请求,则一直使用同一个连接。 数据库短连接:则是指每次执行完很少的几次查询就断开连接,下次查询再重新建立一个。
2、查询缓存:MySQL 拿到一个查询请求后,会先到查询缓存。
但是大多数情况下我会建议你不要使用查询缓存,为什么呢?因为查询缓存往往弊大于利(查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。因此很可能你费劲地把结果存起来,还没使用呢,就被一个更新全清空了)。
3、分析器:MySQL 需要知道你要做什么,因此需要对 SQL 语句做解析。
4、优化器:优化器是在表里面有多个索引的时候,决定使用哪个索引。
5、执行器:通过分析器知道了你要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执行语句。
数据库的存储引擎层:负责数据的存储和提取,其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。
RedoLog 和 BinLog 区别
1、 redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。
2、 redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。
3、redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
mysql的两阶段提交原理
阶段1:InnoDB redo log 写盘(引擎层),InnoDB 事务进入 prepare 状态;
阶段2:如果前面prepare成功,binlog 写盘(Server层),那么再继续将事务日志持久化到binlog,如果持久化成功,那么 InnoDB 事务 则进入 commit 状态(实际是在redo log里面写上一个commit记录);
好处:
两阶段提交是跨系统维持数据逻辑一致性时常用的一个方案,这个方案也同时解决磁盘IO的性能。
数据库的事务:就是要保证一组数据库操作,要么全部成功,要么全部失败。
数据库执行引擎中的 InnoDB 支持事务,MySQL 原生的 MyISAM 引擎就不支持事务,这也是 MyISAM 被 InnoDB 取代的重要原因之一。
事务隔离特性
ACID:表示原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。
多事务并发执行问题
脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)。
事务隔离级别
读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。
事务隔离真理:隔离级别越高,数据库效率越低。
很多时候要在二者之间寻找一个平衡(跟代码加了重量级锁同理:利用 synchronized 锁住整个方法,效率比锁住方法的某个代码块是要低很多的)。
对于事务最重要的便是隔离级别了,但每种隔离级别都不是十全十美的,相对应的会引发匹配的问题,下面逐一解析。
读未提交是指:一个事务还没提交时,它做的变更就能被别的事务看到。(隔离性差,会出现脏读情况)
问题:产生了“脏读”数据,另一个未提交的事务,影响了数据居然也被读取到了。解决办法是设置隔离级别为“读提交”。
读提交是指:一个别的事务提交之后,它做的变更才会被别的事务看到。(oracle默认,隔离性还行,会出现不可重复读情况)
但正因为可以“及时的”读取到别的事务提交结果,出现了一个事务范围内同一个事务,但两个相同的查询却返回了不同数据,这就是不可重复读。
解决办法就是设置隔离级别为“可重复读”。解决不可重复读就需要锁行了。
可重复读是指,一个当前事务执行过程中看到的数据,总是跟这个当前事务在启动时看到的数据是一致的。
当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。(Mysql默认)
可重复读:
如果使用锁机制来实现这两种隔离级别,在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复读了。 但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会 发现莫名其妙多了一条之前没有的数据。
串行化:顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。
当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
总结:存在即合理,每种隔离级别都有自己的使用场景,你要根据自己的业务情况来定。
数据库锁出现的原因是为了处理并发问题,因为数据库是一个多用户共享的资源,当出现并发的时候,就会导致出现各种各样奇怪的问题,就像程序代码一样,出现多线程并发的时候,如果不做特殊控制的话,就会出现意外的事情,比如“脏“数据、修改丢失等问题。
从程序员的角度,锁可以分为:“乐观锁” 和 “悲观锁”。
乐观锁:程序员设计表结构加字段&更新操作进行判断是否字段数值一直。
悲观锁:(由系统提供的)表锁、行锁、间隙锁等。
针对悲观锁,其实日常的很多sql,我们已经不知不觉的使用到了:
对主键或者唯一索引进行增删改或显示的加锁,InnoDB会加行锁。
间隙锁的唯一目的就是阻止其他事务插入到间隙中。
一个表有id为1,2,3,5,6,9行数据,执行如下sql语句:
InnoDB不仅会锁住id为5和6两行的数据,也会锁住id为4(虽然该行并不存在)的纪录。
间隙锁的目的是为了防止幻读,其主要通过两个方面实现这个目的: (1)防止间隙内有新数据被插入 (2)防止已存在的数据,更新成间隙内的数据
next-key lock=行锁+间隙锁;如果一个事务在记录R上的某个索引有共享/互斥锁,也会对其前面一个范围加锁。
锁定的区域:根据索引会形成一个个左开右闭的一个区间,根据查询的条件其所在的区间,并且包括其后的区间。
索引目的:为了提高数据查询效率(减少磁盘查询次数)。
索引模型:
哈希索引的示意图:
Hash索引的不足: 不支持联合索引,不支持排序和分组,不支持范围查询,甚至大规模数据情况下,会带来哈希冲突问题。
B+树索引数据结构见下图:
简要概述:
主键索引:主键索引树,叶子结点包含整条数据内容。
非主键索引:非主键索引树,叶子结点仅包含数据的ID内容,因此需要回表获取更全面的数据(查询多一次主键树)。
下面总结一些索引使用的法则。
第1条sql 走索引,第2条sql则是全表扫描,非常慢。 6. 在索引列上进行计算、函数、手动或自动的类型转换会导致索引失效;见下图:
我们总结一下,上文主要归纳总结了Mysql的主要核心概念:日志系统&隔离级别&索引模型&索引使用经验等。
如果想学习的更加深入,推荐一门很好的课程给大家:《MySQL精讲45讲》,丁奇大大的优秀课程。
参考文章:
我正在参与 腾讯云开发者社区数据库专题有奖征文。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。