关注 欢少的成之路 回复算法,MySQL,8888,6666 领取海量学习资料。有机会参与领书活动!
大家好,我是Leo。目前在常州从事Java后端开发的工作。这篇是MySQL面试系列的总结篇。后续会不断修订该系列内容。这个系列会向字节,网易,阿里,腾讯,美团,快手的相关朋友一起整理输出。希望帮助更多的朋友早日入大厂!
整篇的写作大概就是这样思路。一篇带你熟悉MySQL!篇幅很大,建议先关注,收藏!
整个篇幅的知识点全部缩减。为面试系列打造!如果想具体深入研究,请关注公众号浏览其他文章即可!
平时我们都是关注SQL是如何执行的,但是有没有了解整个MySQL的结构呢?这里我们介绍一下
整个MySQL主要分两层。
这两层主要由四部分构成
连接器: 校验用户身份信息,校验当前用户的SQL语句权限,管理SQL连接的通道
分析器: 词法分析,语法分析。用于处理客户端的SQL语句,分析处理完之后写入缓存,如果下次命中的话直接返回提高查询效率。
优化器: 生成执行计划,索引选择(这里可以完美解释我上面抛出的SQL执行问题)
执行器: 调用操作存储引擎,捞取数据。
大概介绍了MySQL每一种结构发挥的作用。这里扩展一下大家一直说的长连接和短连接的优化!
**长连接:**建立连接之后,如果客户端的有请求操作则一种使用同一个连接进行交互处理
**短连接:**建立连接之后,并且客户端执行完自己的需求之后,就关闭了连接。
长短连接总结: 数据库建立连接这个过程是比较复杂的,所以建立尽量减少使用短连接的方式,也就是尽量使用长连接。但是长连接是比较容易涨内存的。也会被系统误认为内存占用过大强行杀死。
优化方案: 可以采用定期断开长连接的方法优化。还可以通过执行 mysql_reset_connection 来重新初始化连接资源。这个过程不需要重连和重新做权限验证。
首先要介绍两个日志。redo log 和 binlog。
redolog充当于我们平时生活中的记事本,备忘录。
binlog充当于阎王殿的生死簿。
数据库中也是一样。当进行数据修改操作时,不会立即修改到磁盘。如果是立即修改的话,对磁盘IO影响是比较大的。所以平时的修改操作都会先写到redo log
中,等系统认为不忙的时候再更新到binlog
中。
区别
修改流程
update vip set name=‘欢少的成长之路’ where ID=2
name=欢少的成长之路
写入新行这里面可以跟面试官介绍一下两阶段提交,第四步和第五步为什么要分开写入呢?这个就是两阶段提交的精髓
目的是为了让数据保持一致 如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致,下面我们举例论证一下。
欢少的成长之路
。但是由于 binlog 没写完就 crash 了,这时候 binlog 里面就没有记录这个语句。因此,之后备份日志的时候,存起来的 binlog 里面就没有这条语句。然后你会发现,如果需要用这个 binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行 name 的值就是 欢少个人业务
,与原库的值不同。欢少个人业务
。但是 binlog 里面已经记录了把 name 从 欢少个人业务
改成 欢少的成长之路
这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行 c 的值就是 欢少的成长之路
,与原库的值不同。上述就是一个SQL如何执行的介绍了,从查询与修改分别介绍了流程以及涉及到的知识点。最重要的就是两阶段提交了。如果面试官问你的话来个反证法差不多就过关啦
索引结构是MySQL最底层的数据处理结构了。主要分五块
哈希
这个就是通过hash
算法,把每个数据都hash出一串key之后,然后存在数据页的某一个位置。如果出现了相同的key就是发生了哈希碰撞。这个后续在算法章节会详细介绍一下。
这里MySQL解决方案是采用链表+哈希的方式共同存储在一个槽中。
链表
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
二叉树
二叉树是一种树形结构,每个节点只有两颗子节点。它是一种最简单且最重要的树。二叉树的递归定义为:二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树
红黑树
红黑树是一种 特定类型的二叉树,它是在计算机科学中用来组织数据比如数字的块的一种结构。若一棵二叉查找树是红黑树,则它的任一子树必为红黑树。
红黑树是一种平衡二叉查找树的变体,它的左右子树高差有可能大于 1,所以红黑树不是严格意义上的平衡二叉树(AVL),但对之进行平衡的代价较低, 其平均统计性能要强于 AVL 。
由于每一棵红黑树都是一棵二叉排序树,因此,在对红黑树进行查找时,可以采用运用于普通二叉排序树上的查找算法,在查找过程中不需要颜色信息。
B+树
B+树是B树的一个升级版,相对于B树来说B+树更充分的利用了节点的空间,让查询速度更加稳定,其速度完全接近于二分法查找
哈希
**优点:**哈希索引在解决单值查询的时候是非常快的,时间复杂度是O(1)。
**缺点:**但是不支持范围查询,所以当前MySQL中也应用到了哈希索引,但是并不是默认索引。
链表
二叉树
红黑树
B+树
第二部分,大概介绍了MySQL的五个索引结构。从概念,到优缺点的介绍。通过优缺点为切入点进行分析了MySQL为什么会以B+树为默认索引结构的原因。
概念
聚簇索引也是主键索引。一个表只能有一个聚簇索引。当表有聚簇索引时,数据行是保存在索引的叶子页的。
优点
数据访问更快,数据都保存在一棵树上,可以避免为了查询其他列进行回表操作。
缺点
聚簇索引极大的提高了IO密集型应用的性能,但是如果都放入内存中,访问的顺序就没那么必要了。聚簇索引也就失去了优势。
更新聚簇索引列的代价很高,因为会强制InnoDB将每个被更新的行移动到新的位置。
基于聚簇索引的表在插入新行,或者主键被更新导致需要移动行的时候,可能面临“页分裂”的问题。当行的主键值要求必须将这一行插入到某个已满的页中时,存储引擎会将该页分裂成两个页面来容纳该行,这就是一次分裂操作。页分裂会导致表占用更多的磁盘空间。
聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏,或者由于页分裂导致数据存储不连续的时候
概念
非聚簇索引也是二级索引。
叶子节点不存储数据,存储的是数据行地址,也就是说根据索引查找到数据行的位置再去磁盘查找数据,这就有点类似一本书的目录,比如要找到第三章第一节,那就现在目录里面查找,找到对应的页码后再去对应的页码看文章。
优缺点
非聚簇索引在做查找时,往往需要二次查询。第一次查找到主键值,再通过主键值找到数据行对应的数据页,再通过数据页中的Page Directory找到数据行。
一个表中可以存在多个非聚簇索引。
如果主键比较大的话,那非聚簇索引将会变得更大,因为非聚簇索引的叶子节点存储的是主键值,过长的主键值,会导致非叶子节点占用更多的物理空间
最基本的索引,没有任何限制,是我们经常使用到的索引。他的任务是加快对数据的访问速度。因此,应该只为那些最经常出现在查询条件(WHEREcolumn=)或排序条件(ORDERBYcolumn)中的数据列创建索引。只要有可能,就应该选择一个数据最整齐、最紧凑的数据列(如一个整数类型的数据列)来创建索引
与普通索引类似,不同的是,唯一索引的列值必须唯一,但允许为空值。主键索引是特殊的唯一索引,不允许有空值。
将几个列作为一条索引进行检索,使用最左匹配原则。举一个用户登录的例子。可以把登录账号和登录密码设为联合索引。这样可以提供性能的同时,节省索引的维护成本。
索引下推是在MySQL5.6引入的优化。可以在索引遍历过程中,对索引中包含的字段先做判断,过滤掉不符合条件的记录,减少回表字数。
第三部分大概介绍了MySQL内部索引的概念,优缺点,应用等。面试中常问的就是聚簇索引与非聚簇索引的区别。所以这里也是把这两块写的最详细的地方。
MySQL错误日志是记录MySQL 运行过程中较为严重的警告和错误信息,以及MySQL每次启动和关闭的详细信息。错误日志的命名通常为hostname.err
通过如下SQL,可以找到错误日志的位置。
show variables like '%log_error%';
错误日志如果不清理或删除,那么它会一直增长。在MySQL 5.5.7之前,可以通过mysqladmin –uroot –p flush-logs
命令删除错误日志。MySQL 5.5.7以及之后,只能通过下面方式来归档、备份错误日志
shell> mv host_name.err host_name.err-old
shell> mysqladmin -u root -p flush-logs
shell> mv host_name.err-old backup-directory
错误日志可以任意命名。只需要在/etc/my.cnf配置文件中,添加了参数log_error=/u02/mysql/mysql.err,重新启动MySQL即可。
MySQL的查询日志记录了所有MySQL数据库请求的信息。无论这些请求是否得到了正确的执行。默认文件名为hostname.log。默认情况下MySQL查询日志是关闭的。生产环境,如果开启MySQL查询日志,对性能还是有蛮大的影响的
不常用就不做过多介绍了
MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。
long_query_time的默认值为10,意思是运行10S以上的语句。默认情况下,Mysql数据库并不启动慢查询日志,需要我们手动来设置这个参数,当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件,也支持将日志记录写入数据库表。
慢查询日志涉及的重要参数:
慢查询日志涉及的重要工具:
常用指令
mysqldumpslow -s r -t 20 /mysqldata/mysql/mysql06-slow.log | more
-s 是表示按照何种方式排序,
c: 访问计数
l: 锁定时间
r: 返回记录
t: 查询时间
al:平均锁定时间
ar:平均返回记录数
at:平均查询时间
-t 是top n的意思,即为返回前面多少条的数据
后面是目录
more: 另外建议在使用这些命令时结合 | 和more 使用 ,否则有可能出现刷屏的情况。
提到redolog,肯定是要聊到redo log buffer
和redo log file
。前者是日志的缓存,是易失性的。后者是日志文件,是持久性的。
redo log buffer
要做的是一个事务在插入一条数据的时候,需要先写入日志。但是又不能在还没有提交事务的时候直接写到redo log文件中。这个日志的临时存放处就是redo log buffer。真正在写入redo log文件的过程是在commit这一步完成的。(执行一个SQL语句也是一个事务)
如果还没等到commit这一步,主要会有两种可能
接着持久化磁盘
redo log buffer的写入策略,是由innodb_flush_log_at_trx_commit
InnoDB 有一个后台线程,每隔 1 秒,就会把 redo log buffer 中的日志,调用 write 写到文件系统的 page cache,然后调用 fsync 持久化到磁盘。
redo log buffer的刷新策略,是由innodb_log_buffer_size
控制的。
(注意,由于这个事务并没有提交,所以这个写盘动作只是 write,而没有调用 fsync,也就是只留在了文件系统的 page cache。)
(假设一个事务 A 执行到一半,已经写了一些 redo log 到 buffer 中,这时候有另外一个线程的事务 B 提交,如果 innodb_flush_log_at_trx_commit 设置的是 1,那么按照这个参数的逻辑,事务 B 要把 redo log buffer 里的日志全部持久化到磁盘。这时候,就会带上事务 A 在 redo log buffer 里的日志一起持久化到磁盘。)
日志逻辑序列号,简称LSN。LSN是单调递增的。用来对应 redo log 的一个个写入点。每次写入长度为 length 的 redo log, LSN 的值就会加上 length。LSN 也会写到 InnoDB 的数据页中,来确保数据页不会被多次执行重复的 redo log。
如上图所述,
所以,一次组提交里面,组员越多,节约磁盘 IOPS 的效果越好。但如果只有单线程压测,那就只能老老实实地一个事务对应一次持久化操作了。
在并发更新场景下,第一个事务写完 redo log buffer 以后,接下来这个 fsync 越晚调用,组员可能越多,节约 IOPS 的效果就越好。
binlog写入日志这个是比较简单的。提到binlog,必然提到binlog cache。那么binlog cache是什么?
binlog cache是一个二进制日志文件的缓冲区,他是由一个参数 binlog_cache_size
控制大小的缓冲区。
一个事务在执行是时候是不允许被拆开的,因此无论事务多大,都是要一次性保存执行的。那么这个就涉及到了binlog cache
的保存问题。如果所占的内存大小超过了这个binlog_cache_size
参数的设定。就会采用暂存到磁盘。事务在提交的时候,会先把binlog cache里的数据写入到binlog中,并清空binlog cache数据。
由上图我们可以得知每个binlog cache是由单独的一个线程享有的。也就是说多个线程带着多个binlog cache写入binlog file是非常快的,因为并没有涉及到磁盘IO的开销。
当进行到了fsync的时候,才是将数据持久化到磁盘操作。这个时候才会占用磁盘IO,也就是我们常说的IOPS。
何时write?何时fsync?
主要由sync_binlog控制的。
因此,在出现 IO 瓶颈的场景里,将 sync_binlog 设置成一个比较大的值,可以提升性能。在实际的业务场景中,考虑到丢失日志量的可控性,一般不建议将这个参数设成 0,比较常见的是将其设置为 100~1000 中的某个数值。
但是,将 sync_binlog 设置为 N,对应的风险是:如果主机发生异常重启,会丢失最近 N 个事务的 binlog 日志。
binlog也是可以组提交的。主要分成两部分
如上图所述,可以看第二步。
如果多个事务都已经write了(也就是说写入到redo log buffer了),再到第四步的时候就可以一起持久化到磁盘了。不是提升IOPS的这个优化过程嘛!
不过通常情况下第 3 步执行得会很快,所以 binlog 的 write 和 fsync 间的间隔时间短,导致能集合到一起持久化的 binlog 比较少,因此 binlog 的组提交的效果通常不如 redo log 的效果那么好。
如果你想提升 binlog 组提交的效果,可以通过设置 binlog_group_commit_sync_delay
和 binlog_group_commit_sync_no_delay_count
来实现。这两个只要有一个满足条件就会调用 fsync。
WAL机制主要得益于
undo log
主要有两个作用:回滚和多版本控制(MVCC)
在数据修改的时候,不仅记录了redo log
,还记录undo log
,如果因为某些原因导致事务失败或回滚了,可以用undo log
进行回滚
undo log
主要存储的也是逻辑日志,比如我们要insert
一条数据了,那undo log
会记录的一条对应的delete
日志。我们要update
一条记录时,它会记录一条对应相反的update记录。
这也应该容易理解,毕竟回滚嘛,跟需要修改的操作相反就好,这样就能达到回滚的目的。因为支持回滚操作,所以我们就能保证:“一个事务包含多个操作,这些操作要么全部执行,要么全都不执行”。【原子性】
因为undo log
存储着修改之前的数据,相当于一个前版本,MVCC实现的是读写不阻塞,读的时候只要返回前一个版本的数据就行了。
跳表 也是为了 快速查找 而提出的一种数据结构
我们在链表中查询数据的时候,时间复杂度是O(n),为了解决效率问题,跳表就产生了。它本质上是一种多级链表,通过增加数据的冗余来换取查找的时间复杂度,属于空间换时间的思想。不过呢,其实空间也不会消耗太多,因为冗余的只是节点指针。
优点分析
链表,跳表比较
查询流程
如果要在这里面找 21
跳表的主要思想就是这样逐渐建立索引,加速查找与插入。从最上层开始,如果key小于或等于当层后继节点的key,则平移一位;如果key更大,则层数减1,继续比较。最终一定会到第一层
插入流程
先确定该元素要占据的层数 K(采用丢硬币的方式,这完全是随机的)。
然后在 Level 1 … Level K 各个层的链表都插入元素。
用Update数组记录插入位置,同样从顶层开始,逐层找到每层需要插入的位置,再生成层数并插入。
例子:插入 119, K = 2
删除流程
与插入类似
回表这里我们举一个常见的例子。从刚接触代码起,我们就已经开始写登录注册了。那么我们登录的时候账号,密码是如何设置的呢?
数据量小还好,一旦数据量起来的肯定是要添加索引的。问题来了,索引如何建立!
如果只给账号设置索引的话就碰到了回表操作。
MySQL底层是B+树。如果给账号设置索引的话,账号这个字段就成了一个节点树。而我们查询的时候会查询账号+密码。密码不在这颗树上,所以就需要回表去查询密码这个字段然后拼凑在一起。
回表意味着增加磁盘IO的开销,所以避免回表也是优化MySQL的一种方式。还是举登录这个例子,账号密码属于高频查询。给账号+密码创建一个联合索引就可以避免回表了。
剩下的就根据各自的业务场景需求啦。比如软件设计师官网的登录。利用的是身份证+密码。每一种都不一样。
主键自增也是高频面试话题,今天我们就来介绍一下,使用MySQL自增ID可以节省我们多少成本,ID为什么会不连续,上限的一系列问题吧。
存放位置
自增值是保存在表结构定义里的,实际上表结构定义是存放在后缀名为.frm
文件中,但不会保存自增值。
自增值的保存策略
自增值修改机制
如果一个字段为自增字段,在插入一行数据的时
自增值生成算法是:从 auto_increment_offset 开始,以 auto_increment_increment 为步长,持续叠加,直到找到第一个大于 X 的值,作为新的自增值。默认值都是 1
自增值的修改时机
CREATE TABLE `t` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `c` (`c`)
) ENGINE=InnoDB;
假设表中存在一条(1,1,1)数据。如果在插入一条(null,1,1)。执行流程如下
这个时候问题就出来了,2的值没插进去,自增值也没有被改回去,就形成了不连续的情况
还有一种情况就是事务问题。介绍完2个机制1个策略了。直接快刀斩乱麻不啰嗦了。
为了避免两个事务申请到相同的自增 id,肯定要加锁,然后顺序申请。
上面是两个事务在执行插入语句,如果其中一个事务执行失败或者进行了回滚。那么就缺失了一条产生了一条空隙。原本ID为4的数据没有了。而且自增值也是5,也没有改回4。
MySQL之所以没有把ID改回去是因为考虑性能问题!
我们可以举两个反例。如果要退回去的话,肯定要判断退回去的这个ID是否存在。那么如何判断,肯定是要查表的。
每次在申请ID之前,先判断表ID是否存在的话,性能是大打折扣。本来申请ID是一个很快的操作,现在还要去主键索引上判断ID是否存在。
性能: 敢削我性能我锤死你。
还要一种情况就是,完成一个事务提交确认无误之后,再释放锁。这样的话虽然可以保证安全性,但是锁的粒度太大,系统并发能力大大下降。
所以业务方面进行一个平衡,还是选择了性能,没有把ID改回去。
这里可以跟面试官简单的介绍一些历史
5.0版本
系统采用的就是我们上面介绍的比较安全的,并发度偏低的方法。一个语句申请了自增锁,会等语句执行结束之后才释放。
5.1.22版本
对比5.0版本,这里引进了一个策略innodb_autoinc_lock_mode
分析一下insert-select的问题
大家可以回想一下写入日志的事务。如果binlog格式为statement,那么他们如何记录?
如果insert-select采用申请后立即释放的方案。多个数据不过申请主键ID,会出现两个事务交叉的形式。也就是说一个事务的ID数据不是连续的。
一旦两个事务同时执行,要么先写事务A,要么先写事务B。无论哪一种binlog拿去从库执行的时候,insert-select恢复出来的时候ID是连续的,这个库的数据就出现了不一致的情况。
至今MySQL引用的是5.1.22的版本
这个知识点也是大厂比较常问的一个话题。因为大厂的数据量是比较大的,的确会遇到这样的场景。
主键ID自增上限后,就会出现覆盖掉原数据的情况。上线是4294967295,近43亿。
从这个角度看,我们还是应该在 InnoDB 表中主动创建自增主键。因为,表自增 id 到达上限后,再插入数据时报主键冲突错误,是更能被接受的。
毕竟覆盖数据,就意味着数据丢失,影响的是数据可靠性;报主键冲突,是插入失败,影响的是可用性。而一般情况下,可靠性优先于可用性。
一个表中没有主键的话,MySQL会默认建立一个隐藏字段,这个字段就是row_id。默认情况下建立的主键ID都是8个字节的,这个row_id是6个字节的。
like查询以 % 开头
<>
OR语句前后没有同时使用索引
数据类型出现隐式转换
使用is null函数时,不能利用索引,只能全表扫描。(其他函数也要注意)
SQL中有or,也会失效
注意varchar就必须加单引号,如果不加就会误认为int类型,虽然查询效果是一致的。但是索引失效了,增加了查询性能,也多消耗了磁盘IO的开销。
什么是事务呢?事务就是银行的需求一样,如果在执行过程中断电或者不符合条件的情况被停止执行,则已经执行的sql语句全部回滚。也就是说 事务操作过程要不全部成功,要不全部失败!事务ACID的特性可以确保银行不会弄丢你的钱
MVCC是多版本并发控制。通过保存数据在某一个时间点的快照来实现的。也就是说不管需要执行多长时间。每次事务执行的数据都是一致的。相反! 根据事务开始时间的不同选择的快照也是不同的,所以每个事务对同一张表,同一个时刻看到的数据有可能是不一样的。(如果没有这一方面的概念听起来可能有点迷惑)
多版本并发控制实现的不同,典型的实现有乐观锁并发控制与悲观锁并发控制。
MVCC通过每行记录后面保存两个隐藏的列来实现的,一个是保存行的创建时间,一个是保存行的过期时间。存储的不是时间值,而是系统的版本号。每开始一个新的事务,系统版本号会自动增加。事务开始时刻的系统版本号也就是事务的版本号,用来查询到每行记录的版本号进行对比。
优点:保存这两个额外的系统版本号的好处就是 操作数据的时候不需要单独上锁,这样设计使得数据操作很简单,性能也很好。并且也能保证只会读取到符合标准的行。
缺点:每行记录都需要额外的存储空间,需要做更多的检查行的操作,以及额外的维护工作
MVCC只在repertable read(可重复读)和read committed(提交读)两种隔离级别下工作。其他两种隔离级别都和mvcc不兼容!
Tip:read uncommitted总是读取最新的数据行,而不符合当前事务版本的数据行。serializable则会对所有读取的行都加锁
通过set transaction isolationlevel
//设置隔离级别,设置隔离级别会在下一个事务开始的时候生效
事务日志这里常问的大概就是 redo log,undo log。具体的 我就不过多介绍了,写在 第四模块了。这里再提一遍,只是提升一下面试官问到事务的时候,绝对会问到事务日志的。建议多看看!
这种从事务开启到事务结束,如果同一个数据看到不同的结果。我们就称为 幻读。
下面我们举一个例子
事务A 按照一定条件进行数据读取, 期间事务B 插入了相同搜索条件的新数据,事务A再次按照原先条件进行读取时,发现了事务B 新插入的数据 称为幻读
脏读
脏读是在未提交读隔离级别下容易遇到的问题。事务中的修改即使没有提交对其他事务都是可见的,也可以称为脏读
如果是新插入的数据可以采用间隙锁的方式解决幻读的问题。
如果是修改一个数据的话可以采用加锁的方式解决幻读的问题。
隔离级别为,串行化的情况下,幻读是不存在的。因为串行化锁的是整个表。
具体的实现,原理,方式会在11模块介绍MySQL的锁
命令
11.4 间隙锁
11.5 读写锁
11.6 共享锁
11.7 排他锁
11.8 意向锁
11.9 元数据锁
众所周知,MySQL是满足最左匹配原则的。也是面试高频的一个点,一般会让你介绍一下什么是最左匹配原则以及最左匹配原则的应用技巧。下面我们来介绍一下。
假设组合索引为A,B,C。我们分情况一一介绍
根据上述情况我们总结一下,最左原则。即:SQL语句中的对应条件的先后顺序无关。只要出现最左侧的索引树就为最左匹配原则。在explain执行计划中,可以通过key这一列查看是否命中,是否符合最左匹配原则。
个人建议:这里说一下题外话,建议每写一个SQL我们要保存有走执行计划的习惯,如果没命中索引,就把SQL优化一下,时间一长,慢慢的就对SQL优化有了简单的认识,再配上一些理论,你的个人实力绝对会上一层楼的!
MySQL的主从同步问题,这里我们可以跟面试官介绍一下,binlog的三种格式问题,就是因为这三种格式的存在才保证了MySQL的主从同步问题。
命令参数
binlog_format=‘row’
statement
首先就是第一种statement。记录的是大概的信息,几乎是我们的执行信息,我们看不到具体的逻辑是什么。所以如果同步到从库上,很容易会发现数据不一致的情况。
这里格式的优点就是,记录日志比较简洁,占用空间较小,但是风险较大,一旦数据丢失无法找到相应的数据。
row
第二种就是row格式的binlog日志。这种格式的优点就是,日志丰富,只要有row格式的binlog日志,想干什么操作都可以,丢的数据也可以随时(一般是15天)找回来。唯一的缺点就是日志过于丰富,内存占用过大,如果是在线上的话,磁盘写完之后,风险也是比较大的。需要做一些特殊处理。
比如日志定期备份转移,设置一个失效时间。保存15天内的数据,15天外的一概不管!
mixed
这里格式的出现,是上述的结合体,为什么这么说呢?mixed格式,使用了statement格式的优点,同时也使用了row格式的优点。
我想很多读者会感到比较疑惑,世上岂会有十全十美的事呢?下面我们详细介绍一下。
mixed会多做一个判断,他会判断,这个binlog会不会引起数据不一致这个问题。如果会引起,那么就采用row格式的。如果不会引起,那么就采用statement格式的日志。
主从同步
主库,从库在做数据一致性同步的时候主要依靠的就是binlog日志,如果在我们做操作时,日志保存的比较详细,那么就足矣可以保证主从一致性问题。
这里我们扩展两个问题
①:主从切换时的数据安全性问题。有一个A库和B库,客户端一开始访问的是A库,这个时候做了主从切换,主库从A切换到了B。(数据同步线程具有超级管理员权限)客户端访问B库的这一过程中,如果把从库设置成readonly模式
②:主从同步的循环复制问题。节点 A 上更新了一条语句,然后再把生成的 binlog 发给节点 B,节点 B 执行完这条更新语句后也会生成 binlog。(我建议你把参数 log_slave_updates 设置为 on,表示备库执行 relay log 后生成 binlog)。那么,如果节点 A 同时是节点 B 的备库,相当于又把节点 B 新生成的 binlog 拿过来执行了一次,然后节点 A 和 B 间,会不断地循环执行这个更新语句,也就是循环复制了。解决方案如下:
这里不做详细介绍了,为面试打造!详细的技术点看其他文章。
讲到高可用的话,肯定少不了两个策略。可靠性优先策略,可用性优先策略。
面试官问的话肯定要先从源头说起。比如为什么会有高可用,你可以聊一下从硬件的问题,大事务的问题,大表DDL的问题,从库复制能力的问题。这些一系列的问题导致延时,为了高可用的考虑才引进了两个策略。下面介绍了这两个策略是什么。
可靠性优先策略
seconds_behind_master
是否小于某个值,如果大于某个值的话延迟太大会影响业务数据的,所以一定要小于某个值的时候才可以继续下一步seconds_behind_master
的值,直到这个值变成0为止。(因为只有延时足够低,数据才足够安全)这里的第二步把主库A改成了只读模式,这是不可用的时间,这段时间都是不可写的,有数据的话只能等待。
可用性优先策略
与可靠性优先策略对比,唯一的区别就是。这里不等同步完成之后再切换过去和状态修改。而是直接把一系列步骤一次性到位。这样是比较危险的,主备切换的可用性优先策略会导致数据不一致。因此,大多数情况下,我都建议你使用可靠性优先策略。毕竟对数据服务来说的话,数据的可靠性一般还是要优于可用性的。
具体的策略根据业务来定! 听的不是很明白的 群里讨论
前段时间公司上了一套热门方案,分库分表,读写分离,一主多从这类技术栈。考虑到了全局性的唯一ID的问题。我们采用的是雪花算法进行生成唯一ID。
这个算法可以保证,一个机房的一台机器上,在同一毫秒内,生成了一个唯一的 id。可能一个毫秒内会生成多个 id,但是有最后 12 个 bit 的序号来区分开来。
(1)高性能高可用:生成时不依赖于数据库,完全在内存中生成。
(2)容量大:每秒中能生成数百万的自增ID。
(3)ID自增:存入数据库中,索引效率高。
依赖与系统时间的一致性,如果系统时间被回调,或者改变,可能会造成id冲突或者重复。
真实开发过程中,除了一线互联网大厂会有那么多的机器,估计我们不会接触那么多的机器,我们可以改进算法,生成18个bit的ID就够我们使用的了。
这里生成多少位的,取决于公司的体量吧
当数据的体量达到一定级别之后,代码优化,已经达不到真实的性能要求了。下一步就可以考虑分库分表了。
我见过很多人不管什么问题,上来就分库分表是不对的。微信公众号也有很多篇文章的标题也是比较搞笑的《老大让我优化数据库,我上来就分库分表,他过来就是一jio》
下面可以介绍一下分库分表下的两种拆分以及何时拆分
水平拆分,主要拆的一个数据量级的问题。如果一个表中的数据超过500万行,那么就可以考虑进行拆分了。水平拆分的方式类似于医院男女科一样。
来了100个人报名。50个男,50个女。50个男肯定选择男科报名,50个女选择女科报名。有可能例子不恰当大概的意思差不多。
如果表中有原数据,可以采用把ID取模处理。偶数去A表,基数去B表。这个例子应该比较经典吧。
水平拆分的优点:
水平切分的缺点:
就是根据不同的业务进行拆分的,拆分成不同的数据库,比如会员数据库、订单数据库、支付数据库、消息数据库等,垂直拆分在大型电商项目中使用比较常见。
优点:拆分后业务清晰,拆分规则明确,系统之间整合或扩展更加容易。
缺点:部分业务表无法join,跨数据库查询比较繁琐(必须通过接口形式通讯(http+json))、会产生分布式事务的问题,提高了系统的复杂度。举栗子:不可能出现,在订单服务中,订单服务直接连接会员服务的数据库这种情况。
我这里用的是mycat中间件进行拆分。mycat支持10种分片策略
1、求模算法
2、分片枚举
3、范围约定
4、日期指定
5、固定分片hash算法
6、通配取模
7、ASCII码求模通配
8、编程指定
9、字符串拆分hash解析
详细的就不介绍了,我会选择一篇分库分表详细的介绍一下。大概的就是这些了。
内存上的数据和磁盘上的数据页的内容一致时,称为 “干净页”。
内存上的数据和磁盘上的数据页的内容不一致时,称为 “脏页”。
MySQL刷脏页的这个机制,会遇到查询卡顿的情况。为什么这么说呢,我们举一个场景吧。我老家里是开超市的,刷脏页的这个机制就好比我们家的账本,如果在超市营业期间,有人来赊账,我们就可以直接把赊账信息填写在那种临时记事本上,等晚上下班了再把数据转移到超市大赊账本上。
那么如果这段期间,临时记事本用光了,就必须停下手里的工作把临时记事本上的数据全部转移到大赊账本之后,再进行下面的操作,在转移的过程中几乎是属于卡顿情况的。
刷脏页是怎么刷的
首页你要告诉MySQL当前的计算机能刷多少的IO能力,这样innodb才能使出吃奶的力气进行刷脏页上的数据,这样也算是性能最大化吧。这个值不能过高也不能过低,过高的导致查询性能过低,如果过低就导致,刷脏页的数据跟不上添加的数据。最终影响系统的使用性能。
刷脏页比例如何设置
涉及的参数是 innodb_io_capacity
,innodb_max_dirty_pages_pct
第二个参数是控制刷脏页的比例,默认值为75,也就是75%。
假设脏页比例为M,我们范围是从0-100开始计算的,innodb每次写入的时候都有一个序号,这个序号跟checkpoint之间的差值我们设为N。N会算出一个范围0-100之间的数据。然后再根据F2(N)算法继续计算,这个算法比较复杂,我们只需要能说出N越大,算出来的值就越大就好了。
然后用F1(M)和 F2(N)取一个最大值假设为R,之后引擎就可以按照 innodb_io_capacity 定义的能力乘以 R% 来控制刷脏页的速度。
脏页比例是通过 Innodb_buffer_pool_pages_dirty/Innodb_buffer_pool_pages_total
得到的
扩展一个参数
innodb_flush_neighbors。MySQL在刷脏页时,会有一个连坐机制,当前面那个参数为1时,就会启用连坐机制,如果为0时,就不会启用连坐机制。
这个连坐机制是什么呢?如果刷一个脏页为AA,AA的旁边的数据页也是脏页,那么刷到AA的同时会把旁边的BB也一起刷掉。
具体的计算比较复杂,建议一笔带过,大概的流程能说出来就可以了。具体的技术文章在我公众号中。
这个问题应该是被问烂了。这个问题如果学过C语言的时候应该会更容易理解一些。
MySQL中删除数据是采用删除标记的方式。并不是直接删除对应的数据,所以给你的感觉数据的确没有了,但是数据页中仍然存在那块数据内存。
这里扩展一下空间复用的问题。
如果删除的那个数据是在300-700之间,并且插入的那个值的ID也是 300-700之间时,才会去复用这个空间,如果不是这个范围的就不会复用此空间。只有同时删除一整页数据的时候,下一次才会百分之百的复用,这样的几率还是比较小的。
如果不是百分之百的复用那么就会存在一种空洞的现象!我们复现一下,一条1 - 5的记录中,1,2,5被复用了,3,4没有被复用,这种情况就是空洞。
插入也会造成空洞,空洞的主要影响就是数据不紧凑,从而造成查询性能变慢。
解决方案
答案肯定是不会OOM的
首先我们介绍一下,当我们查询200G的数据的流程问题。
从上述流程中我们可以得到,一个查询在发送过程中,占用的 MySQL 内部的内存最大就是 net_buffer_length 这么大,并不会达到 200G;socket send buffer 也不可能达到 200G(默认定义 /proc/sys/net/core/wmem_default),如果 socket send buffer 被写满,就会暂停读数据的流程。
综上所述:MySQL查询是 边读边发 的!
以上内容是公司这边出问题了,我接触到的一些浅的知识,后续将继续维护更新!