大家好,我是 Snow Hide,作为《MySQL 实战》这个专栏的学员之一,这是我打卡的第 35 天,也是我第 98 次进行这种操作。
今天我温习了该专栏里叫《怎么最快地复制一张表?》、《如何正确地显示随机消息?》的文章。
关键词总结:mysqldump 方法(参数含义、一条 INSERT 语句只插入一行数据、source 客户端命令执行流程)、导出 CSV 文件(使用语句时的注意点、load data 命令、命令语句执行流程、命令语句的完整执行流程、load data 命令两种用法、tab 参数)、物理拷贝方法(可传输表空间、拷贝表的几个注意点)、内存临时表(语句的执行流程、通过慢查询日志验证正确性、每个引擎用来唯一标识数据行的信息、order by rand()/内存临时表/rowid)、磁盘临时表(内存临时表大小、磁盘临时表引擎、优先队列算法)、随机排序方法(随机选择一个 word 值、获取严格随机的结果、随机取三个 word 值)。
所学总结:
《怎么最快地复制一张表?》
mysqldump 方法
mysqldump -h$host -P$port -u$user --add-locks=0 --no-create-info --single-transaction --set-gtid-purged=OFF db1 t --where="a>900" --result-file=/client_tmp/t.sql
参数含义
--add-locks
:设置为 0,表示在输出的文件结果里,不增加LOCK TABLES t WRITE;
语句;--no-create-info
:不需要导出表结构;--single-transaction
:在导出数据的时候不需要对表 db1.t 加表锁,而是使用START TRANSACTION WITH CONSISTENT SNAPSHOT
的方式;--set-gtid-purged=off
:不输出跟 GTID 相关的信息;--result-file
:输出文件的路径,其中client
表示生成的文件是在客户端机器上。
一条 INSERT 语句只插入一行数据
加上 --skip-extended-insert
参数来确保一条 INSERT 语句指插入一行数据。
source 客户端命令执行流程
mysql -h127.0.0.1 -P13000 -uroot db2 -e "source /client_tmp/t.sql"
其执行流程如下:
- 默认以分号为结尾逐条读取 SQL 语句;
- 将 SQL 语句发送至服务端执行。
导出 CSV 文件
select * from db1.t where a>900 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
文件不存在,否则执行时会报错; - 该命令生成的文本文件,原则上一条数据对应一行。但如果字段中包含换行符,那生成的文本中也会有换行符。不过,类似换行符、制表符等等前面都会跟上
\
转义符。这样,它就跟字段、数据的分隔符区分开了。
load data 命令
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
文件读入完成,然后提交事务。
命令语句的完整执行流程
- 主库执行完成后,将
/server_tmp/t.csv
文件内容写至binlog
文件中; - 往
binlog
文件写入语句load data local infile '/tmp/SQL_LOAD_MB-1-0' INTO TABLE db2.t
; - 将
binlog
日志传至备库; - 备库的 apply 线程在执行事务日志时:
- 读取
binlog
中t.csv
文件的内容并将其写入本地临时目录/tmp/SQL_LOAD_MB-1-0
中; - 再执行
load data
语句,往备库的db2.t
表中插入与主库相同的数据。
- 读取
load data 命令两种用法
- 不加
local
,则读取服务端文件,该文件必须位于secure_file_priv
指定的目录或子目录下; - 加上
local
,则读取客户端文件,只要 mysql 客户端有访问权限即可。此时,MySQL 客户端现将本地文件传给服务端,后执行上述的load data
流程。
tab 参数
由于 select ... into outfile
不会同时生成表结构文件。此时,我们需要用 mysqldump 及其提供的 --tab
参数来同时导出表结构定义文件和 csv 数据文件:
mysqldump -h$host -P$port -u$user ---single-transaction --set-gtid-purged=OFF db1 t --where="a>900" --tab=$secure_file_priv
物理拷贝方法
可传输表空间
通过导出加导入表空间的方式来实现物理拷贝表的功能。
将表 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
相同的数据。
拷贝表的几个注意点
- 在第三步执行完
flush table
命令后,db1.t
表处于只读状态,直到执行unlock tables
命令后才释放读锁; - 在执行
import tablespace
时,为了让文件的表空间编号和数据字段的一致,会修改r.ibd
表空间编号。而表空间编号存在于每个数据页中。因此,如果是 TB 级文件,则每个数据页都需要被修改,所以import
语句的执行是需要时间的。但相比于逻辑导入的方式,import
语句的耗时则非常短。
《如何正确地显示随机消息?》
内存临时表
mysql> select word from words order by rand() limit 3;
语句的执行流程
- 创建一个使用 memory 引擎的临时表,有两个字段,第一个是 double 类型,为方便描述,记为字段 R,第二个是 varchar(64) 类型,记为字段 W。该表没有索引;
- 从 words 表中,按住建顺序去除所有的 word 值。对于每一个 word 值,调用 rand() 函数生成一个大于 0 小于 1 的随机小数,把随机小数和 word 分别存入临时表的 R 和 W 字段中。至此,扫描行数是 10000;
- 现在临时表有 10000 行数据了,接下来我们要在这个没有索引的内存临时表上,按字段 R 排序;
- 初始化 sort_buffer。其中有两个字段,一个是 double 类型,另一个是整型;
- 从内存临时表中逐行地取出 R 值和位置信息,分别存入 sort_buffer 中的两个字段中。这个过程要对内存临时表做全表扫描,此时扫描行数增加 10000,变成了 20000;
- 在 sort_buffer 中根据 R 的值进行排序。注意,这个过程没有涉及到表操作,所以不会增加扫描行数;
- 排序完成后,取出前三个结果的位置信息,依次到内存临时表中取出 word 值,返回给客户端。这个过程中,访问了表的三行数据,总扫描行数变成了 20003。
通过慢查询日志验证正确性
# Query_time: 0.900376 Lock_time: 0.000347 Rows_sent: 3 Rows_examined: 20003
SET timestamp=1541402277;
select word from words order by rand() limit 3;
每个引擎用来唯一标识数据行的信息
- 对于有主键的 InnoDB 表来说,这个 rowid 就是主键 ID;
- 对于没有主键的 InnoDB 表来说,这个 rowid 就是由系统生成的;
- MEMORY 引擎不是索引组织表。该示例中,你可以认为它是个数组。因此,这个 rowid 是数组的下标。
order by rand()/内存临时表/rowid)
order by rand()
使用了内存临时表,内存临时表排序的时候使用了 rowid 排序方法。
磁盘临时表
内存临时表大小
tmp_table_size
这个配置限制了内存临时表的大小,默认值是 16MB。如果其大小超过了 tmp_table_size
,那么其就会转成磁盘临时表。
磁盘临时表引擎
默认是 InnoDB,由参数 internal_tmp_disk_storage_engine
来控制。
优先队列算法
- 对于 10000 个准备排序的(R,rowid),先取前三行,构造成一个堆;
- 取下一行(R’,rowid’),跟当前堆里面最大的 R 比较,如果 R’ 小于 R,则把(R,rowid)从堆中去掉,换成(R’,rowid’);
- 重复第 2 步,直到第 10000 个(R’,rowid’)完成比较。
随机排序方法
随机选择一个 word 值
- 取得表的主键 id 的最大值 M 和最小值 N;
- 用随机函数生成一个最大值到最小值之间的数 X = (M-N)*rand() + N;
- 取不小于 X 的第一个 ID 的行。
获取严格随机的结果
- 取得整个表的行数,并记为 C;
- 取得 Y = floor(C * rand())。floor 函数的作用,是取整数部分;
- 再用 limit Y,1 取得一行。
随机取三个 word 值
- 取得整个表的行数,记为 C;
- 根据相同的随机方法得到 Y1、Y2、Y3;
- 再执行三个 limit Y,1 语句得到三行数据。
末了
重新总结了一下文中提到的内容:物理拷贝的方式最快(全表、服务器拷贝、都使用 InnoDB 引擎)、mysqldump 生成包含 INSERT 语句文件的方式不适用于关联表等复杂的条件语句(可导出部分数据)、select … into outfile 的方式最灵活(支持所有 SQL、只能导出一张表、表结构需要另外的语句单独备份)、内存临时表、磁盘临时表、随机排序方法。