MYSQL实战45讲
第1讲 一条SQL查询语句是如何执行的?
1.Server层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖MySQL的大多数核心服务
功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在
这一层实现,比如存储过程、触发器、视图等。
2.而存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持InnoDB、MyISAM、
Memory等多个存储引擎。现在最常用的存储引擎是InnoDB,它从MySQL 5.5.5版本开始成为了
默认存储引擎。
第2讲 日志系统:一条SQL更新语句是如何执行的?
WAL技术,WAL的全称是Write-Ahead Logging,它的关键点就是先写日志,再写磁盘,也就是先写粉板,等不忙的时候再写账本。
与查询流程不一样的是,更新流程还涉及两个重要的日志模块,redo log(重做日志)和 binlog(归档日志)。如果接触MySQL,那这两个词肯定是绕不过 的,我后面的内容里也会不断地和你强调。
Redo log和binlog的两阶段提交
redo log和binlog都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。
第3讲 事务隔离:为什么你改了我还看不见?
查询长事务
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60
第4讲 深入浅出索引(上)
InnoDB采用的B+树结构,以及为什么 InnoDB要这么选择。
B+树能够很好地配合磁盘的读写特性,减少单次查询的磁盘访问次数。
第5讲 深入浅出索引(下)
覆盖索引、最左前缀原则、索引下推
第6讲 全局锁和表锁 :给表加个字段怎么有这么多阻碍?
全局锁主要用在逻辑备份过程中。对于全部是InnoDB引擎的库,我建议你选择使用–single-
transaction参数,对应用会更友好。
表锁一般是在数据库引擎不支持行锁的时候才会被用到的。如果你发现你的应用程序里有lock
tables这样的语句,你需要追查一下。
要么是你的系统现在还在用MyISAM这类不支持事务的引擎,那要安排升级换引擎;
要么是你的引擎升级了,但是代码还没升级。我见过这样的情况,最后业务开发就是把lock
tables 和 unlock tables 改成 begin 和 commit,问题就解决了。
第7讲 行锁功过:怎么减少行锁对性能的影响?
如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁的申请时机尽量往后放。
第8讲 事务到底是隔离的还是不隔离的?
InnoDB的行数据有多个版本,每个数据版本有自己的rowtrx_id,每个事务或者语句有自己的一 致性视图。普通查询语句是一致性读,一致性读会根据rowtrx_id和一致性视图确定数据版本的 可见性。
对于可重复读,查询只承认在事务启动前就已经提交完成的数据;
对于读提交,查询只承认在语句启动前就已经提交完成的数据;
而当前读,总是读取已经提交完成的最新版本。
第9讲 普通索引和唯一索引,应该怎么选择?
普通索引会使用Change buffer,唯一索引不会。
InooDB会将这些更新操作缓存在change buffer中,这样就不需要从磁盘中读入这个数据页了。在下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行change buffer中与这个页有关的操作。
innodb_change_buffer_max_size来动态设置
第10讲 MYSQL为什么回选错索引?
analyze table t 命令,可以用来重新统计索引信息
第11讲 怎么给字符串字段加索引?
1 | select |
第12讲 为什么我的MYSQL会抖一下?
可能是在进行flush操作,刷脏页
innodb_io_capacity参数,它会告诉InnoDB你的磁盘能力。这个值我建议你设置成磁盘的IOPS。磁盘的IOPS可以通过fio这个工具来测试。
1 | fio -filename=$filename -direct=1 -iodepth 1 -thread -rw=randrw -ioengine=psync -bs=16k -size=500M -n |
InnoDB的刷盘速度就是要参考这两个因素:一个是脏页比例,一个是redo log写盘速度。
参数innodb_max_dirty_pages_pct是脏页比例上限,默认值是75%。
合理地设置innodb_io_capacity的值,并且平时要多关注脏页比例,不要让它经常接近75%.
1 | select VARIABLE_VALUE into @a from global_status where VARIABLE_NAME = 'Innodb_buffer_pool_pages_dirty'; |
第13讲 为什么表数据删掉一半,表文件大小不变?
数据删除流程导致表数据页空洞
重建表
推荐用GitHub开源的gh-ost来做。
第14讲 count(*)这么慢,我该怎么办?
count(字段)<count(主键id)<count(1)≈count(),所以我建议你,尽量使用count(\)
第15讲 答疑文章(一)日志和索引相关问题
A和B相互关注的问题
比较A和B的大小,如果A<B
1 | begin; |
如果A>B
1 | begin; |
第16讲 “order by”是怎么工作的?
确定一个排序语句是否使用了临时文件。
1 | /* 打开optimizer_trace,只对本线程有效 */ |
MYSQL优先全字段排序> rowid排序
第17讲 “order by”是怎么工作的?
内存临时表
order by rand()使用了内存临时表,内存临时表排序的时候使用的是rowid排序方法。
归并排序 => 结果全部有序
磁盘临时表
优先队列排序算法 => 小根堆
随机排序算法
第18讲 为什么这些SQL语句逻辑相同,性能却差异巨大?
1 | CREATE TABLE `tradelog`( |
对索引字段做函数操作,可能会破坏索引值的有序性,因此优化器就决定放弃走树搜索功能。
第19讲 为什么我只查一行的语句,也执行这么慢?
第一类:查询长时间不返回
等MDL锁
通过查询sys.schema_table_lock_waits这张表,直接找出造成阻塞的process id。
等flush
等行锁
select * from t sys.innodb_lock_waits where locked_table='test'.'t'\G
第20讲 幻读是什么,幻读有什么问题?
产生幻读的原因是,行锁只能锁住行,但是新插入记录这个动作,要更新的是记录之间的“间隙”。因此,为了解决幻读问题,InnoDB只好引入新的锁,也就是间隙锁(Gap Lock)
第21讲 为什么我只改一行的语句,锁这么多?
两个原则、两个优化、一个bug
原则1:加锁的基本单位是next-key lock。希望你还记得,next-key lock是前开后闭区间。
原则2:查找过程中访问到的对象才会加锁。
优化1:索引上的等值查询,给唯一索引加锁的时候,next-key lock退化为行锁。
优化2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁。
一个bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。
第22讲 MYSQL有哪些“饮鸩止渴”提高性能的方法?
慢查询
存储过程实现,语句改写规则
1 | insert into query_rewrite.rewrite_rules(pattern, replacement, pattern_database) values ("select * from t where id + 1 = ?", "select * from t where id = ? - 1", "db1"); |
第23讲 MySQL是怎么保证数据不丢的?
只要redo log和binlog保证持久化到磁盘,就能确保MySQL异常重启后,数据可以恢复。
第24讲 MySQL是怎么保证主备一致的?
mixed格式的意思是,MySQL自己会判断这条SQL语句是否可能引起主备不一致,如果有可能,就用row格式,
否则就用statement格式。
第25讲 MySQL是怎么保证高可用的?(浅了解)
主备延迟:机器差、备库压力大、大事务、备库的并行复制能力
数据库运维系统都有备库延迟监控,其实就是在备库上执行 showslave status,采集seconds_behind_master的值
第26讲 备库为什么会延迟好几个小时?(浅了解)
从这些分析中,你也会发现大事务不仅会影响到主库,也是造成备库复制延迟的主要原因之一。
因此,在平时的开发工作中,我建议你尽量减少大事务操作,把大事务拆成小事务。
第27讲 主库出问题了从库怎么办?(浅了解)
如果你使用的MySQL版本支持GTID的话,我都建议你尽量使用GTID模式来做一主多从的切换。
第28讲 读写分离有哪些坑?
在实际应用中,这几个方案是可以混合使用的。
比如,先在客户端对请求做分类,区分哪些请求可以接受过期读,而哪些请求完全不能接受过期
读;然后,对于不能接受过期读的语句,再使用等GTID或等位点的方案。
第29讲 如何判断一个数据库是不是出问题了?
select 1这样的方法是不是已经被淘汰了呢,但实际上使用非常广泛的MHA(MasterHigh Availability),默认使用的就是这个方法。
其实,每个改进的方案,都会增加额外损耗,并不能用“对错”做直接判断,需要你根据业务实际情况去做权衡。
我个人比较倾向的方案,是优先考虑update系统表,然后再配合增加检测performance_schema 的信息。
第30讲 答疑文章(二):用动态的观点看加锁
介绍了怎么看showengine innodb status输出结果中的事务信息和死锁信息。
第31讲 误删数据后除了跑路,还能怎么办?
删行
使用delete语句误删了数据行,可以用Flashback工具通过Flashback把数据恢复回来。
Flashback恢复数据的原理,是修改binlog的内容,拿回原库重放。而能够使用这个方案的前提
是,需要确保binlog_format=row和 binlog_row_image=FULL。
删库
假如有人中午12点误删了一个库,恢复数据的流程如下:
\1. 取最近一次全量备份,假设这个库是一天一备,上次备份是当天0点;
\2. 用备份恢复出一个临时库;
\3. 从日志备份里面,取出凌晨0点之后的日志;
\4. 把这些日志,除了误删除数据的语句外,全部应用到临时库。
预防误删库/表
账号分离、必须先对表做改名操作
第32讲 为什么还有kill不掉的语句?
客户端连接 -A关闭自动补全
我们感知的慢,不是服务端慢,而是客户端慢。
–quick参数的意思,是让客户端变得更快。
第33讲 我查这么多数据会不会把数据库内存打爆?
MYSQL是边读边发数据给客户端。
“Sending data”并不一定是指“正在发送数据”,而可能是处于执行器过程中的任意阶段。
执行showengine innodb status ,可以看到“Buffer pool hit rate”字样,显示的就是当前的命中率。
InnoDB内存管理用的是最近最少使用 (Least RecentlyUsed, LRU)算法,这个算法的核心就是淘汰最久未使用的数据。
LRU算法的操作逻辑:
\1. 扫描过程中,需要新插入的数据页,都被放到old区域;
\2. 一个数据页里面有多条记录,这个数据页会被多次访问到,但由于是顺序扫描,这个数据页第一次被访问和最后一次被访问的时间间隔不会超过1秒,因此还是会被保留在old区域;
\3. 再继续扫描后续的数据,之前的这个数据页之后也不会再被访问到,于是始终没有机会移到链表头部(也就是young区域),很快就会被淘汰出去。
这个策略最大的收益,就是扫描这个大表的过程中,虽然也用到了Buffer Pool,但是对young区域完全没有影响,从而保证了Buffer Pool响应正常业务的查询命中率
第34讲 到底可不可以使用join?
这个过程就跟我们写程序时的嵌套查询类似,并且可以用上被驱动表的索引,所以我们称之为“IndexNested-Loop Join”
简称NLJ。前提是“可以使用被驱动表的索引”。
“Block Nested-Loop Join”,特点为被驱动表上没有可用的索引。
要不要使用join语句时,就是看explain结果里面,Extra字段里面有没有出现“Block Nested Loop”字样。
什么是小表?
过滤条件后select的数据条数少的。
第35讲 join语句怎么优化?
在这些优化方法中:
\1. BKA优化是MySQL已经内置支持的,建议你默认使用;
\2. BNL算法效率低,建议你都尽量转成BKA算法。优化的方向就是给被驱动表的关联字段加上索引;
\3. 基于临时表的改进方案,对于能够提前过滤出小数据的join语句来说,效果还是很好的;4. MySQL目前的版本还不支持hash join,但你可以配合应用端自己模拟出来,理论上效果要好于临时表的方案。
哈希join 在业务端实现。
第36讲 为什么临时表可以重名?
临时表一般用于处理比较复杂的计算逻辑。由于临时表是每个线程自己可见的,所以不需要考虑多个线程执行同一个处理逻辑时,临时表的重名问题。在线程退出的时候,临时表也能自动删除,省去了收尾和异常处理的工作。
第37讲 什么时候会使用内部临时表?
了group by的几种实现算法,从中可以总结一些使用的指导原则:
\1. 如果对group by语句的结果没有排序要求,要在语句后面加 order bynull;
\2. 尽量让group by过程用上表的索引,确认方法是explain结果里没有Using temporary和 Using filesort;
\3. 如果group by需要统计的数据量不大,尽量只使用内存临时表;也可以通过适当调大tmp_table_size参数,来避免用到磁盘临时表;
\4. 如果数据量实在太大,使用SQL_BIG_RESULT这个提示,来告诉优化器直接使用排序算法得到group by的结果。
第38讲 都说InnoDB好那还要不要使用Memory引擎?
如果一个备库重启,会导致主备同步线程停止;如果主库跟这个备库是双M架构,还可能导致主库的内存表数据被删掉。
就是内存临时表。内存表支持hash索引,这个特性利用起来,对复杂查询的加速效果还是很不错的。
主库暂时不能修改引擎,那么就把备库的内存表引擎先都改成InnoDB。
第39讲 自增主键为什么不是连续的?
在MyISAM引擎里面,自增值是被写在数据文件上的。而在InnoDB中,自增值是被记录在内存的。MySQL直到8.0版本,才给InnoDB表的自增值加上了持久化的能力,确保重启前后一个表的自增值不变。
第40讲 insert语句的锁为什么这么多?
insert 语句如果出现唯一键冲突,会在冲突的唯一值上加共享的next-key lock(S锁)。因此,碰到由于唯一键约束导致报错后,要尽快提交或回滚事务,避免加锁时间过长。
第41讲 怎么最快地复制一张表?
物理拷贝
mysqldump
select …into outfile的方法是最灵活的,支持所有的SQL写法。但,这个方法的缺点之一就
是,每次只能导出一张表的数据,而且表结构也需要另外的语句单独备份。
第42讲 grant之后要跟着flush privileges吗?
grant语句会同时修改数据表和内存,判断权限的时候使用的是内存数据。因此,规范地使用grant和revoke语句,是不需要随后加上flush privileges语句的。
flush privileges语句本身会用数据表的数据重建一份内存权限数据,所以在权限数据可能存在不一致的情况下再使用。而这种不一致往往是由于直接用DML语句操作系统权限表导致的,所以
我们尽量不要使用这类语句。
第43讲 要不要使用分区表?
有两个问题需要注意:
\1. 分区并不是越细越好。实际上,单表或者单分区的数据一千万行,只要没有特别大的索引,对于现在的硬件能力来说都已经是小表了。
\2. 分区也不要提前预留太多,在使用之前预先创建即可。比如,如果是按月分区,每年年底时再把下一年度的12个新分区创建上即可。对于没有数据的历史分区,要及时的drop掉。