《MySQL45讲》笔记
-
建立连接的过程复杂,应尽量减少建立连接动作,尽量使用长连接。但是长连接不断开就不会释放占用的内存,积累下来可能会导致oom,造成mysql 重启
- 定期断开长连接
- 每执行一次比较大的操作后,通过执行mysql_reset_connection 来初始化连接资源(仅限于5.7及之后版本)
-
查询缓存弊大于利。每次更新就会清空缓存
-
redo log(重做日志)innoDB特有,记录数据,如c=1,在innoDB引擎空闲时,把这个记录更新到磁盘里
-
binlog(归档日志)Server 层实现,所有引擎都可以使用,记录语句原始逻辑,如c=0+1
-
undo log
-
两阶段提交,保证数据和逻辑一致性。
- redo log :肚子里有食物
- binlog :吃食物的过程
- 先写redo log后写binlog:在写redo log未写binlog 时crash,恢复时,肚子里已经有食物了,但是没吃
- 先写binlog后写redo log:在写binlog未写redo log 时crash,恢复时,吃了,但是肚子里没有食物
- 无疑上述两种情况都是不合理的
-
innodb_flush_log_at_trx_commit = 1;每次食物的redo log 都持久化到磁盘,可以保证mysql 异常重启后数据不丢失。
-
可重复读隔离级别下,SQL在执行的时候会创建一个视图,避免不可重复度的问题
-
尽量不要使用长事务:长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。长事务还会占用锁资源
-
建议总是使用set autocommit=1, 通过显式语句的方式来启动事务
-
主键索引和非主键索引的区别:非主键索引在非索引覆盖的情况下,会搜索索引树得到主键id,再通过主键id 的索引树搜索到数据,称为回表。
-
主键索引一般选用自增字段,主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小,在kv 场景(只有一个索引且该索引必须是唯一索引)可以选择业务字段作为主键。
-
创建索引时应考虑覆盖索引的情况。
-
对大表频繁进行insert 和delete 操作,时间一长会缠上表碎片,insert 随机值作为主键id, 会产生很多数据页分裂操作,而delete 一些排列有序的主键值,折线delete 的空间不会被直接释放,而是仅仅进行delete 的标记。重建表可以释放空余空间。
alter table A engine=Innodb
-
官方自带的逻辑备份工具是mysqldump,使用single-transaction 方法可以启动一个事务来确保一致性。single-transaction方法只适用于所有的表使用事务引擎的库。
-
Flush tables with read lock(FTWRL) 命令可以让数据库处于只读状态。典型的使用场景是做全库逻辑备份。
-
改变表结构会对表加上MDL锁,这个时候增删改查都会被阻塞,如果这个时候,有频繁的查询语句,超时后客户端有重启机制,会再起一个新的session,让这个库的线程很快爆满。理想的解决机制是:在改表语句中设置超时时间,拿不到先放弃,之后重试。
-
两条事务并行的时候,后启动的事务会被阻塞,知道第一条执行commit后。
-
两阶段锁协议:航所在需要的时候加上,等到事务结束时释放。因此在写事务的时候顺序越靠前的事务行被锁住的时间越长。
-
死锁
-
在rr 隔离级别下,事务在启动的时候会创建一个基于整库的快照,InnoDB利用了“所有数据都有多个版本”的这个特性,实现了“秒级创建快照”的能力。
- 如果落在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的;
- 如果落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;
- 如果落在黄色部分,那就包括两种情况
a. 若 row trx_id在数组中,表示这个版本是由还没提交的事务生成的,不可见;
b. 若 row trx_id不在数组中,表示这个版本是已经提交了的事务生成的,可见。
id = 1,k = 1
事务A 在启动时,事务C 还未开始,处于红色部分,未开始区域,不可见;事务B 在事务A 启动后启动,处于黄色部分,并且事务B 在事务A 提交后提交,黄色部分第一种情况,不可见。因此,事务A select 语句的值为1。
思考题
-
定期全量备份的周期“取决于系统重要性,有的是一天一备,有的是一周一备”。那么在什么场景下,一天一备会比一周一备更有优势呢?或者说,它影响了这个数据库系统的哪个指标?
A:好处是“最长恢复时间”更短。在一天一备的模式里,最坏情况下需要应用一天的binlog。比如,你每天0点做一次全量备份,而要恢复出一个到昨天晚上23点的备份。一周一备最坏情况就要应用一周的binlog了。系统的对应指标就是提到的RTO(恢复目标时间)。
-
系统里面应该避免长事务,如果你是业务开发负责人同时也是数据库负责人,你会有什么方案来避免出现或者处理这种情况呢?
MA:1. 事务超时回滚;
A:从应用开发端,1. 确认是否使用
set autocommit=0
。这个确认工作可以在测试环境中开展,通过general_log 的日志来确认。2. 确认不必要的事务(select)。3. 根据业务本身的预估,通过SET MAX_EXECUTION_TIME
命令来控制每个语句的执行最长时间,避免单个语句意外执行太长时间(意外原因?)。? 从数据库端:1. 监控 information_schema.Innodb_trx 表,设置长事务阈值,超过就报警/Kill。2. percona 的pt-kill 工具。3. 测试阶段要求输出general_log,分析日志行为提前发现。4. mysql 5.6+ 的版本,吧innodb_undo_tablespaces 设置成2(或者更大)
-
对于上面例子中的InnoDB表T,如果你要重建索引 k,你的两个SQL语句可以这么写:
alter table T drop index k; alter table T add index(k);
如果你要重建主键索引,也可以这么写:
alter table T drop primary key; alter table T add primary key(id);
我的问题是,对于上面这两个重建索引的作法,说出你的理解。如果有不合适的,为什么,更好的方法是什么?
MA:重建索引k,先加索引,再删除。
A:重建索引k的做法是合理的,可以达到省空间的目的。但是,重建主键的过程不合理。不论是删除主键还是创建主键,都会将整个表重建。所以连着执行这两个语句的话,第一个语句就白做了。这两个语句,你可以用这个语句代替 :
alter table T engine=InnoDB
。 -
DBA小吕在入职新公司的时候,就发现自己接手维护的库里面,有这么一个表,表结构定义类似这样的:
CREATE TABLE `geek` ( `a` int(11) NOT NULL, `b` int(11) NOT NULL, `c` int(11) NOT NULL, `d` int(11) NOT NULL, PRIMARY KEY (`a`,`b`), KEY `c` (`c`), KEY `ca` (`c`,`a`), KEY `cb` (`c`,`b`) ) ENGINE=InnoDB;
公司的同事告诉他说,由于历史原因,这个表需要a、b做联合主键,这个小吕理解了。
但是,学过本章内容的小吕又纳闷了,既然主键包含了a、b这两个字段,那意味着单独在字段c上创建一个索引,就已经包含了三个字段了呀,为什么要创建“ca”“cb”这两个索引?
同事告诉他,是因为他们的业务里面有这样的两种语句:
select * from geek where c=N order by a limit 1; select * from geek where c=N order by b limit 1;
我给你的问题是,这位同事的解释对吗,为了这两个查询模式,这两个索引是否都是必须的?为什么呢?
MA:第一句不用,第二句要。根据最左匹配原则,第一句可以用到主键索引,而第二句不行。
A:是ca可以去掉,cb需要保留。
-
备份一般都会在备库上执行,你在用–single-transaction方法做逻辑备份的过程中,如果主库上的一个小表做了一个DDL,比如给一个表上加了一列。这时候,从备库上会看到什么现象呢?
MA:备库会在执行完备份后,再进行DDL。
A:备份关键语句
/* 为了确保RR 隔离级别,再设置一次 */ Q1:SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; /* 启动事务 */ Q2:START TRANSACTION WITH CONSISTENT SNAPSHOT; /* other tables */ /* 设置一个保存点 */ Q3:SAVEPOINT sp; /* 时刻 1 */ /* 拿到表结构 */ Q4:show create table `t1`; /* 时刻 2 */ /* 拿数据,导数据 */ Q5:SELECT * FROM `t1`; /* 时刻 3 */ /* 回滚到sp,这里是为了释放t1 的DML锁 */ Q6:ROLLBACK TO SAVEPOINT sp; /* 时刻 4 */ /* other tables */
因此有下面几种情况:
- 如果在Q4 语句执行之前到达,现象:没有影响,备份拿到的是DDL 后的表结构
- 如果在时刻2 到达,则表结构被改过,Q5 执行的时候报
Table definition has changed, please retry transaction
,现象:mysqldump 终止 - 如果在时刻2 和时刻3 之间到达,mysqldump 占着t1 的DML 读锁,binlog被阻塞,现象:主从延迟,直到Q6 执行完成
- 如果从时刻4 开始,mysqldump 释放了MDL 读锁,现象:没有影响,备份拿到的是DDL 前的表结构
-
如果你要删除一个表里面的前10000行数据,有以下三种方法可以做到:
- 第一种,直接执行delete from T limit 10000;
- 第二种,在一个连接中循环执行20次 delete from T limit 500;
- 第三种,在20个连接中同时执行delete from T limit 500。
你会选择哪一种方法呢?为什么呢?
MA:第二种,第一种会造成较长的阻塞。第三种消耗过多的资源。