MySQL 实战 45 讲(笔记)

课程链接:开篇词 | 这一次,让我们一起来搞懂MySQL-极客时间

开篇词 | 这一次,让我们一起来搞懂MySQL

高频出现的知识,如事务、索引、锁等内容构成专栏的主线

MySQL主机的binlog同步到备机是主机推还是备机拉?

备库注册 主库推送 的模式

01 | 基础架构:一条SQL查询语句是如何执行的

连接mysql时:history可以查看终端的所有密码建立连接后,不会立即执行语句,而是还有其他步骤。 连接成功后,语句会同时传给缓存,和分析器,如果能在缓存中匹配到该语句,则会直接返回其value,执行器里的该语句停止运行,如果缓存中没有匹配到,则分析器继续执行,到执行器才会真正被执行

8.0没有查询缓存的机制,因为命中率太低

using(ID) 是 on t1.id == t2.id的简写 因为联表on条件是两张表中同一个字段,可以简写为using(字段名)

1. 连接器是从权限表里边查询用户权限并保存在一个变量里边以供查询缓存,分析器,执行器在检查权限的时候使用。

2. sql执行过程中可能会有触发器这种在运行时才能确定的过程,分析器工作结束后的precheck是不能对这种运行时涉及到的表进行权限校验的,所以需要在执行器阶段进行权限检查。另外正是因为有precheck这个步骤,才会在报错时报的是用户无权,而不是 k字段不存在(为了不向用户暴露表结构)。

3. 词法分析阶段是从information schema里面获得表的结构信息的。

4. 可以使用连接池的方式,将短连接变为长连接。

5. mysql_reset_connection是mysql为各个编程语言提供的api,不是sql语句。

6. wait_timeout是非交互式连接的空闲超时,interactive_timeout是交互式连接的空闲超时。执行时间不计入空闲时间。这两个超时设置得是否一样要看情况

分析器到查询缓存那个箭头代表更新完回来失效缓存

交互式连接: 通过 mysql 客户端连接.

非交互式连接: 通过 jdbc 方式连接.

存储引擎本身也有缓存的

如何系统学习?

先了解一下大概 然后用 碰到问题查 有一定经验后再系统的看文档

between and跟<=>=是等价操作

02 | 日志系统:一条SQL更新语句是如何执行的?

crash-safe好比数据库重启了,内存中的数据页没有同步到磁盘中,可以通过redo log日志恢复

binlog为什么没有crash_safe的能力呢?binlog日志也记录了所有的操作,也有位点

写入方式的问题,binlog是追加写,crash时不能判定binlog中哪些内容是已经写入到磁盘,哪些还没被写入。而redolog是循环写,从check point到write pos间的内容都是未写入到磁盘的

redo log的两阶段提交:redo log 等待 binlog 写入完成后,由 prepare 变为 commit 提交状态

1 prepare阶段 2 写binlog 3 commit

当在2之前崩溃时 重启恢复:后发现没有commit,回滚。备份恢复:没有binlog 。 一致

当在3之前崩溃 重启恢复:虽没有commit,但满足prepare和binlog完整,所以重启后会自动commit。备份:有binlog. 一致

通过定期的整库备份加上binlog的操作回放可以保证数据的安全性。因为binlog记录的是数据的逻辑操作(原始sql语句)

binlog(归档日志) -> 恢复数据: 从 最近一次全量备份 的时间点 开始; 到 删库跑路 的时间点 结束;

Binlog有两种模式,statement 格式的话是记sql语句, row格式会记录行的内容,记两条,更新前和更新后都有。binlog几大模式,一般采用row,因为遇到时间,从库可能会出现不一致的情况,但是row更新前后都有,会导致日志变大

数据库的扩容,即增加备份库来提高系统读数据库的能力的时候,常采取全量备份+binlog实现。假如binlog和redo log记录的事务的逻辑状态不一致,则会导致严重的主从数据库数据不一致问题

分布式系统也要用到 两阶段提交来保证事务

在什么场景下,一天一备会比一周一备更有优势呢?

在一天一备的模式里,最坏情况下需要应用一天的 binlog,一周一备最坏情况就要应用一周的 binlog 了。更频繁全量备份需要消耗更多存储空间,但是恢复的更快

03 | 事务隔离:为什么你改了我还看不见?

RC级别下,MVCC视图会在每一个语句前创建一个,所以在RC级别下,一个事务是可以看到另外一个事务已经提交的内容,因为它在每一次查询之前都会重新给予最新的数据创建一个新的MVCC视图。 RR级别下,MVCC视图实在开始事务的时候就创建好了,这个视图会一直使用,直到该事务结束。 这里要注意不同的隔离级别他们的一致性事务视图创建的时间点是不同的。 RU:没有视图的概念,直接返回最小行数据。 RC:在每一行SQL语句执行的时候创建。 RR:在事务启动的时候创建。 Serial:通过锁来实现数据访问,没有视图的概念

每条记录更新时除了记录变更记录,还会记录一条变更相反的回滚操作记录,前者记录在redo log,后者记录在undo log

不同时刻启动的事务会有不同的 read-view。同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)

在可重复读隔离级别中,表中的数据其实已经改变,在前面的视图里,需要查找某条记录时,是通过取当前数据,再取视图对应的回滚段回滚到该视图的值

若两个事物,开始值为1,一个要把值加1,一个要加2,最后加2的后提交事物,那最终结果为3?

第二个事务操作那行会被锁住,直到第一个事务提交,所以不会出现3,只会4

系统会判断,当没有事务再需要用到这些回滚日志时,回滚日志会被删除。

为什么尽量不要使用长事务?

长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。对应的undo log不会删除

除了对回滚段的影响,长事务还占用锁资源,也可能拖垮整个库

用什么方案来避免出现或者处理长事务呢?

开发端和服务端两方面

ibdata文件是共享表空间数据文件。 5.7版本支持单独配置undo log的路径和表空间文件。 为什么回滚到清理后,文件还是不会变小?

因为数据和回滚段一起保存在同一个文件中,不能直接删除文件。文件不会变小,清除的空间仅仅是可以分配给其他数据存储,不在被回滚段占用了

1、START TRANSACTION 后,不管autocommit 是1还是0 。 只有当commit数据才会生效,ROLLBACK后就会回滚。 2、当autocommit 为 0 时,不管有没有START TRANSACTION。 只有当commit数据才会生效,ROLLBACK后就会回滚。 3、如果autocommit 为1 ,并且没有START TRANSACTION 。 会自动commit。调用ROLLBACK是没有用的。即便设置了SAVEPOINT

04 | 深入浅出索引(上)

哈希表结构:这个地方有个把key计算成确定的位置,最初是取余数,把余数相同的放到同一位置,这个位置叫做哈希槽。哈希槽相同的key就会产生哈希碰撞,碰撞后就会挂在同一链表上。查找的时候先通过key找到哈希槽,再遍历链表找到对应key匹配的值。就像字典,造字典的人会把相同声母的字放一起,字的拼音就是key,声母就是哈希槽。查字典先根据拼音声母查到具体位置,再去挨个儿找key(拼音)直到找到字

有序数组能够解决hash函数不能满足支持快速范围查询。查找速度为log(n),但缺陷也很明显,针对插入和删除场景,需要挪动后面的整个记录,代价太高。有序数组适用于静态搜索引擎

有序数组不管是等值查找和范围查找都很优秀,可是有序数组不适合更新删除增加。比较适合静态数组静态数据

二叉搜索树的时间复杂度近似等于二分法O(log(N))。 但前提是二叉搜索树近似平衡 二叉搜索树有可能出现极坏的情况,就是变成链表

二叉树也会畸变成单链表,所以才有了AVL树通过旋转的方式来维持树的平衡,但后来发现大量的旋转实在是性能不好,所以有了红黑树。都是二叉树,只是约束条件不一样。没有最好的方法只有更适合的方法,要维持某一方面的优势需要牺牲另一方面的优势,就看如何选择了

为什么数据库存储使用b+树 而不是二叉树,因为二叉树树高过高,每次查询都需要访问过多节点,即访问数据块过多,而从磁盘随机读取数据块过于耗时。B+ 树能够很好地配合磁盘的读写特性,减少单次查询的磁盘访问次数。2叉树的树高会很高 所以会有很多层 而每一层的数据是不太可能连续的 这样导致磁盘在找每一层的数据时候 会有较高的寻址代价

MySql默认一个节点的长度为16K,一个整数(bigint)字段索引的长度为 8B,另外每个索引还跟着6B的指向其子树的指针;所以16K/14B ≈ 1170

N叉树由于在读写上的性能优点,以及适配磁盘的访问模式,广泛应用在数据库引擎上

redis中使用的是跳表的数据结构。

MyISAM和InnoDB两个存储引擎的索引实现方式都是基于B+树,但是两者的实现不同。 MyISAM:叶节点的data域存放的是数据记录的地址。因此,MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录。MyISAM的索引方式也叫做“非聚集”的。MyISAM的辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。 InnoDB:InnoDB的数据文件本身就是索引文件。这棵树的叶节点data域保存了完整的数据记录。InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的所有辅助索引都引用主键作为data域

从性能和存储空间方面考量,自增主键往往是更合理的选择:

自增主键是追加操作,都不涉及到挪动其他记录,也不会触发叶子节点的分裂。

主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。

现在很多业务插入数据很凶残,容易超过int 上限, 实际上是建议设置bigint unsigned

为什么页面中有漏洞?

当对InnoDB进行修改操作时,例如删除一些行,这些行只是被标记为“已删除”,而不是真的从索引中物理删除了,因而空间也没有真的被释放回收。 InnoDB的Purge线程会异步的来清理这些没用的索引键和行,但是依然没有把这些释放出来的空间还给操作系统重新使用,因而会导致页面中存在很多空洞

为什么要重建索引?

索引可能因为删除,或者页分裂等原因,导致数据页有空洞,重建索引的过程会创建一个新的索引,把数据按顺序插入,这样页面的利用率最高,也就是索引更紧凑、更省空间

“N叉树”的N值在MySQL中是可以被人工调整的么?

1, 通过改变key值来调整 N叉树中非叶子节点存放的是索引信息,索引包含Key和Point指针。Point指针固定为6个字节,假如Key为10个字节,那么单个索引就是16个字节。如果B+树中页大小为16K,那么一个页就可以存储1024个索引,此时N就等于1024。我们通过改变Key的大小,就可以改变N的值 2, 改变页的大小 页越大,一页存放的索引就越多,N就越大

innodb B+树主键索引的叶子节点存的是什么?

InnoDB磁盘管理的最小单位就是“页”,也就是说无论是叶子节点、非叶子节点和行数据,都是存放在页当中。 页组成结构有头部数据、主体数据和尾部数据。 头部数据主要存的是页相关数据,例如上一页、下一页、当前页号等。是一个双向链表结构。 主体数据主要关注索引和数据的存储,也就是我们常说的索引和数据的存储位置。主体数据当中有一个“User Records”的概念,用来存储索引和数据,是一个单链表结构。 User Records根据节点的不同,User Records又分为四种不同类型:主键索引树叶子节点和非叶子节点,二级索引树叶子节点和非叶子节点。 有了页和User Records的认识,其实说叶子节点存的是页是一种笼统的回答,基于我的理解,我认为叶子节点(主键索引树叶子节点)存放的是行数据更为贴切

索引只能定位到page,page内部怎么去定位行数据?

内部有个有序数组,二分法。1 页:N 槽,1 槽:1 组,1 组: N 记录。总体:页-槽(即组)-记录。在页内二分定槽(即定组),在组内遍历链表定记录

不论是删除主键还是创建主键,都会将整个表重建

05 | 深入浅出索引(下)

覆盖索引、前缀索引、索引下推

InnoDB会把主键字段放到索引定义字段后面, 当然同时也会去重。 所以,当主键是(a,b)的时候, 定义为c的索引,实际上是(c,a,b); 定义为(c,a)的索引,实际上是(c,a,b) 你看着加是相同的 ps 定义为(c,b)的索引,实际上是(c,b,a)

06 | 全局锁和表锁 :给表加个字段怎么有这么多阻碍?

全局锁、表级锁

写是排他锁,写锁意味着其他线程不能读也不能写。读锁是共享锁,加上后其他锁只能读不能写,本线程也不能写

表不可用的原因是因为 sessionc 申请写锁 并且在队列处于优先,导致 sessionc 后面的所有 读锁 请求申请都被 block 了。这个时候客户端如果有频繁重试的逻辑就会导致不停的和数据库建立连接,把连接池打满导致库不可用

有未提交的事务时无法修改表字段,而且在存在长事务时执行修改表字段命令是一个危险的操作,可能阻塞其它增删改查请求,或导致线程爆满

07 | 行锁功过:怎么减少行锁对性能的影响?

行锁、两阶段锁、死锁和死锁检测

08 | 事务到底是隔离的还是不隔离的?

begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作 InnoDB 表的语句,事务才真正启动。

版本链中版本号不一定是递增的

事务更新数据的时候,只能用当前读。更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)。除了 update 语句外,select 语句如果加锁,也是当前读(lock in share mode 或 for update)

09 | 普通索引和唯一索引,应该怎么选择?

使用普通索引时,数据在内存中直接更新,不在内存的话change buffer记录更新操作,然后定时刷新到磁盘或者读取数据的时候更新读入内存的数据并刷新

change buffer适合写多读少的场景,账单类、日志类的系统

尽量选择普通索引

10 | MySQL为什么有时候会选错索引?

索引统计的更新机制

慢查询日志是真正判断SQL效率的依据。 explain是解释优化器如何工作选择的

11 | 怎么给字符串字段加索引?

使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本

使用前缀索引就用不上覆盖索引对查询性能的优化

12 | 为什么我的MySQL会“抖”一下?

偶尔“抖”一下的那个瞬间,可能就是在刷脏页(flush)

13 | 为什么表数据删掉一半,表文件大小不变?

delete 命令其实只是把记录的位置,或者数据页标记为了“可复用”,但磁盘文件的大小是不会变的

经过大量增删改的表,都是可能是存在空洞的

alter table A engine=InnoDB 命令来重建表

14 | count(*)这么慢,我该怎么办?

按照效率排序的话,count(字段)<count(主键 id)<count(1)≈count(*)

15 | 答疑文章(一):日志和索引相关问题

redo log buffer 就是一块内存,用来先存 redo 日志

16 | “order by”是怎么工作的?

sort_buffer

全字段排序

rowid 排序

17 | 如何正确地显示随机消息?

内存临时表

磁盘临时表

优先队列排序算法

随机排序方法

18 | 为什么这些SQL语句逻辑相同,性能却差异巨大?

案例一:条件字段函数操作

案例二:隐式类型转换

案例三:隐式字符编码转换

19 | 为什么我只查一行的语句,也执行这么慢?

第一类:查询长时间不返回

等 MDL 锁

等 flush

等行锁

第二类:查询慢

20 | 幻读是什么,幻读有什么问题?

幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。幻读仅专指“新插入的行”。幻读在“当前读”下才会出现。

加for update是当前读

binlog日志是在commit提交时进行记录的

行锁

间隙锁 (Gap Lock,可能产生死锁),间隙锁记为开区间

间隙锁和行锁合称 next-key lock,前开后闭区间

读提交的话,就没有间隙锁

21 | 为什么我只改一行的语句,锁这么多?

加锁规则:

原则 1:加锁的基本单位是 next-key lock。希望你还记得,next-key lock 是前开后闭区间。

原则 2:查找过程中访问到的对象才会加锁。

优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。

优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。

一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

案例一:等值查询间隙锁

案例二:非唯一索引等值锁

访问到的对象才会加锁,这个“对象”指的是列,不是 记录行。 补充一下: 加锁,是加在索引上的。 列上,有索引,就加在索引上; 列上,没有索引,就加在主键上

访问到的对象才会加锁,这个“对象”指的是列,不是 记录行。 补充一下: 加锁,是加在索引上的。 列上,有索引,就加在索引上; 列上,没有索引,就加在主键上

案例三:主键索引范围锁

案例四:非唯一索引范围锁

案例五:唯一索引范围锁 bug

案例六:非唯一索引上存在"等值"的例子

案例七:limit 语句加锁

在删除数据的时候尽量加 limit,减小加锁范围

案例八:一个死锁的例子

在分析加锁规则的时候可以用 next-key lock 来分析。但是要知道,具体执行的时候,是要分成间隙锁和行锁两段来执行的

在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务提交或者回滚时才释放。这个就是两阶段锁协议

读提交隔离级别下,锁的范围更小,锁的时间更短,这也是不少业务都默认使用读提交隔离级别的原因

可重复读隔离级别防止幻读可以通过间隙锁来解决

22 | MySQL有哪些“饮鸩止渴”提高性能的方法?

短连接风暴

第一种方法:先处理掉那些占着连接但是不工作的线程。

优先断开事务外空闲的连接,再断开事务内空闲的连接

第二种方法:减少连接过程的消耗

跳过权限验证

慢查询性能问题

导致慢查询的第一种可能是,索引没有设计好。

onlineDDL的理念就是不会因为DDL的操作阻塞DML操作,但是仍然能阻塞其他DDL操作

导致慢查询的第二种可能是,语句没写好。

MySQL 5.7 提供了 query_rewrite 功能,可以把输入的一种语句改写成另外一种模式

导致慢查询的第三种可能,就是MySQL 选错了索引。 加force index。

QPS 突增问题

23 | MySQL是怎么保证数据不丢的?

binlog 的写入机制

binlog cache

sync_binlog控制binlog真正刷盘的频率,对于一个IO非常大的情景,这个数字调大可以提高性能,但是如果容错率非常低的情况下, 必须设为1

redo log 的写入机制

redo log buffer

redo log 的 innodb_flush_..._commit 设置为 1

24 | MySQL是怎么保证主备一致的?

MySQL 主备的基本原理

中转日志(relay log)

binlog 的三种格式对比

statement

主备使用的索引可能是不一致的,最终导致执行删除时删除的数据不一致。这是statement格式的binlog的缺陷

row

记录了真实删除行的主键 id

mixed

mixed 格式的意思是,MySQL 自己会判断这条 SQL 语句是否可能引起主备不一致,如果有可能,就用 row 格式,否则就用 statement 格式

循环复制问题

实际生产上使用比较多的是双 M 结构(互为主备)

25 | MySQL是怎么保证高可用的?

主备延迟

主备延迟的来源

有些部署条件下,备库所在机器的性能要比主库所在的机器性能差

第二种常见的可能了,即备库的压力大

这就是第三种可能了,即大事务。

一次性地用 delete 语句删除太多数据

大表 DDL

主备切换策略

可靠性优先策略,切换流程中是有不可用时间的

可用性优先策略,可能出现数据不一致的情况

MySQL 高可用系统的可用性,是依赖于主备延迟的。延迟的时间越小,在主库故障的时候,服务恢复需要的时间就越短,可用性就越高

binlog 发送是主库主动推送的

26 | 备库为什么会延迟好几个小时?

MySQL 的各种多线程复制策略

27 | 主库出问题了,从库怎么办?

大多数的互联网应用场景都是读多写少

备库和从库的概念是不同的,虽然二者都是只读的,但是从库对外提供服务,而备库只是为主库提供备份

一主多从架构下,主库故障后的主备切换问题

GTID 的全称是 Global Transaction Identifier,也就是全局事务 ID,是一个事务在提交的时候生成的,是这个事务的唯一标识

基于GTID的主备切换: 关键点就是去主和备的交集

28 | 读写分离有哪些坑?

读写分离架构

客户端来选择后端数据库进行查询,一般配合Zookeeper

proxy 的架构

在从库上会读到系统的一个过期状态”的现象,在这篇文章里,我们暂且称之为“过期读”。

处理过期读问题?

强制走主库方案

Sleep 方案

判断主备无延迟方案

  • seconds_behind_master 是否已经等于 0
  • 对比位点确保主备无延迟
  • 对比 GTID 集合确保主备无延迟

配合 semi-sync(半同步复制)

等主库位点方案

GTID 方案 

29 | 如何判断一个数据库是不是出问题了?

select 1 判断

select 1 成功返回,只能说明这个库的进程还在,并不能说明主库没问题

并发连接和并发查询,并不是同一个概念

查表判断

更新判断

外部检测天然有一个问题,就是随机性

内部统计

30 | 答疑文章(二):用动态的观点看加锁

不等号条件里的等值查询

等值查询的过程

怎么看死锁?

怎么看锁等待?show engine innodb status

31 | 误删数据后除了跑路,还能怎么办?

误删行,使用 delete 语句误删了数据行,可以用 Flashback 工具通过闪回把数据恢复回来

误删库 / 表,使用全量备份,加增量日志

延迟复制备库,主动加大同步延迟了

32 | 为什么还有kill不掉的语句?

当对一个表做增删改查操作的时候,加 MDL 读锁;当要对表做结构变更操作的时候,加 MDL 写锁。读锁之间不互斥,因此你可以有多个线程同时对一张表增删改查。读写锁之间、写锁之间是互斥的

收到 kill 以后,线程做什么?类似java发送信号量,不是马上停

kill 无效的第一类情况,即:线程没有执行到判断线程状态的逻辑

另一类情况是,终止逻辑耗时较长

33 | 我查这么多数据,会不会把数据库内存打爆?

net_buffer_length

本地网络栈(socket send buffer)

客户端socket receive buffer

如果客户端使用–quick 参数,会使用 mysql_use_result 方法。这个方法是读一行处理一行

对于正常的线上业务来说,如果一个查询的返回结果不会很多的话,我都建议你使用 mysql_store_result 这个接口,直接把查询结果保存到本地内存

WAL:Write-Ahead Logging

先写redo log到log buffer,具体内容就是针对哪个表空间的哪些页面做了哪些修改,然后log buffer中的日志内容会在某些时候写到redo日志文件中,比如事务提交时。至于为什么写redo日志会比刷新内存中的数据页到磁盘快,是因为服务器在启动时就已经给redo日志文件分配好了一块物理上连续的磁盘空间,每次写redo日志都是往文件中追加写,并没有寻址的过程。而修改过的数据页要刷新到磁盘的话,可能对应的磁盘空间并不是物理连续的,找起来费劲

内存命中率要在 99% 以上

MySQL 采用的是边算边发的逻辑,因此对于数据量很大的查询结果来说,不会在 server 端保存完整的结果集。所以,如果客户端读结果不及时,会堵住 MySQL 的查询过程,但是不会把内存打爆

对于 InnoDB 引擎内部,由于有淘汰策略,大查询也不会导致内存暴涨。并且,由于 InnoDB 对 LRU 算法做了改进,冷数据的全表扫描,对 Buffer Pool 的影响也能做到可控

34 | 到底可不可以使用join?

Index Nested-Loop Join(最优)

应该让小表来做驱动表。前提是“可以使用被驱动表的索引”

Simple Nested-Loop Join

Block Nested-Loop Join

join_buffer中比对,时间复杂度上来说,和Simple一样,但是内存操作,速度上会快很多

如果explain 结果里面,Extra 字段里出现“Block Nested Loop”字样,尽量不要使用,因为走的是M*N全表数据判断

在决定哪个表做驱动表的时候,应该是两个表按照各自的条件过滤,过滤完成之后,计算参与 join 的各个字段的总数据量,数据量小的那个表,就是“小表”,应该作为驱动表

35 | join语句怎么优化?

Multi-Range Read 优化

如果按照主键的递增顺序查询的话,对磁盘的读比较接近顺序读,能够提升读性能。MRR的核心是将索引的回表变成批量的顺序读;

Batched Key Access

BKA 算法的优化要依赖于 MRR

基于临时表的改进方案,对于能够提前过滤出小数据的 join 语句来说,效果还是很好

扩展 -hash join

36 | 为什么临时表可以重名?

内存表和临时表的区别

临时表的特性

临时表只能被创建它的 session 访问,所以在这个 session 结束的时候,会自动删除临时表

临时表的应用

临时表经常会被用在复杂查询的优化过程中。其中,分库分表系统的跨库查询就是一个典型的使用场景

为什么临时表可以重名?

临时表链表,命名和线程相关

临时表和主备复制

用户临时表和内部临时表

37 | 什么时候会使用内部临时表?

group by 的几种实现算法

如果语句执行过程可以一边读数据,一边直接得到结果,是不需要额外内存的,否则就需要额外的内存,来保存中间结果;

join_buffer 是无序数组,sort_buffer 是有序数组,临时表是二维表结构;

如果执行逻辑需要用到二维表特性,就会优先考虑使用临时表。比如我们的例子中,union 需要用到唯一索引约束, group by 还需要用到另外一个字段来存累积计数

38 | 都说InnoDB好,那还要不要使用Memory引擎?

可以看到,由于重启会丢数据,如果一个备库重启,会导致主备同步线程停止;如果主库跟这个备库是双 M 架构,还可能导致主库的内存表数据被删掉。因此,在生产上,我不建议你使用普通内存表。

基于内存表的特性,我们还分析了它的一个适用场景,就是内存临时表。内存表支持 hash 索引,这个特性利用起来,对复杂查询的加速效果还是很不错的

39 | 自增主键为什么不是连续的?

1.插入时的其他唯一索引冲突 2.事务回滚 3.同一语句批量插入

40 | insert语句的锁为什么这么多?

insert … select 是很常见的在两个表之间拷贝数据的方法。你需要注意,在可重复读隔离级别下,这个语句会给 select 的表里扫描到的记录和间隙加读锁。

而如果 insert 和 select 的对象是同一个表,则有可能会造成循环写入。这种情况下,我们需要引入用户临时表来做优化。

insert 语句如果出现唯一键冲突,会在冲突的唯一值上加共享的 next-key lock(S 锁)。因此,碰到由于唯一键约束导致报错后,要尽快提交或回滚事务,避免加锁时间过长。

41 | 怎么最快地复制一张表?

为了避免对源表加读锁,更稳妥的方案是先将数据写到外部文本文件,然后再写回目标表

使用 mysqldump 命令将数据导出成一组 INSERT 语句

导出 CSV 文件

物理拷贝方法

可传输表空间

42 | grant之后要跟着flush privileges吗?

用户权限范围从大到小,正常情况下,grant 命令之后,没有必要跟着执行 flush privileges 命令。

全局权限

db 权限

表权限和列权限

flush privileges 使用场景

43 | 要不要使用分区表?

MySQL 在第一次打开分区表的时候,需要访问所有的分区;

在 server 层,认为这是同一张表,因此所有分区共用同一个 MDL 锁;

在引擎层,认为这是不同的表,因此 MDL 锁之后的执行过程,会根据分区表规则,只访问必要的分区。

分区表的一个显而易见的优势是对业务透明,相对于用户分表来说,使用分区表的业务代码更简洁。还有,分区表可以很方便的清理历史数据   alter table t drop partition

每年年底时再把下一年度的 12 个新分区创建上即可

44 | 答疑文章

join 的写法

使用 left join 时,左边的表不一定是驱动表

如果需要 left join 的语义,就不能把被驱动表的字段放在 where 条件里面做等值判断或不等值判断,必须都写在 on 里面

Simple Nested Loop Join 的性能问题

distinct 和 group by 的性能

备库自增主键问题

45 | 自增id用完怎么办?

表的自增 id 达到上限后,再申请时它的值就不会改变,进而导致继续插入数据时报主键冲突的错误。

row_id 达到上限后,则会归 0 再重新递增,如果出现相同的 row_id,后写的数据会覆盖之前的数据。

Xid 只需要不在同一个 binlog 文件中出现重复值即可。虽然理论上会出现重复值,但是概率极小,可以忽略不计。

InnoDB 的 max_trx_id 递增值每次 MySQL 重启都会被保存起来,所以我们文章中提到的脏读的例子就是一个必现的 bug,好在留给我们的时间还很充裕。

thread_id 是我们使用中最常见的,而且也是处理得最好的一个自增 id 逻辑了。

猜你喜欢

转载自blog.csdn.net/qq_35572013/article/details/128365863