mysql的一些知识点

mysql

一条SQL更新语句是如何执行的

1.执行器先找引擎取 ID=2 这一行。ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2 这一行所在的数据页本来就在内存中(change buffer),就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。
2.执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
3.引擎将这行新数据更新到内存中(change buffer),同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
4.执行器生成先把日志写到 binlog cache,事务提交的时候,再把 binlog cache 写到 binlog 文件中。
5.执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。

redo log的写入有两个步骤,第一次是praper状态,事务commit后才会更新为commit状态。

redo log

redo log 是 InnoDB 引擎特有的, redo log 是物理日志,记录的是“在某个数据页上做了什么修改” ,会有专门的pure线程处理redo log

undo log

回滚日志,在数据修改的时候,不仅记录了redo,还记录了相对应的undo,如果因为某些原因导致事务失败或回滚了,可以借助该undo进行回滚。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。MVCC中,一个记录有多个版本,要得到某个版本的值,就是通过undo log计算到的。

事务与mvcc

Innodb实现事务隔离,是通过mvcc和锁实现的。一致性读的时候(就是快照读),不需要加锁,直接通过mvcc实现。

快照读(所有的select语句)

在实现上, InnoDB 为每个事务构造了一个数组,用来保存这个事务启动瞬间(事务启动是指第一次查询sql时,不是 start transtion),当前正在“活跃”的所有事务 ID。“活跃”指的就是,启动了但还没提交。这个数组中,最小值称为低水位,最大值加1称为高水位。读取一行数据得到 row trx_id,通过这个数组来判断是否可见,如果不可见,通过undo log得到上一个版本的数据。

当前读

如果是当前读,就必须是读最新的版本信息,就要加上行锁锁住这行。一直到当前的事务提交。当前读的情况有:update t set k=k+1 where id=1 ….

为什么要重建表

innodb经过大量的增删改之后,数据页会留下大量空洞,浪费磁盘空间,重建表可以解决这个问题。

页合并和页分裂

innodb删除某个记录,其实就是标记这个位置可复用。 当页中删除的记录达到MERGE_THRESHOLD(默认页体积的50%),InnoDB会开始寻找最靠近的页(前或后)看看是否可以将两个页合并以优化空间使用。 当插入或者更新记录时,当前页已经满了,需要在这个页中间插入一个记录,产生页分裂,同时产生页空洞。

重建表

你可以使用 alter table A engine=InnoDB 命令来重建表。

联合索引的使用

index(name, age),先按照name排序,然后是age排序,索引上保留name和age的值

覆盖索引

查询的字段已经在索引上获取,不用回表查询。

最左前缀原则
索引下推

select * from tuser where name like ‘张%’ and age=10

会从索引找到张开头的数据,然后过滤age=10后,再去回表查询。就是说会尽力在索引中过滤完条件再去回表,而不是回表完在过滤其他where条件。

Order By使用索引

innodb的索引有两大功能,快速定位记录,还有就是排序功能。尽力保证order by的字段利用到索引

Change Buffer和redo log

change buffer作用,当需要更新一个数据页时,如果数据页在内存中就直接更新,而如果这个数据页还没有在内存中的话,在不影响数据一致性的前提下(没有唯一索引),InnoDB 会将这些更新操作缓存在 change buffer 中,这样就不需要从磁盘中读入这个数据页了。在下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行 change buffer 中与这个页有关的操作。通过这种方式就能保证这个数据逻辑的正确性。

写入或者更新一条数据

对于唯一索引来说,需要将数据页读入内存,判断到没有冲突,插入这个值,语句执行结束;

对于普通索引来说,则是将更新记录在 change buffer,语句执行就结束了。(如果马上有读取这个记录,还是需要去读取这行记录到内存,然后merge,所以说如果说change bufer更适合在写多读少的情况,并且没有用到唯一索引)

redo log 主要节省的是随机写磁盘的 IO 消耗(转成顺序写),而 change buffer 主要节省的则是随机读磁盘的 IO 消耗。

Mysql与客户端如何交互

我们查询一个大数据的时候,需要担心把mysql内存爆掉吗?

实际上,服务端并不需要保存一个完整的结果集。取数据和发数据的流程是这样的:
1.获取一行,写到 net_buffer 中。这块内存的大小是由参数 net_buffer_length 定义的,默认是 16k。
2.重复获取行,直到 net_buffer 写满,调用网络接口发出去(socket)。如果发送成功,就清空 net_buffer,然后继续取下一行,并写入 net_buffer。
3.如果发送函数返回 EAGAIN 或 WSAEWOULDBLOCK,就表示本地网络栈(socket send buffer)写满了,进入等待。直到网络栈重新可写,再继续发送。

也就是说,MySQL 结果是“边读边发的”,这个概念很重要。这就意味着,如果客户端接收得慢,会导致 MySQL 服务端由于结果发不出去,这个事务的执行时间变长。

如果net_buffer写满了,mysql执行show processlist显示Sending to client

mysql客户端接收服务端返回结果的方式有两种:

一种是本地缓存,也就是在本地开一片内存,先把结果存起来。如果你用 API 开发,对应的就是 mysql_store_result 方法。

另一种是不缓存,读一个处理一个。如果你用 API 开发,对应的就是 mysql_use_result 方法。

Mysql内部临时表

union

会有去重操作,所以会生成一个临时表保存数据,每次获取一行,需要判断是否存在。union all不需要去重,所以不会生成临时表,直接返回数据即可。

group by

select a,count(1) as num from t group by a

1.创建内存临时表,表里有两个字段 a 和 num,主键是 a; //group by 字段就是主键

2.从t中扫描数据(可能会选择一个索引),写入到内存临时表中。由于a是主键,所以写入第一条后,后面的只会修改聚合的字段,比如num+1.

3.遍历完后,再根据a字段排序,得到结果集返回客户端。 如果你的需求并不需要对结果进行排序,那你可以在 SQL 语句末尾增加 order by null

explain结果,extra字段包含,Using temporary; Using filesort

磁盘临时表

内存临时表的大小是有限制的,参数 tmp_table_size 就是控制这个内存大小的,默认是 16M。 这时候就会把内存临时表转成磁盘临时表,磁盘临时表默认使用的引擎是 InnoDB

group by 优化方法 – 索引

不论是使用内存临时表还是磁盘临时表,group by 逻辑都需要构造一个带唯一索引的表,执行代价都是比较高的。 首先看看临时表的作用,因为扫描的数据得到的a是无序的,所以需要构造一个主键为a的临时表,好随时修改对应主键的聚合值。如果扫描出来就是a有序的,就不需要临时表了。

explain 语句,extra显示,Using index,表示group by 索引字段,不用临时表,不用排序

group by 优化方法 – 直接排序

如果没有办法避开临时表和排序,可以估算数据,直接走磁盘临时表。不然会先放到内存临时表,插入一部分数据后,发现内存临时表不够用了再转成磁盘临时表”,看上去就有点儿傻。

Explain的解读

mysql的一些知识点

ID:

ID值越大,越先被执行,如果Id值相同,就从上往下执行。

mysql的一些知识点
如上图所示,在id为1时,table显示的是 <derived2> ,这里指的是指向id为2的表,即t3表的衍生表。

type

const 表示通过索引一次就找到了,const用于比较primary key 或者unique索引。因为只匹配一行数据,所以很快。如将主键置于where列表中,MySQL就能将该查询转换为一个常量。
eq_ref 唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描
ref 非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体。
range 只检索给定范围的行,使用一个索引来选择行,key列显示使用了哪个索引,一般就是在你的where语句中出现between、< 、>、in等的查询,这种范围扫描索引比全表扫描要好,因为它只需要开始于索引的某一点,而结束于另一点,不用扫描全部索引。
index Full Index Scan,Index与All区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小。(也就是说虽然all和Index都是读全表,但index是从索引中读取的,而all是从硬盘读取的)
all Full Table Scan 将遍历全表以找到匹配的行

mysql的主从

备库 B 跟主库 A 之间维持了一个长连接。主库 A 内部有一个线程,专门用于服务备库 B 的这个长连接。一个事务日志同步的完整过程是这样的:
1.在备库 B 上通过 change master 命令,设置主库 A 的 IP、端口、用户名、密码,以及要从哪个位置开始请求 binlog,这个位置包含文件名和日志偏移量。
2.在备库 B 上执行 start slave 命令,这时候备库会启动两个线程,io_thread 和 sql_thread。
3.其中 io_thread 负责与主库建立连接。主库 A 校验完用户名、密码后,开始按照备库 B 传过来的位置,从本地读取 binlog,发给 B。
4.备库 B 拿到 binlog 后,写到本地文件,称为中转日志(relay log)。sql_thread 读取中转日志,解析出日志里的命令,并执行。

binlog的格式

statement
当 binlog_format=statement 时,binlog 里面记录的就是 SQL 语句的原文,由于执行过程可能使用到的索引不同,可能会造成主从不一致。
row
当 binlog_format 使用 row 格式的时候,binlog 里面记录了所有变化的行的id,所以两边执行的sql修改的一定是同一行。
mix
两种方式并存

本作品采用《CC 协议》,转载必须注明作者和本文链接
用过哪些工具?为啥用这个工具(速度快,支持高并发...)?底层如何实现的?
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!