一、批量进行Mysql数据处理的一项工作记录
在使用SQL执行一起数据批量处理的时候遇到执行数速度非常慢。表temp_users是一个包含百万级的用户ID表,表user_list是一个亿级的表,因为跨库,这里使用的是federated引擎创建的结构表。根据要实现的目标,理论上要执行的SQL应该如下:
update temp_users tu, user_list ul set tu.passwordss=ul.passwd, tu.mobiless=ul.mobile where tu.passwordss is null and tu.uidss=ul.uid;
但因为数据量太大,在直接执行这个SQL的时候发现半天没有响应,基本属于走不通的理论可行的逻辑(当然或许也和我本机性能较差)。当前情况下,只能着手进行优化处理。
【1.分步先批量取出uid批量修改数据】
查询百万级表,一次取出几百条的用户ID,使用in查询从亿级表中取出要拿到的字段数据,然后组装成几百个SQL一次性扔给数据库执行修改,首先是这个方法是可行的,但速度大概在1秒10条左右,还是比较慢,全量修改完毕需要约3天时间。
【2.直接对uid分批执行SQL】
在1方法太慢之后,我想了想其它的办法,其中之一就是将uid在SQL中进行分批以减少一次影响的数量。总共百万级,又涉及到跨库查询,在尝试按末尾一位、二位、三位后,发现三位执行时间还可以,且不易超时。如下:
update tv_users tu,user_list ul set tu.passwordss=ul.passwd, tu.mobiless=ul.mobile where SUBSTR(uidss,-3) = '100' and tu.passwordss is null and tu.uidss=ul.uid;
#受影响的行: 1521
#时间: 16.357s
此方法确实可行。这样来看,一秒终大约能执行100条,是上面速度的10倍,应该在几个小时就能执行完毕。于是使用程序批量生成1000条SQL语句放在navicat查询下执行,
【3.使用存储函数替代批量执行】
在2的方法凑效之后,可以考虑使用存储函数,就不需要再用程序批量生成一大堆的SQL,粘贴执行,使用存储函数更显得对MYSQL的熟练运用吧。存储函数如下:
delimiter //
drop procedure if exists doupdate;
create procedure doupdate()
begin
declare i int;
#小于100的加前补0的逻辑
set i = 100;
repeat
update tv_users tu,user_list ul set tu.passwordss=ul.passwd, tu.mobiless=ul.mobile where tu.passwordss is null and SUBSTR(uidss,-3) = i and tu.uidss=ul.uid;
set i = i + 1;
until i > 999
end repeat;
end //
#调用函数
call doupdate()
【4.发现问题速度变慢】
上面2,3都是可以使用的方法,且我在开始执行的时候也确实可行,速度也正常,但是在执行到后期发现速度越来越慢:
[SQL] update tv_users tu,user_list ul set tu.passwordss=ul.passwd, tu.mobiless=ul.mobile where tu.passwordss is null and SUBSTR(uidss,-3) = '302' and tu.uidss=ul.uid; #受影响的行: 1514 #时间: 49.051s [SQL]update tv_users tu,user_list ul set tu.passwordss=ul.passwd, tu.mobiless=ul.mobile where tu.passwordss is null and SUBSTR(uidss,-3) = '989' and tu.uidss=ul.uid; #受影响的行: 1524 #时间: 162.145s [SQL]update tv_users tu,user_list ul set tu.passwordss=ul.passwd, tu.mobiless=ul.mobile where tu.passwordss is null and SUBSTR(uidss,-3) = '990' and tu.uidss=ul.uid; #受影响的行: 1510 #时间: 249.241s [SQL]update tv_users tu,user_list ul set tu.passwordss=ul.passwd, tu.mobiless=ul.mobile where tu.passwordss is null and SUBSTR(uidss,-3) = '991' and tu.uidss=ul.uid; #受影响的行: 1569 #时间: 331.324s
在该表中,目前只有uidss有索引,但使用substr进行处理查询就没法用上uidss的主索引了(此处我进行了测试,其中还发现查询select count(*)的时候能用上这个主索引,但是查询具体字段的时候用不上)。我到是知道mysql里BTREE索引中到是有一个前缀索引,但是我这里的查询是需要对后缀进行索引才有用。当然我也可以脑洞大开想一下再加一列,这列数据刚好和uidss字段内容是反着等,从而加上索引,不过我暂时还不想去试验这个方法。
为什么会越来越慢呢?从索引来看,此查询用不上什么索引,或者说我未建相关索引;从数据变化来看,执行到越后面,passwordss字段为null的行越来越少。其它的变化我看不出来,是不是可以推测导致查询变慢的原因是因为null字段的问题,即一开始都是大量的null的时候还好查,后面null行越为越少时查询越来越慢。NULL 并不是空值,而是要占用空间,所以mysql在进行比较的时候,NULL 会参与字段比较,所以对效率有一部分影响。在B树索引中不会存储NULL值,所以如果索引的字段可以为NULL,索引的效率会下降很多。当然我这里尚未对passwordss字段加索引,但鉴于此,于是我考虑将这个passwordss字段的null值全部替换成空然后对此字段加索引进行尝试。操作后进行查询效率如下:
[SQL]update tv_users tu force index (passwordss),user_list ul set tu.passwordss=ul.passwd, tu.mobiless=ul.mobile where tu.passwordss ='' and SUBSTR(uidss,-3) = '001' and tu.uidss=ul.uid ; #受影响的行: 0 #时间: 36.331s [SQL]update tv_users tu force index (passwordss),user_list ul set tu.passwordss=ul.passwd, tu.mobiless=ul.mobile where tu.passwordss ='' and SUBSTR(uidss,-3) = '040' and tu.uidss=ul.uid ; #受影响的行: 1589 #时间: 134.757s
【5.其它可考虑的方法】
上面虽然有所改善,但也只能说是有一点点吧,甚至也可以说没有什么改善。然后又有了一些想法,
#创建视图,利用视图修改
create view nopassdata as SELECT uid,passwd,mobile from user_list where uid in (select uidss from tv_users where passwordss = SUBSTR(uidss,-3) = '999');
update tv_users left join nopassdata on uid=uidss set passwordss=passwd, mobiless=mobile where passwordss='' and SUBSTR(uidss,-3) = '104';
#会有报错:[Err] 1443 - The definition of table 'nopassdata' prevents operation UPDATE on table 'tv_users'.
#创建临时表,连接临时表进行操作
create temporary table nopasstable SELECT uid,passwd,mobile from user_list where uid in (select uidss from tv_users where passwordss = '');
#整合成一个SQL:
update tv_users left join (
SELECT uid,passwd,mobile from user_list where uid in (select uidss from tv_users where passwordss = '' limit 10);
) as nopass on uid = uidss set passwordss=passwd, mobiless=mobile where passwordss='';
#提示报错[Err] 1235 - This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery' MySQL子查询不支持 limit
#于是再加一层嵌套
update tv_users left join (
SELECT uid,passwd,mobile from user_list where uid in (select uidss from (select uidss from tv_users where passwordss = '' limit 10) as tea )
) as nopass on uid = uidss set passwordss=passwd, mobiless=mobile where passwordss='';
写到这里,我里通过方法2已经完成了90%的数据更新了,不过所使用的方法我相信不是最优的方法,做个记录吧。
二、保存一个nginx变量大全
$arg_PARAMETER 如果在请求中设置了查询字符串,那么这个变量包含在查询字符串是GET请求PARAMETER中的值。 $args 该变量的值是GET请求在请求行中的参数。 $binary_remote_addr 二进制格式的客户端地址 $body_bytes_sent 响应体的大小,即使发生了中断或者是放弃,也是一样的准确。 $content_length 该变量的值等于请求头中的Content-length字段的值 $cookie_COOKIE 该变量的值是cookie COOKIE的值 $document_root 该变量的值为当前请求的location(http,server,location,location中的if)中root指令中指定的值。 $document_uri 同$uri $host 该变量的值等于请求头中Host的值。如果Host无效时,那么就是处理该请求的server的名称。 在下列情况中,$host变量的取值不同于$http_host变量。 当请求头中的Host字段未指定(使用默认值)或者为空值,那么$host等于server_name指令指定的值。 当Host字段包含端口是,$host并不包含端口号。另外,从0.8.17之后的nginx中,$host的值总是小写。 $hostname 有gethostname返回值设置机器名。 $http_HEADER 该变量的值为HTTP 请求头HEADER,具体使用时会转换为小写,并且将“——”(破折号)转换为"_"(下划线)。 $is_args 如果设置了$args,那么值为“?”,否则为“” $limit_rate 该变量允许限制连接速率。 $nginx_version 当前运行的nginx的版本号 $query_string 同$args $remote_addr 客户端的IP地址 $remote_user 该变量等于用户的名字,基本身份验证模块使用。 $remote_port 客户端连接端口 $request_filename 该变量等于当前请求文件的路径,有指令root或者alias和URI构成。 $request_body 该变量包含了请求体的主要信息。该变量与proxy_pass或者fastcgi_pass相关。 $request_body_file 客户端请求体的临时文件。 $request_completion 如果请求成功完成,那么显示“OK”。如果请求没有完成或者请求不是该请求系列的最后一部分,那么它的值为空。 $request_method 该变量的值通常是GET或者POST。 $request_uri 该变量的值等于原始的URI请求,就是说从客户端收到的参数包括了原始请求的URI,该值是不可以被修改的,不包含主机名,例如“/foo/bar.php?arg=baz”。 $scheme 该变量表示HTTP scheme(例如HTTP,HTTPS),根据实际使用情况来决定, 例如:rewrite ^ $scheme://example.com$uri redirect; $server_addr 该变量的值等于服务器的地址。通常来说,在完成一次系统调用之后就会获取变量的值,为了避开系统钓鱼,那么必须在listen指令中使用bind参数。 $server_name 该变量为server的名字。 $server_port 该变量等于接收请求的端口。 $server_protocol 该变量的值为请求协议的值,通常是HTTP/1.0或者HTTP/1.1 $uri 该变量的值等于当前请求中的URI(没有参数,不包括$args)的值。它的值不同于request_uri,由浏览器客户端发送的request_uri的值。 例如,可能会被内部重定向或者使用index。 另外需要注意:$uri不包含主机名,例如 "/foo/bar.html" 当前URL= $scheme://$server_name/$url