查看MySQL LOAD DATA进度

概述

开发过程中经常会使用MySQL的LOAD DATA功能,用于导入文件到MySQL的指定数据库表中。

若已经将文件切分为N个小文件再进行LOAD操作(例如使用Linux下的 split 工具),那么进度还是很容易把控的,可以通过直接查找当前正在进行导入的分片,进而判断当前的分片。

可是,如果某些情况下直接对一个大型的文件进行进行LOAD操作,整个过程并不能直观的获取当前的进度的,需要通过一些相对曲折的过程才能获取当前LOAD的进度。

分析

/proc虚拟文件系统

Linux中的/proc虚拟文件系统是一个非常有趣的部分,这一个目录并不是包含了一些常规意义上的文件,而是表征了进程的部分运行时信息。部分Linux工具更是可以直接用读取目录中的部分信息来替代[1]

/proc下可以看到大量的名为数字的目录,这些数字正是进程的pid。而cd到其中任何一个目录下,可以看到类似的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/proc/18458$ ll
总用量 0
dr-xr-xr-x 2 root root 0 329 10:48 attr
-rw-r--r-- 1 root root 0 43 21:34 autogroup
-r-------- 1 root root 0 43 21:34 auxv
-r--r--r-- 1 root root 0 43 21:34 cgroup
--w------- 1 root root 0 43 21:34 clear_refs
-r--r--r-- 1 root root 0 127 14:44 cmdline
-rw-r--r-- 1 root root 0 43 21:34 comm
-rw-r--r-- 1 root root 0 43 21:34 coredump_filter
-r--r--r-- 1 root root 0 43 21:34 cpuset
lrwxrwxrwx 1 root root 0 43 21:34 cwd -> /
-r-------- 1 root root 0 318 10:31 environ
lrwxrwxrwx 1 root root 0 223 13:24 exe -> /usr/local/nginx/sbin/nginx
dr-x------ 2 root root 0 329 10:48 fd
dr-x------ 2 root root 0 43 21:34 fdinfo
-r-------- 1 root root 0 43 21:34 io
-rw------- 1 root root 0 43 21:34 limits
-rw-r--r-- 1 root root 0 43 21:34 loginuid
-r--r--r-- 1 root root 0 43 21:34 maps
-rw------- 1 root root 0 43 21:34 mem
-r--r--r-- 1 root root 0 43 21:34 mountinfo
-r--r--r-- 1 root root 0 43 21:34 mounts
-r-------- 1 root root 0 43 21:34 mountstats
dr-xr-xr-x 5 root root 0 43 21:34 net
dr-x--x--x 2 root root 0 43 21:34 ns
-r--r--r-- 1 root root 0 43 21:34 numa_maps
-rw-r--r-- 1 root root 0 43 21:34 oom_adj
-r--r--r-- 1 root root 0 43 21:34 oom_score
-rw-r--r-- 1 root root 0 43 21:34 oom_score_adj
-r--r--r-- 1 root root 0 43 21:34 pagemap
-r--r--r-- 1 root root 0 43 21:34 personality
lrwxrwxrwx 1 root root 0 43 21:34 root -> /
-rw-r--r-- 1 root root 0 43 21:34 sched
-r--r--r-- 1 root root 0 43 21:34 schedstat
-r--r--r-- 1 root root 0 43 21:34 sessionid
-r--r--r-- 1 root root 0 43 21:34 smaps
-r--r--r-- 1 root root 0 43 21:34 stack
-r--r--r-- 1 root root 0 127 14:44 stat
-r--r--r-- 1 root root 0 127 15:55 statm
-r--r--r-- 1 root root 0 127 14:44 status
-r--r--r-- 1 root root 0 43 21:34 syscall
dr-xr-xr-x 3 root root 0 43 21:34 task
-r--r--r-- 1 root root 0 43 21:34 wchan

各个目录的说明可以参考此处

/proc下的fdinfo

这里我们关注的地方是如何通过这些丰富的信息获取导入数据库的进度。

考虑到这一导入操作,实际上是利用了MySQL进行读取文件的操作,那么,只需要知道MySQL当前读取的文件位置,就可以了解到当前的进度了。

/proc/[PID]/fdinfo/这一目录正是解决这一问题的关键,这一目录包含了当前进程已打开的文件的信息,其中文件名正是文件描述符的名称,而相关信息则存储在这个只读文件之中。包含的信息形如:

1
2
3
/proc/18458/fd$ cat ../fdinfo/7
pos: 2293603
flags: 0102001

pos

pos即文件读取游标的偏移值,也就是我们关注的已读取到的位置。

flags

flags则是一个八进制数,表征当前文件的打开状态。

以上述打开的文件为例,这是一个Nginx打开的日志文件,通过lsof +fg -p [PID]可以看到这一文件打开使用的flag:

1
2
3
COMMAND  PID USER   FD   TYPE  FILE-FLAG             DEVICE   SIZE/OFF     NODE NAME
...
nginx 18458 root 7w REG W,AP,LG 8,7 2293603 4194313 /home/www/logs/access_log

可以看到使用了W、AP、LG三个flag,而W对应的是O_WRONLYAP对应的是O_APPENDLG对应的的O_LARGEFILE,这三个常量的值一般可以在/usr/include/bits/fcntl.h中找到:

1
2
3
bits/fcntl.h:36:#define O_WRONLY	     01
bits/fcntl.h:42:#define O_APPEND 02000
bits/fcntl.h:71:# define O_LARGEFILE 0100000

所以flags的值为何是0102001也可以解释了。

获取进度

根据上述分析,首先我们直接找到正在进行LOAD操作的MySQL进程的PID,获取之后查看当前打开的文件(假设文件名为foo)在进程中的fd:

1
ls -l /proc/[PID]/fd | grep foo | grep -v grep | awk '{print $9}'

获取fd之后,直接读取对应的fdinfo:

1
cat /proc/[PID]/fdinfo/[fd]

根据pos可以知道当前已读取了的文件位置,进而获知LOAD进度。

工具化

参见GitHub;

用法:

1
./mysql_load_progress.sh /full/path/to/loading/file

输入文件名需要是完整路径。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/sh

LOAD_FILE_NAME=$1

if [ ! -f $LOAD_FILE_NAME ];then
echo "No such file"
exit
fi

MYSQL_PIDS=`ps -ef | grep mysql | awk '{print $2}'`
fsize=`ls -l $LOAD_FILE_NAME | awk '{print $5}'`

for pid in $MYSQL_PIDS
do
fd=`lsof -p $pid | grep $LOAD_FILE_NAME | grep -vE "grep|sh -c" | awk '{print $4}' | grep -oP '\d+(?=r)'`

if [ -f /proc/$pid/fdinfo/$fd ];then
read_pos=`cat /proc/$pid/fdinfo/$fd | grep pos | awk "{print \\$2/$fsize*100\"%\"}"`
echo $LOAD_FILE_NAME" "$read_pos
fi
done

以上。