网站建设培训 通州,巩义网站建设案件数据,百度快照查询入口,深圳找个人做网站文章目录 1. 自增主键为什么不是连续的#xff1f;1.1 自增值保存在哪儿#xff1f;1.2 自增值修改机制1.2.1 自增值的修改时机1.2.2 自增值为什么不能回退? 1.3 自增锁的优化1.3.1 自增锁设计历史 2. Insert语句为何很多锁?2.1 insert … select 语句2.2 insert 循环写入2… 文章目录 1. 自增主键为什么不是连续的1.1 自增值保存在哪儿1.2 自增值修改机制1.2.1 自增值的修改时机1.2.2 自增值为什么不能回退? 1.3 自增锁的优化1.3.1 自增锁设计历史 2. Insert语句为何很多锁?2.1 insert … select 语句2.2 insert 循环写入2.3 insert 唯一键冲突2.4 insert into … on duplicate key update 3. 如何快速复制一张表3.1 mysqldump 方法3.2 导出 CSV 文件3.3 物理拷贝方法 1. 自增主键为什么不是连续的
创建一个表 t其中 id 是自增主键字段、c 是唯一索引。
CREATE TABLE t (id int(11) NOT NULL AUTO_INCREMENT,c int(11) DEFAULT NULL,d int(11) DEFAULT NULL,PRIMARY KEY (id),UNIQUE KEY c (c)
) ENGINEInnoDB;1.1 自增值保存在哪儿
在这个空表 t 里面执行 insert into t values(null, 1, 1); 插入一行数据再执行 show create table 命令就可以看到如下图所示的结果
图 1 自动生成的 AUTO_INCREMENT 值 可以看到表定义里面出现了一个 AUTO_INCREMENT2表示下一次插入数据时如果需要自动生成自增值会生成 id2。
这个输出结果容易引起这样的误解自增值是保存在表结构定义里的。实际上表的结构定义存放在后缀名为.frm 的文件中但是并不会保存自增值。
不同的引擎对于自增值的保存策略不同。
MyISAM 引擎的自增值保存在数据文件中。InnoDB 引擎的自增值其实是保存在了内存里并且到了 MySQL 8.0 版本后才有了“自增值持久化”的能力也就是才实现了“如果发生重启表的自增值可以恢复为 MySQL 重启前的值”具体情况是 在 MySQL 5.7 及之前的版本自增值保存在内存里并没有持久化。每次重启后第一次打开表的时候都会去找自增值的最大值 max(id)然后将 max(id)1 作为这个表当前的自增值。举例来说如果一个表当前数据行里最大的 id 是 10AUTO_INCREMENT11。这时候我们删除 id10 的行AUTO_INCREMENT 还是 11。但如果马上重启实例重启后这个表的 AUTO_INCREMENT 就会变成 10。也就是说MySQL 重启可能会修改一个表的 AUTO_INCREMENT 的值。在 MySQL 8.0 版本将自增值的变更记录在了 redo log 中重启的时候依靠 redo log 恢复重启之前的值。
1.2 自增值修改机制
在 MySQL 里面如果字段 id 被定义为 AUTO_INCREMENT在插入一行数据的时候自增值的行为如下
如果插入数据时 id 字段指定为 0、null 或未指定值那么就把这个表当前的 AUTO_INCREMENT 值填到自增字段如果插入数据时 id 字段指定了具体的值就直接使用语句里指定的值。
根据要插入的值和当前自增值的大小关系自增值的变更结果也会有所不同。假设某次要插入的值是 X当前的自增值是 Y。
如果 XY那么这个表的自增值不变如果 X≥Y就需要把当前自增值修改为新的自增值。
新的自增值生成算法是 从自增的初始值(auto_increment_offset) 开始以步长(auto_increment_increment) 为步长持续叠加直到找到第一个大于 X 的值作为新的自增值。auto_increment_offset 和 auto_increment_increment默认值都是 1 在一些场景下使用的就不全是默认值。比如双 M 的主备结构里要求双写的时候我们就可能会设置成 auto_increment_increment2让一个库的自增 id 都是奇数另一个库的自增 id 都是偶数避免两个库生成的主键发生冲突 当 auto_increment_offset 和 auto_increment_increment 都是 1 的时候新的自增值生成逻辑很简单就是
如果准备插入的值 当前自增值新的自增值就是“准备插入的值 1”否则自增值不变。
1.2.1 自增值的修改时机
在这两个参数都设置为 1 的时候自增主键 id 却不能保证是连续的这是什么原因呢 要回答这个问题要看一下自增值的修改时机。 假设表 t 里面已经有了 (1,1,1) 这条记录这时我再执行一条插入数据命令
insert into t values(null, 1, 1); 语句的执行流程就是
执行器调用 InnoDB 引擎接口写入一行传入的这一行的值是 (0,1,1);InnoDB 发现用户没有指定自增 id 的值获取表 t 当前的自增值 2将传入的行的值改成 (2,1,1);将表的自增值改成 3继续执行插入数据操作由于已经存在 c1 的记录所以报 Duplicate key error语句返回。
执行流程图如下
图 2 insert(null, 1,1) 唯一键冲突 可以看到这个表的自增值改成 3是在真正执行插入数据的操作之前。 语句真正执行的时候因为碰到唯一键 c 冲突所以 id2 这一行并没有插入成功但也没有将自增值再改回去。
所以在这之后再插入新的数据行时拿到的自增 id 就是 3。
图 3 一个自增主键 id 不连续的复现步骤 唯一键冲突是导致自增主键 id 不连续的第一种原因。 同样地事务回滚也会产生类似的现象这就是第二种原因。
1.2.2 自增值为什么不能回退?
假设有两个并行执行的事务在申请自增值的时候为了避免两个事务申请到相同的自增 id肯定要加锁然后顺序申请。
假设事务 A 申请到了 id2 事务 B 申请到 id3那么这时候表 t 的自增值是 4之后继续执行。事务 B 正确提交了但事务 A 出现了唯一键冲突。如果允许事务 A 把自增 id 回退也就是把表 t 的当前自增值改回 2那么就会出现这样的情况表里面已经有 id3 的行而当前的自增 id 值是 2。接下来继续执行的其他事务就会申请到 id2然后再申请到 id3。这时就会出现插入语句报错“主键冲突”。
为了解决这个主键冲突有两种方法
每次申请 id 之前先判断表里面是否已经存在这个 id。如果存在就跳过这个 id。但是这个方法的成本很高。因为本来申请 id 是一个很快的操作现在还要再去主键索引树上判断 id 是否存在。把自增 id 的锁范围扩大必须等到一个事务执行完成并提交下一个事务才能再申请自增 id。这个方法的问题就是锁的粒度太大系统并发能力大大下降。
这两个方法都会导致性能问题。因此InnoDB 放弃了这个设计语句执行失败也不回退自增 id。也正是因为这样所以才只保证了自增 id 是递增的但不保证是连续的。
1.3 自增锁的优化
自增 id 锁并不是一个事务锁而是每次申请完就马上释放以便允许别的事务再申请。
1.3.1 自增锁设计历史
在 MySQL 5.0 版本的时候自增锁的范围是语句级别。 如果一个语句申请了一个表自增锁这个锁会等语句执行结束以后才释放。显然这样设计会影响并发度。 MySQL 5.1.22 版本引入了一个新策略新增参数 innodb_autoinc_lock_mode默认值是 1。
这个参数的值被设置为 0 时表示采用之前 MySQL 5.0 版本的策略即语句执行结束后才释放锁这个参数的值被设置为 1 时 通 insert 语句自增锁在申请之后就马上释放类似 insert … select 这样的批量插入数据的语句自增锁还是要等语句结束后才被释放 这个参数的值被设置为 2 时所有的申请自增主键的动作都是申请后就释放锁。
疑问: 为什么默认设置下insert … select 要使用语句级的锁为什么这个参数的默认值不是 2 答案是这么设计还是为了数据的一致性。
看一下这个场景
图 4 批量插入数据的自增锁 往表 t1 中插入了 4 行数据然后创建了一个相同结构的表 t2然后两个 session 同时执行向表 t2 中插入数据的操作。
如果 session B 是申请了自增值以后马上就释放自增锁那么就可能出现这样的情况
session B 先插入了两个记录(1,1,1)、(2,2,2)然后session A 来申请自增 id 得到 id3插入了3,5,5)之后session B 继续执行插入两条记录 (4,3,3)、 (5,4,4)。
如果我们现在的 binlog_formatstatement由于两个 session 是同时执行插入数据命令的所以 binlog 里面对表 t2 的更新日志只有两种情况要么先记 session A 的要么先记 session B 的。但不论是哪一种这个 binlog 拿去从库执行或者用来恢复临时实例备库和临时实例里面session B 这个语句执行出来生成的结果里面id 都是连续的。这时这个库就发生了数据不一致。
问题的原因因为原库 session B 的 insert 语句生成的 id 不连续。这个不连续的 id用 statement 格式的 binlog 来串行执行是执行不出来的。
要解决这个问题有两种思路
一种思路是让原库的批量插入数据语句固定生成连续的 id 值。所以自增锁直到语句执行结束才释放就是为了达到这个目的。另一种思路是在 binlog 里面把插入数据的操作都如实记录进来到备库执行的时候不再依赖于自增主键去生成。这种情况其实就是 innodb_autoinc_lock_mode 设置为 2同时 binlog_format 设置为 row。
因此在生产上尤其是有 insert … select 这种批量插入数据的场景时从并发插入数据性能的角度考虑建议你这样设置innodb_autoinc_lock_mode2 并且 binlog_formatrow. 这样做既能提升并发性又不会出现数据一致性问题。 这里说的批量插入数据包含的语句类型是 insert … select、replace … select 和 load data 语句。 在普通的 insert 语句里面包含多个 value 值的情况下即使 innodb_autoinc_lock_mode 设置为 1也不会等语句执行完成才释放锁。因为这类语句在申请自增 id 的时候是可以精确计算出需要多少个 id 的然后一次性申请申请完成后锁就可以释放了。
也就是说批量插入数据的语句之所以需要这么设置是因为“不知道要预先申请多少个 id”。
对于批量插入数据的语句MySQL 有一个批量申请自增 id 的策略
语句执行过程中第一次申请自增 id会分配 1 个1 个用完以后这个语句第二次申请自增 id会分配 2 个2 个用完以后还是这个语句第三次申请自增 id会分配 4 个依此类推同一个语句去申请自增 id每次申请到的自增 id 个数都是上一次的两倍。
一起看看下面的这个语句序列
insert into t values(null, 1,1);
insert into t values(null, 2,2);
insert into t values(null, 3,3);
insert into t values(null, 4,4);
create table t2 like t;
insert into t2(c,d) select c,d from t;
insert into t2 values(null, 5,5);insert…select实际上往表 t2 中插入了 4 行数据。但是这四行数据是分三次申请的自增 id第一次申请到了 id1第二次被分配了 id2 和 id3 第三次被分配到 id4 到 id7。 由于这条语句实际只用上了 4 个 id所以 id5 到 id7 就被浪费掉了。之后再执行 insert into t2 values(null, 5,5)实际上插入的数据就是8,5,5)。
这是主键 id 出现自增 id 不连续的第三种原因。 小结
在 MyISAM 引擎里面自增值是被写在数据文件上的。而在 InnoDB 中自增值是被记录在内存的。MySQL 直到 8.0 版本才给 InnoDB 表的自增值加上了持久化的能力确保重启前后一个表的自增值不变。一个语句执行过程中自增值改变的时机分析了为什么 MySQL 在事务回滚的时候不能回收自增 id。MySQL 5.1.22 版本开始引入的参数 innodb_autoinc_lock_mode控制了自增值申请时的锁范围。从并发性能的角度考虑我建议你将其设置为 2同时将 binlog_format 设置为 row。 思考 在最后一个例子中执行 insert into t2(c,d) select c,d from t; 这个语句的时候如果隔离级别是可重复读repeatable readbinlog_formatstatement。这个语句会对表 t 的所有记录和间隙加锁。 为什么需要这么做呢 答案见下节正文 2. Insert语句为何很多锁?
MySQL 对自增主键锁做了优化尽量在申请到自增 id 以后就释放自增锁。
因此insert 语句是一个很轻量的操作。不过这个结论对于“普通的 insert 语句”才有效。也就是说还有些 insert 语句是属于“特殊情况”的在执行过程中需要给其他资源加锁或者无法在申请到自增 id 以后就立马释放自增锁。
2.1 insert … select 语句
表 t 和 t2 的表结构、初始化数据语句如下
CREATE TABLE t (id int(11) NOT NULL AUTO_INCREMENT,c int(11) DEFAULT NULL,d int(11) DEFAULT NULL,PRIMARY KEY (id),UNIQUE KEY c (c)
) ENGINEInnoDB;insert into t values(null, 1,1);
insert into t values(null, 2,2);
insert into t values(null, 3,3);
insert into t values(null, 4,4);create table t2 like t一起来看看为什么在可重复读隔离级别下binlog_formatstatement 时执行
insert into t2(c,d) select c,d from t;这个语句时需要对表 t 的所有行和间隙加锁呢
这个问题我们需要考虑的还是日志和数据的一致性看下这个执行序列
图 1 并发 insert 场景 实际的执行效果 如果 session B 先执行由于这个语句对表 t 主键索引加了 (-∞,1]这个 next-key lock会在语句执行完成后才允许 session A 的 insert 语句执行。
如果没有锁的话就可能出现 session B 的 insert 语句先执行但是后写入 binlog 的情况。于是在 binlog_formatstatement 的情况下binlog 里面就记录了这样的语句序列
insert into t values(-1,-1,-1);
insert into t2(c,d) select c,d from t;这个语句到了备库执行就会把 id-1 这一行也写到表 t2 中出现主备不一致。
2.2 insert 循环写入
执行 insert … select 的时候对目标表也不是锁全表而是只锁住需要访问的资源。
有这么一个需求要往表 t2 中插入一行数据这一行的 c 值是表 t 中 c 值的最大值加 1。 SQL 语句
insert into t2(c,d) (select c1, d from t force index(c) order by c desc limit 1);这个语句的加锁范围就是表 t 索引 c 上的 (3,4]和 (4,supremum]这两个 next-key lock以及主键索引上 id4 这一行。
执行流程也比较简单从表 t 中按照索引 c 倒序扫描第一行拿到结果写入到表 t2 中。因此整条语句的扫描行数是 1。 慢查询日志slow log如下图所示
图 2 慢查询日志 – 将数据插入表 t2 Rows_examined1验证了执行这条语句的扫描行数为 1。
如果是要把这样的一行数据插入到表 t 中的话
insert into t(c,d) (select c1, d from t force index(c) order by c desc limit 1);图 3 慢查询日志 – 将数据插入表 t Rows_examined 的值是 5。
图 4 explain 结果 这个语句用到了临时表。也就是说执行过程中需要把表 t 的内容读出来写入临时表。
看看 InnoDB 扫描了多少行。如图 5 所示是在执行这个语句前后查看 Innodb_rows_read 的结果。 图 5 查看 Innodb_rows_read 变化 这个语句执行前后Innodb_rows_read 的值增加了 4。因为默认临时表是使用 Memory 引擎的所以这 4 行查的都是表 t也就是说对表 t 做了全表扫描。
整个执行过程
创建临时表表里有两个字段 c 和 d。按照索引 c 扫描表 t依次取 c4、3、2、1然后回表读到 c 和 d 的值写入临时表。这时Rows_examined4。由于语义里面有 limit 1所以只取了临时表的第一行再插入到表 t 中。这时Rows_examined 的值加 1变成了 5。
也就是说这个语句会导致在表 t 上做全表扫描并且会给索引 c 上的所有间隙都加上共享的 next-key lock。所以这个语句执行期间其他事务不能在这个表上插入数据。 原因是这类一边遍历数据一边更新数据的情况如果读出来的数据直接写回原表就可能在遍历过程中读到刚刚插入的记录新插入的记录如果参与计算逻辑就跟语义不符。 由于实现上这个语句没有在子查询中就直接使用 limit 1从而导致了这个语句的执行需要遍历整个表 t。它的优化方法也比较简单就是用前面介绍的方法先 insert into 到临时表 temp_t这样就只需要扫描一行然后再从表 temp_t 里面取出这行数据插入表 t1。
当然由于这个语句涉及的数据量很小你可以考虑使用内存临时表来做这个优化。使用内存临时表优化时语句序列的写法如下
create temporary table temp_t(c int,d int) enginememory;
insert into temp_t (select c1, d from t force index(c) order by c desc limit 1);
insert into t select * from temp_t;
drop table temp_t;2.3 insert 唯一键冲突
举一个简单的唯一键冲突的例子。
图 6 唯一键冲突加锁 例子也是在可重复读repeatable read隔离级别下执行的。可以看到session B 要执行的 insert 语句进入了锁等待状态。
就是说session A 执行的 insert 语句发生唯一键冲突的时候并不只是简单地报错返回还在冲突的索引上加了锁。 一个 next-key lock 就是由它右边界的值定义的。这时候session A 持有索引 c 上的 (5,10]共享 next-key lock读锁。
分享一个经典的死锁场景
图 7 唯一键冲突 – 死锁 在 session A 执行 rollback 语句回滚的时候session C 几乎同时发现死锁并返回。
这个死锁产生的逻辑是这样的
在 T1 时刻启动 session A并执行 insert 语句此时在索引 c 的 c5 上加了记录锁。注意这个索引是唯一索引因此退化为记录锁在 T2 时刻session B 要执行相同的 insert 语句发现了唯一键冲突加上读锁同样地session C 也在索引 c 上c5 这一个记录上加了读锁。T3 时刻session A 回滚。这时候session B 和 session C 都试图继续执行插入操作都要加上写锁。两个 session 都要等待对方的行锁所以就出现了死锁。
图 8 状态变化图 – 死锁
2.4 insert into … on duplicate key update
这个例子是主键冲突后直接报错如果是改写成
insert into t values(11,10,10) on duplicate key update d100; 的话就会给索引 c 上 (5,10] 加一个排他的 next-key lock写锁。 insert into … on duplicate key update 这个语义的逻辑是插入一行数据如果碰到唯一键约束就执行后面的更新语句。 注意如果有多个列违反了唯一性约束就会按照索引的顺序修改跟第一个索引冲突的行。 表 t 里面已经有了 (1,1,1) 和 (2,2,2) 这两行再来看看下面这个语句执行的效果 图 9 两个唯一键同时冲突 可以看到主键 id 是先判断的MySQL 认为这个语句跟 id2 这一行冲突所以修改的是 id2 的行。
需要注意的是执行这条语句的 affected rows 返回的是 2很容易造成误解。实际上真正更新的只有一行只是在代码实现上insert 和 update 都认为自己成功了update 计数加了 1 insert 计数也加了 1。 小结
insert … select 是很常见的在两个表之间拷贝数据的方法。你需要注意在可重复读隔离级别下这个语句会给 select 的表里扫描到的记录和间隙加读锁。如果 insert 和 select 的对象是同一个表则有可能会造成循环写入。这种情况下我们需要引入用户临时表来做优化。insert 语句如果出现唯一键冲突会在冲突的唯一值上加共享的 next-key lock(S 锁)。因此碰到由于唯一键约束导致报错后要尽快提交或回滚事务避免加锁时间过长。 思考 两个表之间拷贝数据用的是什么方法有什么注意事项吗在你的应用场景里这个方法相较于其他方法的优势是什么呢 如果可以控制对源表的扫描行数和加锁范围很小的话我们简单地使用 insert … select 语句即可实现。 为了避免对源表加读锁更稳妥的方案是先将数据写到外部文本文件然后再写回目标表。常用方案见下节 3.如何快速复制一张表 3. 如何快速复制一张表
先创建一个表 db1.t并插入 1000 行数据同时创建一个相同结构的表 db2.t。
create database db1;
use db1;create table t(id int primary key, a int, b int, index(a))engineinnodb;
delimiter ;;create procedure idata()begindeclare i int;set i1;while(i1000)doinsert into t values(i,i,i);set ii1;end while;end;;
delimiter ;
call idata();create database db2;
create table db2.t like db1.t假设要把 db1.t 里面 a900 的数据行导出来插入到 db2.t 中。
3.1 mysqldump 方法
使用 mysqldump 命令将数据导出成一组 INSERT 语句。可以使用下面的命令
mysqldump -h$host -P$port -u$user --add-locks0 --no-create-info --single-transaction --set-gtid-purgedOFF db1 t --wherea900 --result-file/client_tmp/t.sql把结果输出到临时文件。 命令中主要参数含义如下
–single-transaction 的作用是在导出数据的时候不需要对表 db1.t 加表锁而是使用 START TRANSACTION WITH CONSISTENT SNAPSHOT 的方法–add-locks 设置为 0表示在输出的文件结果里不增加 LOCK TABLES t WRITE; –no-create-info 的意思是不需要导出表结构–set-gtid-purgedoff 表示的是不输出跟 GTID 相关的信息–result-file 指定了输出文件的路径其中 client 表示生成的文件是在客户端机器上的。
通过这条 mysqldump 命令生成的 t.sql 文件中就包含了如图 1 所示的 INSERT 语句。
图 1 mysqldump 输出文件的部分结果 可以看到一条 INSERT 语句里面会包含多个 value 对这是为了后续用这个文件来写入数据的时候执行速度可以更快。
如果希望生成的文件中一条 INSERT 语句只插入一行数据的话可以在执行 mysqldump 命令时加上参数–skip-extended-insert。
可以通过下面这条命令将这些 INSERT 语句放到 db2 库里去执行。
mysql -h127.0.0.1 -P13000 -uroot db2 -e source /client_tmp/t.sql需要说明的是source 并不是一条 SQL 语句而是一个客户端命令。 mysql 客户端执行这个命令的流程是这样的
打开文件默认以分号为结尾读取一条条的 SQL 语句将 SQL 语句发送到服务端执行。
服务端执行的并不是这个“source t.sql语句而是 INSERT 语句。所以不论是在慢查询日志slow log还是在 binlog记录的都是这些要被真正执行的 INSERT 语句。
3.2 导出 CSV 文件
直接将结果导出成.csv 文件。MySQL 提供了下面的语法用来将查询结果导出到服务端本地目录
select * from db1.t where a900 into outfile /server_tmp/t.csv;使用这条语句时需要注意如下几点。
这条语句会将结果保存在服务端。如果你执行命令的客户端和 MySQL 服务端不在同一个机器上客户端机器的临时目录下是不会生成 t.csv 文件的。into outfile 指定了文件的生成位置/server_tmp/这个位置必须受参数 secure_file_priv 的限制。参数 secure_file_priv 的可选值和作用分别是 如果设置为 empty表示不限制文件生成的位置这是不安全的设置如果设置为一个表示路径的字符串就要求生成的文件只能放在这个指定的目录或者它的子目录如果设置为 NULL就表示禁止在这个 MySQL 实例上执行 select … into outfile 操作。 这条命令不会帮你覆盖文件因此你需要确保 /server_tmp/t.csv 这个文件不存在否则执行语句时就会因为有同名文件的存在而报错。这条命令生成的文本文件中原则上一个数据行对应文本文件的一行。但是如果字段中包含换行符在生成的文本中也会有换行符。不过类似换行符、制表符这类符号前面都会跟上“\”这个转义符这样就可以跟字段之间、数据行之间的分隔符区分开。
得到.csv 导出文件后就可以用下面的 load data 命令将数据导入到目标表 db2.t 中。
load data infile /server_tmp/t.csv into table db2.t;这条语句的执行流程如下所示。
打开文件 /server_tmp/t.csv以制表符 (\t) 作为字段间的分隔符以换行符\n作为记录之间的分隔符进行数据读取启动事务。判断每一行的字段数与表 db2.t 是否相同 若不相同则直接报错事务回滚若相同则构造成一行调用 InnoDB 引擎接口写入到表中。 重复步骤 3直到 /server_tmp/t.csv 整个文件读入完成提交事务。
如果 binlog_formatstatement这个 load 语句记录到 binlog 里以后怎么在备库重放呢 由于 /server_tmp/t.csv 文件只保存在主库所在的主机上如果只是把这条语句原文写到 binlog 中在备库执行的时候备库的本地机器上没有这个文件就会导致主备同步停止。 所以这条语句执行的完整流程其实是下面这样的。
主库执行完成后将 /server_tmp/t.csv 文件的内容直接写到 binlog 文件中。往 binlog 文件中写入语句 load data local infile ‘/tmp/SQL_LOAD_MB-1-0’ INTO TABLE db2.t。把这个 binlog 日志传到备库。备库的 apply 线程在执行这个事务日志时 a. 先将 binlog 中 t.csv 文件的内容读出来写入到本地临时目录 /tmp/SQL_LOAD_MB-1-0 中b. 再执行 load data 语句往备库的 db2.t 表中插入跟主库相同的数据。
执行流程如图 2 所示 图 2 load data 的同步流程 这里备库执行的 load data 语句里面多了一个“local”。它的意思是“将执行这条命令的客户端所在机器的本地文件 /tmp/SQL_LOAD_MB-1-0 的内容加载到目标表 db2.t 中”。 load data 命令有两种用法
不加“local”是读取服务端的文件这个文件必须在 secure_file_priv 指定的目录或子目录下加上“local”读取的是客户端的文件只要 mysql 客户端有访问这个文件的权限即可。这时候MySQL 客户端会先把本地文件传给服务端然后执行上述的 load data 流程。
注意 select …into outfile 方法不会生成表结构文件, 所以导数据时还需要单独的命令得到表结构定义。mysqldump 提供了一个–tab 参数可以同时导出表结构定义文件和 csv 数据文件。这条命令的使用方法如下
mysqldump -h$host -P$port -u$user ---single-transaction --set-gtid-purgedOFF db1 t --wherea900 --tab$secure_file_priv这条命令会在 $secure_file_priv 定义的目录下创建一个 t.sql 文件保存建表语句同时创建一个 t.txt 文件保存 CSV 数据。 3.3 物理拷贝方法
前面提到的 mysqldump 方法和导出 CSV 文件的方法都是逻辑导数据的方法也就是将数据从表 db1.t 中读出来生成文本然后再写入目标表 db2.t 中。
直接把 db1.t 表的.frm 文件和.ibd 文件拷贝到 db2 目录下是否可行呢 答案是不行的。因为一个 InnoDB 表除了包含这两个物理文件外还需要在数据字典中注册。直接拷贝这两个文件的话因为数据字典中没有 db2.t 这个表系统是不会识别和接受它们的。 在 MySQL 5.6 版本引入了可传输表空间(transportable tablespace) 的方法可以通过导出 导入表空间的方式实现物理拷贝表的功能。
假设现在的目标是在 db1 库下复制一个跟表 t 相同的表 r具体的执行步骤如下
执行 create table r like t创建一个相同表结构的空表执行 alter table r discard tablespace这时候 r.ibd 文件会被删除执行 flush table t for export这时候 db1 目录下会生成一个 t.cfg 文件在 db1 目录下执行 cp t.cfg r.cfg; cp t.ibd r.ibd这两个命令这里需要注意的是拷贝得到的两个文件MySQL 进程要有读写权限执行 unlock tables这时候 t.cfg 文件会被删除执行 alter table r import tablespace将这个 r.ibd 文件作为表 r 的新的表空间由于这个文件的数据内容和 t.ibd 是相同的所以表 r 中就有了和表 t 相同的数据。
至此拷贝表数据的操作就完成了。这个流程的执行过程图如下 图 3 物理拷贝表
关于拷贝表的这个流程有以下几个注意点
在第 3 步执行完 flsuh table 命令之后db1.t 整个表处于只读状态直到执行 unlock tables 命令后才释放读锁在执行 import tablespace 的时候为了让文件里的表空间 id 和数据字典中的一致会修改 r.ibd 的表空间 id。而这个表空间 id 存在于每一个数据页中。因此如果是一个很大的文件比如 TB 级别每个数据页都需要修改所以你会看到这个 import 语句的执行是需要一些时间的。当然如果是相比于逻辑导入的方法import 语句的耗时是非常短的。 小结 对比一下这三种方法的优缺点。
物理拷贝的方式速度最快尤其对于大表拷贝来说是最快的方法。如果出现误删表的情况用备份恢复出误删之前的临时库然后再把临时库中的表拷贝到生产库上是恢复数据最快的方法。但是这种方法的使用也有一定的局限性 必须是全表拷贝不能只拷贝部分数据需要到服务器上拷贝数据在用户无法登录数据库主机的场景下无法使用由于是通过拷贝物理文件实现的源表和目标表都是使用 InnoDB 引擎时才能使用。 用 mysqldump 生成包含 INSERT 语句文件的方法可以在 where 参数增加过滤条件来实现只导出部分数据。这个方式的不足之一是不能使用 join 这种比较复杂的 where 条件写法。用 select … into outfile 的方法是最灵活的支持所有的 SQL 写法。但这个方法的缺点之一就是每次只能导出一张表的数据而且表结构也需要另外的语句单独备份。
后两种方式都是逻辑备份方式是可以跨引擎使用的。 思考 binlog_formatstatement 的时候binlog 记录的 load data 命令是带 local 的。既然这条命令是发送到备库去执行的那么备库执行的时候也是本地执行为什么需要这个 local 呢如果写到 binlog 中的命令不带 local又会出现什么问题呢 为了确保备库应用 binlog 正常。因为备库可能配置了 secure_file_privnull所以如果不用 local 的话可能会导入失败造成主备同步延迟。 另一种应用场景是使用 mysqlbinlog 工具解析 binlog 文件并应用到目标库的情况。你可以使用下面这条命令 mysqlbinlog $binlog_file | mysql -h$host -P$port -u$user -p$pwd 把日志直接解析出来发给目标库执行。增加 local就能让这个方法支持非本地的 $host。 来自林晓斌《MySql实战45讲》