将MySQL去重操作优化到极致之三弹连发(二):多线程并行执行

上一篇已经将单条查重语句调整到最优,但该语句是以单线程方式执行。能否利用多处理器,让去重操作多线程并行执行,从而进一步提高速度呢?比如我的实验环境是4处理器,如果使用4个线程同时执行查重sql,理论上应该接近4倍的性能提升。

一、数据分片
        我们生成测试数据时,created_time采用每条记录加一秒的方式,也就是最大和在最小的时间差为50万秒,而且数据均匀分布。因此先把数据平均分成4份。

1. 查询出4份数据的created_time边界值

select date_add(‘2017-01-01‘,interval 125000 second) dt1,
       date_add(‘2017-01-01‘,interval 2*125000 second) dt2,
       date_add(‘2017-01-01‘,interval 3*125000 second) dt3,
       max(created_time) dt4
  from t_source;

查询结果如图一所示。

图一

2. 查看每份数据的记录数,确认数据平均分布

select case when created_time >= ‘2017-01-01‘
             and created_time < ‘2017-01-02 10:43:20‘
            then ‘2017-01-01‘
            when created_time >= ‘2017-01-02 10:43:20‘
             and created_time < ‘2017-01-03 21:26:40‘
            then ‘2017-01-02 10:43:20‘
            when created_time >= ‘2017-01-03 21:26:40‘
             and created_time < ‘2017-01-05 08:10:00‘
            then ‘2017-01-03 21:26:40‘
            else ‘2017-01-05 08:10:00‘
        end min_dt,
       case when created_time >= ‘2017-01-01‘
             and created_time < ‘2017-01-02 10:43:20‘
            then ‘2017-01-02 10:43:20‘
            when created_time >= ‘2017-01-02 10:43:20‘
             and created_time < ‘2017-01-03 21:26:40‘
            then ‘2017-01-03 21:26:40‘
            when created_time >= ‘2017-01-03 21:26:40‘
             and created_time < ‘2017-01-05 08:10:00‘
            then ‘2017-01-05 08:10:00‘
            else ‘2017-01-06 18:53:20‘
        end max_dt,
       count(*)
  from t_source
 group by case when created_time >= ‘2017-01-01‘
             and created_time < ‘2017-01-02 10:43:20‘
            then ‘2017-01-01‘
            when created_time >= ‘2017-01-02 10:43:20‘
             and created_time < ‘2017-01-03 21:26:40‘
            then ‘2017-01-02 10:43:20‘
            when created_time >= ‘2017-01-03 21:26:40‘
             and created_time < ‘2017-01-05 08:10:00‘
            then ‘2017-01-03 21:26:40‘
            else ‘2017-01-05 08:10:00‘
        end,
       case when created_time >= ‘2017-01-01‘
             and created_time < ‘2017-01-02 10:43:20‘
            then ‘2017-01-02 10:43:20‘
            when created_time >= ‘2017-01-02 10:43:20‘
             and created_time < ‘2017-01-03 21:26:40‘
            then ‘2017-01-03 21:26:40‘
            when created_time >= ‘2017-01-03 21:26:40‘
             and created_time < ‘2017-01-05 08:10:00‘
            then ‘2017-01-05 08:10:00‘
            else ‘2017-01-06 18:53:20‘
        end;

查询结果如图二所示。

图二

4份数据的并集应该覆盖整个源数据集,并且数据之间是不重复的。也就是说4份数据的created_time要连续且互斥,连续保证处理全部数据,互斥确保了不需要二次查重。实际上这和时间范围分区的概念类似,或许用分区表更好些,只是这里省略了重建表的步骤。

3. 建立查重的存储过程
        有了以上信息我们就可以写出4条语句处理全部数据。为了调用接口尽量简单,建立下面的存储过程。

delimiter //
create procedure sp_unique(i smallint)
begin
    set @a:=‘0000-00-00 00:00:00‘;
    set @b:=‘ ‘;
	if (i<4) then
        insert into t_target
        select * from t_source force index (idx_sort)
         where created_time >= date_add(‘2017-01-01‘,interval (i-1)*125000 second)
           and created_time < date_add(‘2017-01-01‘,interval i*125000 second)
           and (@a!=created_time or @b!=item_name)
           and (@a:=created_time) is not null
           and (@b:=item_name) is not null
         order by created_time,item_name;
        commit;
    else
	insert into t_target
        select * from t_source force index (idx_sort)
         where created_time >= date_add(‘2017-01-01‘,interval (i-1)*125000 second)
           and created_time <= date_add(‘2017-01-01‘,interval i*125000 second)
           and (@a!=created_time or @b!=item_name)
           and (@a:=created_time) is not null
           and (@b:=item_name) is not null
         order by created_time,item_name;
        commit;
    end if;
end
// 

delimiter ; 

查询的执行计划都如图三所示。

图三

mysql优化器进行索引范围扫描,并且使用索引条件下推(ICP)优化查询。

二、并行执行
        下面分别使用shell后台进程和MySQL Schedule Event实现并行。

1. shell后台进程

(1)建立duplicate_removal.sh文件,内容如下。

#!/bin/bash
mysql -vvv -u root -p123456 test -e "truncate t_target" &>/dev/null
date ‘+%H:%M.%N‘
for y in {1..4}
do
  sql="call sp_unique($y)"
  mysql -vvv -u root -p123456 test -e "$sql" &>par_sql1_$y.log &
done
wait
date ‘+%H:%M.%N‘

(2)执行脚本文件

chmod 755 duplicate_removal.sh
./duplicate_removal.sh

执行输出入图四所示。

图四

这种方法用时3.4秒,并行执行的4个过程调用分别用时如图五所示。

图五

可以看到,每个过程的执行时间均不到3.4秒,因为是并行执行,总的过程执行时间也小于3.4秒,比单线程sql速度提高了近3倍。

2. MySQL Schedule Event
        吴老师也用到了并行,但他是利用MySQL自带的Schedule Event功能实现的,代码应该和下面的类似。

(1)建立事件历史日志表

-- 用于查看事件执行时间等信息
create table t_event_history  (
   dbname  varchar(128) not null default ‘‘,
   eventname  varchar(128) not null default ‘‘,
   starttime  datetime(3) not null default ‘0000-00-00 00:00:00‘,
   endtime  datetime(3) default null,
   issuccess  int(11) default null,
   duration  int(11) default null,
   errormessage  varchar(512) default null,
   randno  int(11) default null
);  

(2)修改event_scheduler参数

set global event_scheduler = 1;

(3)为每个并发线程创建一个事件

delimiter //
create event ev1 on schedule at current_timestamp + interval 1 hour on completion preserve disable do
begin
    declare r_code char(5) default ‘00000‘;
    declare r_msg text;
    declare v_error integer;
    declare v_starttime datetime default now(3);
    declare v_randno integer default floor(rand()*100001);  

    insert into t_event_history (dbname,eventname,starttime,randno)
    #作业名
    values(database(),‘ev1‘, v_starttime,v_randno);    

    begin
        #异常处理段
        declare continue handler for sqlexception
        begin
            set v_error = 1;
            get diagnostics condition 1 r_code = returned_sqlstate , r_msg = message_text;
        end;  

        #此处为实际调用的用户程序过程
        call sp_unique(1);
    end;  

    update t_event_history set endtime=now(3),issuccess=isnull(v_error),duration=timestampdiff(microsecond,starttime,now(3)), errormessage=concat(‘error=‘,r_code,‘, message=‘,r_msg),randno=null where starttime=v_starttime and randno=v_randno;  

end
//     

create event ev2 on schedule at current_timestamp + interval 1 hour on completion preserve disable do
begin
    declare r_code char(5) default ‘00000‘;
    declare r_msg text;
    declare v_error integer;
    declare v_starttime datetime default now(3);
    declare v_randno integer default floor(rand()*100001);  

    insert into t_event_history (dbname,eventname,starttime,randno)
    #作业名
    values(database(),‘ev2‘, v_starttime,v_randno);    

    begin
        #异常处理段
        declare continue handler for sqlexception
        begin
            set v_error = 1;
            get diagnostics condition 1 r_code = returned_sqlstate , r_msg = message_text;
        end;  

        #此处为实际调用的用户程序过程
        call sp_unique(2);
    end;  

    update t_event_history set endtime=now(3),issuccess=isnull(v_error),duration=timestampdiff(microsecond,starttime,now(3)), errormessage=concat(‘error=‘,r_code,‘, message=‘,r_msg),randno=null where starttime=v_starttime and randno=v_randno;  

end
//  

create event ev3 on schedule at current_timestamp + interval 1 hour on completion preserve disable do
begin
    declare r_code char(5) default ‘00000‘;
    declare r_msg text;
    declare v_error integer;
    declare v_starttime datetime default now(3);
    declare v_randno integer default floor(rand()*100001);  

    insert into t_event_history (dbname,eventname,starttime,randno)
    #作业名
    values(database(),‘ev3‘, v_starttime,v_randno);    

    begin
        #异常处理段
        declare continue handler for sqlexception
        begin
            set v_error = 1;
            get diagnostics condition 1 r_code = returned_sqlstate , r_msg = message_text;
        end;  

        #此处为实际调用的用户程序过程
        call sp_unique(3);
    end;  

    update t_event_history set endtime=now(3),issuccess=isnull(v_error),duration=timestampdiff(microsecond,starttime,now(3)), errormessage=concat(‘error=‘,r_code,‘, message=‘,r_msg),randno=null where starttime=v_starttime and randno=v_randno;  

end
//  

create event ev4 on schedule at current_timestamp + interval 1 hour on completion preserve disable do
begin
    declare r_code char(5) default ‘00000‘;
    declare r_msg text;
    declare v_error integer;
    declare v_starttime datetime default now(3);
    declare v_randno integer default floor(rand()*100001);  

    insert into t_event_history (dbname,eventname,starttime,randno)
    #作业名
    values(database(),‘ev4‘, v_starttime,v_randno);    

    begin
        #异常处理段
        declare continue handler for sqlexception
        begin
            set v_error = 1;
            get diagnostics condition 1 r_code = returned_sqlstate , r_msg = message_text;
        end;  

        #此处为实际调用的用户程序过程
        call sp_unique(4);
    end;  

    update t_event_history set endtime=now(3),issuccess=isnull(v_error),duration=timestampdiff(microsecond,starttime,now(3)), errormessage=concat(‘error=‘,r_code,‘, message=‘,r_msg),randno=null where starttime=v_starttime and randno=v_randno;  

end
//

delimiter ;   

说明:为了记录每个事件执行的时间,在事件定义中增加了操作日志表的逻辑,因为每个事件中只多执行了一条insert,一条update,4个事件总共多执行8条很简单的语句,对测试的影响可以忽略不计。执行时间精确到毫秒。

(4)触发事件执行

mysql -vvv -u root -p123456 test -e "truncate t_target;alter event ev1 on schedule at current_timestamp enable;alter event ev2 on schedule at current_timestamp enable;alter event ev3 on schedule at current_timestamp enable;alter event ev4 on schedule at current_timestamp enable;"

说明:该命令行顺序触发了4个事件,但不会等前一个执行完才执行下一个,而是立即向下执行。从图六的输出也可以清楚地看到这一点。因此四次过程调用是并行执行的。

图六

(5)查看事件执行日志

select * from t_event_history;

查询结果如图7所示。

图七

可以看到,每个过程的执行均为3.5秒,又因为是并行执行的,因此总的执行之间也是3.5秒,优化效果和shell后台进程方式几乎相同。

参考:
Increasing slow query performance with the parallel query execution
Mysql Event 调度历史记录

时间: 2024-08-06 16:01:56

将MySQL去重操作优化到极致之三弹连发(二):多线程并行执行的相关文章

将MySQL去重操作优化到极致之三弹连发(一):巧用索引与变量

元旦假期收到阿里吴老师来电,被告知已将MySQL查重SQL优化到极致:100万原始数据,其中50万重复,把去重后的50万数据写入目标表只需要9秒钟.这是一个惊人的数字,要知道仅是insert 50万条记录也需要些时间的.于是来了兴趣,自己实验.思考.总结做了一遍. 一.问题提出        源表t_source结构如下:item_id int,created_time datetime,modified_time datetime,item_name varchar(20),other var

mysql基础操作、sql技巧和sql的常见优化

一.常见操作 1.复制表结构create table t2 like t1 复制表数据insert into t2 select * from t1 2.mysql索引 alter table用来创建普通索引.unique索引或primary key索引 alter table t add index index_name(column_list) alter table t add unique(column_list) alter table t add primary key(column

mysql数据库的优化、恢复等操作

1.当你的自增id主键很大时,你想让id重新到1开始自增                    请输入: truncate table 表名; 2.当你的数据库损坏时,你别慌先试试这条命令是否可以帮助你      请输入: repair table 表1,表2- 3.当你的数据表中含有varchar.text等并进行多次删除添加等操作, 会产生好多碎片空间,这回浪费资源,需要进行数据表的优化, 则可以重获碎片空间                                          

MYSQL之性能优化 ----MySQL性能优化必备25条

今天,数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显.关于数据库的性能,这并不只是DBA才需要担心的事,而这更是我 们程序员需要去关注的事情.当我们去设计数据库表结构,对操作数据库时(尤其是查表时的SQL语句),我们都需要注意数据操作的性能.这里,我们不会讲过 多的SQL语句的优化,而只是针对MySQL这一Web应用最多的数据库.希望下面的这些优化技巧对你有用. 1. 为查询缓存优化你的查询 大多数的MySQL服务器都开启了查询缓存.这是提高性最有效的方法之一,而且这是被

mysql数据库性能优化(包括SQL,表结构,索引,缓存)

优化目标减少 IO 次数IO永远是数据库最容易瓶颈的地方,这是由数据库的职责所决定的,大部分数据库操作中超过90%的时间都是 IO 操作所占用的,减少 IO 次数是 SQL 优化中需要第一优先考虑,当然,也是收效最明显的优化手段.降低 CPU 计算除了 IO 瓶颈之外,SQL优化中需要考虑的就是 CPU 运算量的优化了.order by, group by,distinct … 都是消耗 CPU 的大户(这些操作基本上都是 CPU 处理内存中的数据比较运算).当我们的 IO 优化做到一定阶段之后

MySQL的索引优化,查询优化

MySQL逻辑架构 如果能在头脑中构建一幅MySQL各组件之间如何协同工作的架构图,有助于深入理解MySQL服务器.下图展示了MySQL的逻辑架构图. MySQL逻辑架构,来自:高性能MySQL MySQL逻辑架构整体分为三层,最上层为客户端层,并非MySQL所独有,诸如:连接处理.授权认证.安全等功能均在这一层处理. MySQL大多数核心服务均在中间这一层,包括查询解析.分析.优化.缓存.内置函数(比如:时间.数学.加密等函数).所有的跨存储引擎的功能也在这一层实现:存储过程.触发器.视图等.

Mysql数据库性能优化(一)

参考 http://www.jb51.net/article/82254.htm 今天,数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显.关于数据库的性能,这并不只是DBA才需要担心的事,而这更是我们程序员需要去关注的事情.当我们去设计数据库表结构,对操作数据库时(尤其是查表时的SQL语句),我们都需要注意数据操作的性能.这里,我们不会讲过多的SQL语句的优化,而只是针对MySQL这一Web应用最多的数据库. mysql的性能优化无法一蹴而就,必须一步一步慢慢来,从各个方面

架构设计:系统存储(8)——MySQL数据库性能优化(4)

================================ (接上文<架构设计:系统存储(7)--MySQL数据库性能优化(3)>) 4-3.InnoDB中的锁 虽然锁机制是InnoDB引擎中为了保证事务性而自然存在的,在索引.表结构.配置参数一定的前提下,InnoDB引擎加锁过程是一样的,所以理论上来说也就不存在"锁机制能够提升性能"这样的说法.但如果技术人员不理解InnoDB中的锁机制或者混乱.错误的索引定义和同样混乱的SQL写操作语句共同作用,那么导致死锁出现的

针对MySQL大表优化方案

详解MySQL大表优化方案 (1).字段 (2).索引 (3).规范查询SQL (4).存储引擎 (5).mysql配置参数优化 (6).mysql读写分离 (7).分区和分表 单表优化: 当单表的数据不是一直在暴增,不建议使用拆分,拆分会带来逻辑,部署,运维的各种复杂度,一般以整型值为主的表在千万级以下,字符串为主的表在五百万以下是没有太大问题的.而事实上很多时候MySQL单表的性能依然有不少优化空间,甚至能正常支撑千万级以上的数据量 (1).字段 l 尽量使用TINYINT.SMALLINT