02.23 Day 35 - 重温 Day 30-31

大家好,我是 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 线程在执行事务日志时:
    • 读取 binlogt.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、只能导出一张表、表结构需要另外的语句单独备份)、内存临时表、磁盘临时表、随机排序方法。

发布了182 篇原创文章 · 获赞 12 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/stevenchen1989/article/details/104454278