0::MySQL的IO操作 对于操作系统的读取文件的流程,磁盘的IO操作相对于内存操作是比较慢的,为了提高读取速度,我们应该尽量减少磁盘的IO操作,而操作系统一般以4KB为一个数据页读取磁盘数据, 而mysql的话则一般以16kb为一个数据页,已经读取的数据会缓存在操作系统内存的pagecache中,同时会顺序读取相邻的其他的数据页,同时也会保存在pagecache中,这样能提高 文件的读取速率。
1:'事务执行失败,是否会立刻回滚
sql1 insert 成功
sql2 insert 报错
commit sql1入库
begin sql1入库
start transaction sql1入库
"rollbacl" 事务回滚
对于事务执行过程中,如果因为sql异常,是不会自动执行事务回滚的,"需要手动执行rollback",
而且事务的提交的时候,如果执行 begin,commit,start transaction都会导致上一个事务提交。
这个时候如果事务中有错误的数据,这个事务依然会成功提交。
2:binlog,redo log,undo log 的写入顺序
1:undolog buffer
2:数据内存修改
3:redolog
4:binlog
3:事务的隔离级别("InnoDB的默认隔离级别是可重复读")
读未提交,对于数据的读取,如果该数据已经被其他事务修改,但还没有提交事务,也还是能读取到其他事务修改的数据值
读提交,对于数据的读取,如果该数据已经被其他的事务修改,但还没有提交事务,这个时候读取不到其他事务修改后的数据值,但是如果已经事务提交了,可以读取到其他事务修改的数据值
可重复读,对于数据的读取,如果该数据已经被其他的事务修改,无论事务是否已经提交,多次读取到的数据值和第一次读取的是保持一致的。
串行,事务之间串行执行
4:ACID
A:原子性,事务要么全部执行,要么全部不执行
C:一致性,要保证数据库的数据完整性和一致性。
I:隔离性,事务和事务之间相互隔离,事务和事务之间数据不能相互影响。
D:持久性,已经提交的事务,那么对数据库的数据变更将永远保存到数据库中。
5:MVCC:多版本并发控制协议
读提交 和 可重复读
通过一致性视图+事务id确认数据版本的可见性,实现MVCC。
读提交和可重复读的区别:
视图的创建时间。
rr是在事务创建的那一刻或者事务创建执行第一条语句的时候,为整个事务创建统一的一致性视图。
rc是在事务中,每条语句执行前,都创建一个一致性视图。
对于可重复读,查询只承认在事务启动前就已经提交完成的数据。
对于读提交,查询只承认在查询语句执行前已经提交完成的数据。
(事务启动的时候,会使用一个数组记录所有正在执行的事务id,有高位id和低位id,通过一致性视图和事务的高位id和事务的低位id,判断事务数据是否可见)
6:binlog 特点
binlog 是server层的,是逻辑日志,是二进制日志,有三种数据格式,row,statement,row+statement,
statement记录的具体修改的sql语句
优点:只需要记录在master执行的语句的细节和执行语句时候的上下文的信息。这样可以减少binlog文件的大小,减少磁盘IO,提高性能。
''' 缺点:在主从复制的时候,对于特定情况下的存储过程,或者function或trigger调用和触发会导致主从复制不一致的问题。
row不记录sql的上下文信息,仅仅记录哪条数据被修改了,修改成什么样子。
优点:不会出现在某种特定情况下的存储过程,或function,或者trigger调用和触发无法被正确复制的问题。
''' 缺点:binlog的数据量会比较大,例如alter table的时候会让日志暴涨。因为对于alter table这类的表结构变更语句的处理方式是整个表的每一行记录都需要变更,实际上就是重建了整张表,那么每一行数据的变更都会记录在binlog中。
row+statement:混合,一般的复制使用STATEMENT模式保存binlog,对于STATEMENT模式无法复制的操作使用ROW模式保存binlog,MySQL会根据执行的SQL语句选择日志保存方式
7:什么是undolog
undolog主要用于事务回滚和MVCC,是逻辑日志,记录的是sql的操作语句。
事务回滚就是把事务操作的数据恢复到事务执行前的状态。
MVCC就是多版本并发控制协议,通过一致性视图和事务id,获取数据版本的可见性。
undolog的写入的话是也需要写对应的redolog,记录undolog的插入记录。
undolog的存储是在共享表空间的回滚段中,会有一个后台线程定时清理undolog日志,但是在清理的时候需要判断该undolog没有被其他事务使用。
写入:
例如一条数据更新,先写undolog buffer,然后更新内存中数据页的数据,然后写redolog buffer,记录undolog的插入数据,以及修改的数据具体的数据页的内容。
通过WAL机制,提高写入效率。
为什么undolog的操作 需要写入到redolog中。
MySQL在进行数据恢复的时候,会重做所有的事务,包括未提交的事务和已经回滚的事务。需要通过undolog回滚未提交的事务。
''' 8:什么是redo log
redolog是物理日志,记录的是数据在数据页具体的修改。
redolog的大小是固定的,比如可以配置为一组4个文件,每个文件1G,文件从头开始写,写到尾部又循环回到头部继续写。
redolog有一个writepos,是当前记录的位置,一边写一边后移。
还有一个是checkpoint,是当前要擦除的位置,也是往后推移并且循环的,擦除记录前需要把记录更新到数据文件。
如果writepos追上了checkpoint,MySQL就需要暂停服务,对数据进行刷盘。
使用redolog,InnoDB就可以保证及时数据库发生异常重启,之前提交的记录都不会丢失,这个能力成为crash-safe。
redo log的刷盘
1:后台线程每秒写入一次
2:其他的事务提交的时候,也会把另外的事务的redo log也刷盘。
3:内存不足
'''9:change buffer
"changhe buffer使用的是pool buffer的内存大小,是有大小限制的,一般是50%,内存放不下,或者通过checkpoint的机制,对change buffer进行落盘"
change buffer 主要通过减少磁盘的随机读IO,提高MySQL的性能。
change buffer 和redo log一样,也是通过checkpoint机制处理
change buffer 主要用于优化普通二级索引性能,更新,修改,插入。
对于数据的变更,新增,先写到changebuffer中,等下一次查询的时候,通过merge把数据刷新到数据页中,并且写redolog。
merge的流程的话是:
1:从磁盘读取数据页到内存中。
2:通过change buffer修改内存中更新的数据内容。
3:记录数据变更的redolog,和change buffer的变更操作。
merge的触发:
1:读取数据页的时候merge
2:后台线程定期merge
3:数据库正常关闭的过程中会进行merge
change buffer会进行持久化,保存在系统表空间中(ibdata1,或者叫做共享表空间),并且会进行写redolog,这样可以保障在系统宕机的时候,能够通过redolog进行恢复,起到crash-safe的作用。
''' 一个疑问:通过二级索引修改数据的时候,这个时候能使用change buffer吗?毕竟这个时候已经去查询db,回表拿到主键索引的数据。
10:主从同步的机制
{MasterA
{ start->undolog(memory)->data(memory)->redolog(prepare)->binlog->redolog(commit)->ack
{ |
{ v
{ dump_thread----|
{ bg_thread->undolog(disk)->data(disk) |
{ v
{slaveB data<-sql_thread<-relay log<-io_thread
{
"同步的时候是备库主动拉取"
master和slave会建立一个长链接,这个master中会有一个专门的线程来服务该长链接(dump_thread)
同步流程:'(两个阶段 【1创建,从->主】【2过程 主-从】)
"开始创建主从同步的时候,由从库制定具体需要拉取的位置"
1:slave通过change master命令,设置master的IP、端口、用户名、密码、以及要从那个位置开始请求binlog,这个位置包含文件名和日志偏移量。
2:slave执行start slave命令,启动io_thread和sql_thread线程,其中io_thread负责和主库建立长链接。
3:master交验完用户名,密码后,开始按照slave传过来的位置,从本地读取binlog,发送给slave
4:slave拿到binlog后,写本地文件,称为中转日志relay log
5:sql_thread读取relay log,解析出日志里面的命令,并执行。
"主从同步搭建完成之后"
1:由主库来决定要发送的Binlog数据给从库
11:锁
1:行锁:
行锁有一个两阶段锁协议,
'什么是两阶段锁协议:行锁在需要的时候才会加上,然后在事务提交之后,才会释放。
可以分为读锁s和写锁x
行锁和行锁之间可能会产生冲突
读和读 不会冲突
读写 冲突
写读 冲突
写写 冲突
2:间隙锁:
间隙锁之间不会产生冲突
因为间隙锁的目的都是为了保护这个间隙,不让新的数据插入。
"同一个事务中,该事务的间隙锁和行数不会相互影响,其他的事务间隙锁才会影响本事务的行锁"
3:next-key lock
"在可重复度隔离级别下才会生效"
间隙锁和行锁的结合叫做 next-key lock,是一个前开后闭的锁(x,y]
1:上锁的最小单位是next-key lock
2:查询中访问到的对象才会加锁
3:索引上的等值查询,唯一索引查询的时候,退化为行锁
4:索引上的等值查询,如果向右遍历的时候且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁
5:唯一索引上的范围查询,会访问到不满足条件的第一个值为止
4:"间隙锁和gap锁导致死锁案例"
sessionA sessionB
T1 锁住间隙A
T2 锁住间隙A
T3 向间隙A中写入数据(blocked)
T4 向间隙A中写入数据(error Deadlock found)
分析:sessionA和sessionB的,因为执行到T2时刻是还没有任何问题的,因为间隙锁之间不冲突
然后T3时刻,向间隙中写入数据,被sessionA的间隙锁阻塞,进入等待。
然后T4时刻,向间隙中写入数据,被sessionB的间隙锁阻塞,两个session相互等待,导致死锁。
不过因为"innoDB的死锁检测",让sessionA的一局报错返回了.
发现死锁后,会"restarting transaction",'该事务从头开始 也就是begin这行,其他的事务继续执行
这个时候会发现,sessionB被阻塞的操作会被执行,写入了数据,sql执行成功,但是还没提交。
表级锁:
MDL:元数据锁(meta data lock),不需要显示加锁,会被自动加上,只要是用来防止表变更的,保证数据的读写的正确性。
例子:
'MDL的读锁不会互斥。MDL的写和读,读和写,写和写会互斥。
事务A对表结构进行变更,是会加MDL写锁,这个时候,其他的事务没办法对表进行任何的数据读写。
MDL可能会导致一些新能问题,多个事务在执行,如果前面的事务执行了sql读写操作,但是还没有提交,然后有一个事务执行了alter table 等对表结构的操作语句,
会导致后面的所有的事务都被阻塞,如果客户端有重试机制,可能会把MySQL的连接池打满。
"1:主要是因为长事务导致,解决办法可以把长事务kill掉。
2:在alter table设置等待时间,超时后放弃alter table操作,这样可以让后面的语句能执行,之后再手动重试alter table"
表锁: lock tables … read/write 加锁
unlock tables 主动释放锁,也可以在客户端断开的时候自动释放锁。
"注意: read的话,所有事务就只能只读,事务也不能写;write的话本事务可以读写,其他事务无法操作"
12:索引
B+树
聚簇索引的话,叶子节点是数据页
二级索引的话,叶子节点是主键索引的id
索引的优化
1:组合索引
MySQL的索引是最左前缀原则
2:索引下推
在二级索引查询的时候,会通过判断索引上的数据先做一部分的逻辑过滤,然后拿到符合的主键id,然后再通过回表查询具体的数据行
3:覆盖索引
在二级索引查询的时候,需要查询的数据已经在索引上了,可以避免回表的操作,直接通过二级索引拿到需要的所有数据。
查询:
"普通索引的查询会一直查询直到遇到第一个不满足条件的记录位置。
唯一索引的查询,查找到第一个满足条件的记录后,就会停止继续查询。"
13:排序
'如果单行的内容很大,这样会导致sort_buffer存放不下太多的行,也会导致需要划分为多个临时文件,从而导致性能下降。
选择使用哪个算法:取决于排序的行的大小
1:全字段排序
1:初始化sort_buffer,确认放入表列的字段。
2:从索引中找到满足的一条记录,获取到具体的主键id
3:通过主键id,把字段内容存入到sort_buffer中
4:重复 2,3流程,直到所有都查询完成。
5:对soet_buffer中需要排序的字段进行"快速排序"。
6:按照排序结果,返回需要的limit行数
'问题:
1:sort_buffer使用的是内存,会有大小限制,如果需要排序的内容过大,则需要借助磁盘临时文件进行归并排序,把多个文件的排序结果合并成一个真正有序的结果集。
2:具体由mysql的sort_buffer_size控制。
3:临时文件很多的话,性能会很差
2:rowID排序
'如果单行的内容很大,这样会导致sort_buffer存放不下太多的行,也会导致需要划分为多个临时文件,从而导致性能下降。
0:mysql通过控制单行最大排序长度,决定是否使用rowid排序
流程如下:
1:初始化sort_buffer,确认放入需要排序的字段,也就是主键和需要排序的字段
2:从索引找到满足的一行记录,获取到主键id
3:通过主键id,获取到具体的数据行记录,然后把需要排序的字段和主键id写入到sort_buffer中
4:重复2,3步骤
5:对sort_buffer排序。
6:通过排序结果,获取到具体的排序后的主键id,然后回表查询,获取所有的数据,返回给客户端。
"对于排序的优化"
需要排序的字段,可以加组合索引,这样就可以天然有序。
14:什么是幻读,幻读有什么问题
在rr隔离级别下,当前读的时候,对同一范围的查询,多次查询出来的行数不一致。
1:在可从复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此,幻读在当前读下才会出现。
2:数据被其他的事务修改,然后通过当前读读取到修改后的数据,这个时候是不能称为幻读,幻读是专门针对新插入的数据。(假设其他的事务自动提交)
幻读出现的问题:
1:语义 事务原本以为已经锁住所有需要变更的数据,但是因为其他的事务新增数据,导致其他事务中的语义被破坏,因为多出了没有被锁住的新的数据。
2:数据一致性 事务在做数据变更的时候,原以为锁住了所有需要变更的数据,后来其他的事务写入新的数据,导致原本的事务在更新的时候,多更新了其他事务新增的数据,导致数据一致性被破坏。
经过 T1 时刻,id=5 这一行变成 (5,5,100),当然这个结果最终是在 T6 时刻正式提交的 ;
经过 T2 时刻,id=0 这一行变成 (0,5,5);
经过 T4 时刻,表里面多了一行 (1,5,5);
其他行跟这个执行序列无关,保持不变。
这样看,这些数据也没啥问题,但是我们再来看看这时候 binlog 里面的内容。
T2 时刻,session B 事务提交,写入了两条语句;
T4 时刻,session C 事务提交,写入了两条语句;
T6 时刻,session A 事务提交,写入了 update t set d=100 where d=5 这条语句。
不论是拿到备库去执行,还是以后用 binlog 来克隆一个库,这三行的结果,都变成了 (0,5,100)、(1,5,100) 和 (5,5,100)。
解决幻读:
1:间隙锁 (PS:"间隙锁是在可重复读隔离级别下才会生效")
间隙锁带来的问题:
影响了并发度,因为锁住的范围更加的大了。间隙内的都会被锁住。
2:也可以通过binlog日志格式为row+rc隔离级别解决幻读。
15:MySQL脏页刷盘,四种情况
内存中的数据页有3种,
未使用的数据页
干净页,也就是存储了读取的数据
脏页,对读取的数据页做了数据变更
1:内存不足,需要对脏页刷盘
策略是 LRU
2:redolog写满,需要移动checkpoint,进行刷盘
redolog写满的情况,会导致MySQL的写操作跌0,停止所有的更新操作。
3:系统认为mysql空闲的时候,进行刷盘
4:mysql关闭之前,需要进行刷盘。
16:索引的选择
索引的选择是通过优化器来处理的(server层)
影响因素:
1:扫描的行数,因为这个会影响到磁盘IO的次数,和对CPU资源的消耗
2:是否使用临时表
3:是否排序
4:是否回表
判断依据:
1:如何对扫描行数判断
在真正执行语句之前,innodb是无法精准地知道满足这个条件的记录数是多少,只能通过统计信息来估算记录数。
这个统计信息就是索引的区分度。索引上不同值(也就是索引上的"基数cardinality")越多,区分度就越高。
主要是通过采用统计,InnoDB默认选取N个数据页("具体的索引树上的"),统计上面的不同值,得到一个平均值,然后用平均值乘以索引的页面数,就是这个基数。
N 和 M在不同策略下都是不一样的,是否保存内存,是否持久化到持平。"N/M => disk:20/10; memory:8/16"
'当变更的数据行数超过1/M的时候,会重新统计基数,或者通过analyze table z命令来重新统计。
17:表数据删除,表空间的回收
数据的删除流程:
对于删除的记录,innoDB只是会"标记"该记录被删除。如果后续新增,可能会复用这个位置。但是,磁盘文件的大小并不会缩小。
如果对于数据页的删除,那么会对整个数据页进行复用。"数据页会被标记为可复用"
'PS:记录的复用和数据页的复用是不同的,记录的复用只限于符合范围条件的数据。
例如400~600之间,删除了500,然后那么新增500记录,可以服用原本的500,如果新增800,那么不会服用原有的500的位置。
"页合并":如果相邻的两个数据页的利用率都非常小,系统就会把这两个数据页合并到其中一个数据页上,另外一个数据页被标记为可复用。
所以,对于delect命令其实只是把记录的位置,或者数据页标记为"可复用",但磁盘文件的大小是不会变的。
'索引的随机插入,可能会导致数据页的分裂,另外更新索引上的值,可以理解为删除一个旧的值,再插入一个新的值。这也会造成空洞。
"解决":通过重建表
alter table a engine=InnoDB命令来重建表。MySQL会自动完成转存数据,交换表名,删除旧表。
不过有个问题:主键id会被覆盖,导致其他表存储的主键id错乱。
18:count(*)操作流程
1:MyISAM是把表的总行数保存在磁盘中,因此执行count(*)的时候直接返回这个数据,效率很高。
2:InnoDB是需要把数据一行一行地从引擎里面读出来,然后累积计数,所以表数据量很大的时候 count会非常的慢。
"注意:都是以无条件的全表count(*)为例,如果需要带where条件,MyISAM也不会返回得很快"
'为什么InnoDB使用一行一行的
因为及时在同一时刻的多个查询中,因为MVCC的原因,InnoDB需要返回的行数也是不确定的。
因为多个事务中,对于不同版本的数据的可见性不一样,就会导致计算出来的行数都有差异。
"count的优化:逻辑上来说,普通索引上的count和主键索引上的count最终的结果是一致的,
普通索引比主键索引的小很多,所以可以通过统计扫描较小的普通索引减少扫描的数据量,以此来优化统计数量。"
'count(*),mysql做了优化,并不会把所有字段的值都取出来,而是专门做了优化,不取值。count(*)肯定不为null,按行累加。
19:性能分析原则:
1:server层要什么给什么
2:InnoDB只给必要的数据
20:crash-safe
1:如果redolog写了,但是binlog没写,就crash(奔溃)了,那么会在奔溃恢复的时候,会对事务进行回滚,而且此时,binlog还没写,所以不会对从库有影响。
2:如果redolog写了,Binlog写了,然后crash了。
1:如果redolog已经是commit状态,则直接提交事务。
2:如果redolog是prepare状态:
1:binlog是否完成,如果完整,则提交事务。
2:如果binlog不完整,则回滚事务。
3:'binlog的完整性判断。
/~ 一个事务的binlog有完整性的格式。
"statement:末尾会有COMMIT;
row:末尾会有一个XID event;"
以及可以通过binlog-checksum参数,来校验binlog的正确性(对于磁盘原因,日志中间可能会出现错误,可以通过checksum来发现)。
21:处于 prepare 阶段的 redo log 加上完整 binlog,重启就能恢复,MySQL 为什么要这么设计?
因为Binlog如果已经写完整,那么从库会接收到该Binlog进行主从同步,所以主库也需要提交这个事务,保证数据的一致性。
22:正常运行中的实例,数据写入后的最终落盘,是从 redo log 更新过来的还是从 buffer pool 更新过来的呢?
redolog中并没有记录完整的数据页的记录,所以它并没有能力去更新磁盘数据页。
"1:如果是正常运行,内存数据页被修改了之后,跟磁盘的数据页不一致,我们称之为脏页。最终数据落盘,就是把内存中的数据页写盘,整个过程,甚至和redolog毫无关系。
2:如果崩溃恢复,InnoDB如果判断到数据页可能在崩溃恢复的时候丢失了更新,那么会把该数据页加载到内存,然后通过redolog对内存数据页进行更新。
更新完成后,内存数据页就变成脏页,就由回到第一种情况。"
23:为什么这些SQL语句逻辑相同,性能却差异巨大?
' 破坏
eg: select * from tableName where id + 1 = 1000; 破坏
eg: select * from tableName where convert(name using utf8) = "cxcz"; 破坏
"对于字段,不能使用函数,但是对于搜索条件值,可以使用函数,这样不会破坏"
' 不破坏
eg: select * from tableName where id = 1000 + 1; 不破坏
g: select * from tableName where name = convert( "cxcz" using utf8); 不破坏
因素:
1:对字段做函数操作,可能会破坏索引值的有序性,因此优化器就会决定放弃树搜索功能。
2:隐式类型转换 (string 和 int之类的)
3:隐式字符串编码转换 (utf8 和 gbk之类的)