【问题标题】:Insertion in MySQL takes a lot of timeMySQL中的插入需要很多时间
【发布时间】:2014-04-15 07:12:39
【问题描述】:

我有很多表,很少有表有大约 2000 万条记录。 现在,在将结果显示给 UI 之前,我会对这些表进行一些计算。

为此,我创建了存储过程。 在存储过程中,我使用临时表来存储选择查询记录、连接、处理然后返回结果。

我写过这样的查询

Insert INTO A
SELECT * from B JOIN c ....

现在我在这里的选择查询不需要时间(我已经使用解释扩展对其进行了优化)但是我的插入需要很多时间,因为选择的输出以百万为单位。我的表 A 是临时的。

然后我还对这张 A 表做了一些处理。

我的查询是有一种方法可以跳过插入此插入。 我可以将结果放入一个单独的表变量(如果有)中,然后进行处理,而不是临时插入所有这些,然后再次对其进行一些处理。

在此处添加我的存储过程

如果存在则删除过程 6_4_1n2_PortUtil_temp; 分隔符 |

CREATE PROCEDURE 6_4_1n2_PortUtil_temp(utilType VARCHAR(100),service varchar(5000),p_networkType VARCHAR(20),inputCity varchar(5000),inputNodeName varchar(5000), startTime timestamp,endTime timestamp)
BEGIN
DROP TEMPORARY TABLE IF EXISTS TEMP_SERVICE_TBL;
DROP TEMPORARY TABLE IF EXISTS TEMP_NODEIF_TBL;
DROP TEMPORARY TABLE IF EXISTS TEMP_NODEIF_TBL_1;
DROP TEMPORARY TABLE IF EXISTS TEMP_NODEName_TBL;
DROP TEMPORARY TABLE IF EXISTS TEMP_NODEANDIF_TBL;
DROP TEMPORARY TABLE IF EXISTS TEMP_ROUTERTRAFFIC_VLANPRT_SCALE1_TBL;
DROP TEMPORARY TABLE IF EXISTS TEMP_TRAFFIC_TBL;
DROP TEMPORARY TABLE IF EXISTS TRAFFIC_TBL;
DROP TEMPORARY TABLE IF EXISTS FINAL_FETCH;

SELECT now();

CREATE TEMPORARY TABLE TEMP_SERVICE_TBL(nodeName varchar(256),NodeNumber int) ENGINE INNODB;
CREATE TEMPORARY TABLE TEMP_NODEName_TBL(nodeName varchar(256),NodeNumber int) ENGINE INNODB;
CREATE TEMPORARY TABLE TEMP_NODEIF_TBL(NodeNumber int,IfIndex INTEGER,IfSpeed FLOAT,IfDescr VARCHAR(100),IfAlias VARCHAR(100)) ENGINE INNODB;
CREATE TEMPORARY TABLE TEMP_NODEIF_TBL_1(NodeNumber int,IfIndex INTEGER,IfSpeed FLOAT,IfDescr VARCHAR(100),IfAlias VARCHAR(100)) ENGINE INNODB;
CREATE TEMPORARY TABLE TEMP_NODEANDIF_TBL(NodeName Varchar(256),NodeNumber int,IfIndex INTEGER,IfSpeed FLOAT,IfDescr VARCHAR(100),IfAlias VARCHAR(100),PortID BIGINT(20) DEFAULT 0) ENGINE INNODB;

CREATE TEMPORARY TABLE TRAFFIC_TBL(
    PortID                  BIGINT(20),
    NodeName        VARCHAR(100),
    IfDescr         VARCHAR(100),
    IfSpeed         VARCHAR(100),
    InErrPkts       BIGINT(20),
    RcvOctets       BIGINT(20),
    TxOctets        BIGINT(20),
    Time_1          TIMESTAMP) ENGINE INNODB;

CREATE TEMPORARY TABLE TEMP_TRAFFIC_TBL(
        PortID          BIGINT(20), 
    maxTrafficValueIn   BIGINT(20) DEFAULT 0,
    maxOutTrafficValueOut   BIGINT(20) DEFAULT 0,
    avgTrafficValueIn   BIGINT(20) DEFAULT 0,
    avgTrafficValueOut  BIGINT(20) DEFAULT 0,
    CRCError        BIGINT(20) DEFAULT 0,
    UpTime          INTEGER DEFAULT 0,
    Reliablity      INTEGER DEFAULT 0,
    AvgUtilIn       float DEFAULT 0,
    AvgUtilOut      float DEFAULT 0,
    PeakUtilIn      float DEFAULT 0, 
    PeakUtilOut         float DEFAULT 0,
    ThresholdExceed     INTEGER DEFAULT 0,
    inPeakTime      timestamp DEFAULT '0000-00-00 00:00:00',
    outPeakTime         timestamp DEFAULT '0000-00-00 00:00:00') ENGINE INNODB;

SET @where = '';
IF service='ALL'
THEN

    SET @where = '';
ELSE
set @a=1;
set @like="";
select REPLACE(SUBSTRING(SUBSTRING_INDEX(service, ',', @a),LENGTH(SUBSTRING_INDEX(service, ',', @a -1)) + 1),',','') into @service;
while(@service != "")
DO
    IF(@like = "")
    THEN
        SET @like = CONCAT("NodeName like '%",SUBSTRING(@service,2,3),"%'");
    ELSE
        SET @like = CONCAT(@like," or NodeName like '%",SUBSTRING(@service,2,3),"%'");
    END IF;
    set @a=@a+1;
    select REPLACE(SUBSTRING(SUBSTRING_INDEX(service, ',', @a),LENGTH(SUBSTRING_INDEX(service, ',', @a -1)) + 1),',','') into @service;
END WHILE;
SET @where = CONCAT(" where",@like);
END IF;


Set @where2 = '';
IF inputCity='ALL'
THEN
        set @where2 = '';
ELSE
    set @where2 = CONCAT(' and substring(NodeName,1,3) in (',inputCity,')'); 
END IF;


SET @where3 = '';
IF inputNodeName='ALL'
THEN
       set @where3 = '';
ELSE
    set @where3 = CONCAT(' and NodeName in (',inputNodeName,')');   
END IF;

SET @query1 := CONCAT("INSERT INTO TEMP_NODEName_TBL SELECT distinct NodeName,NodeNumber from NODE_TBL",@where, @where2, @where3);

SELECT @query1;

PREPARE statement1 from @query1;
EXECUTE statement1;
DEALLOCATE Prepare statement1;

CREATE INDEX n1 ON TEMP_NODEName_TBL(NodeNumber);
CREATE INDEX i1 ON TEMP_NODEIF_TBL(NodeNumber,IfIndex);
CREATE INDEX portIDIndex1 on TEMP_NODEANDIF_TBL(PortID);

SET @where4 = '';
IF (utilType='ALL')
THEN
    SET @where = '';
ELSE 
    If (utilType = "'AESI-IN'")
    THEN
        SET @where4 = " where IfAlias like '%AESI-IN%'";
    ELSE 
        SET @where4 = " where IfAlias NOT like '%AESI-IN%'";
    END IF;
END IF;




CREATE INDEX i2 ON TEMP_NODEIF_TBL_1(NodeNumber,IfIndex);
CREATE INDEX i3 ON TEMP_NODEANDIF_TBL(NodeNumber,IfIndex);

SET @where5 = ''; 
IF(p_networkType != 'ALL')
THEN
set @r1= SUBSTRING(p_networkType,2,3);
set @r2= SUBSTRING(p_networkType,8,3);
set @r3= SUBSTRING(p_networkType,14,3);
    SET @where5 = CONCAT(" and IfAlias like '%",@r1,"%'");

    if(@r2 != "")
    THEN
        SET @where5 = CONCAT(" and IfAlias like '%",@r2,"%'");

        IF(@r3 != "")
        THEN
            SET @where5 = CONCAT(" and IfAlias like '%",@r3,"%'");
        END IF;
    END IF;
END IF;

SET @query2 := CONCAT("INSERT INTO TEMP_NODEANDIF_TBL(NodeName,NodeNumber,IfIndex,IfSpeed,IfDescr,IfAlias)  SELECT distinct b.NodeName, a.NodeNumber, a.IfIndex,a.IfSpeed,a.IfDescr,a.IfAlias from NODEIF_TBL a JOIN TEMP_NODEName_TBL b ON a.NodeNumber = b.NodeNumber ", @where4, @where5);

SELECT @query2; 
PREPARE statement1 from @query2;
EXECUTE statement1;
DEALLOCATE Prepare statement1;

SELECT "DROP TEMPORARY TABLES";

DROP TEMPORARY TABLE IF EXISTS TEMP_NODEIF_TBL;
DROP TEMPORARY TABLE IF EXISTS TEMP_NODEIF_TBL_1;
DROP TEMPORARY TABLE IF EXISTS TEMP_NODEName_TBL;

update TEMP_NODEANDIF_TBL a,VLANPRT_TBL b  set a.PortID = PrtID  where a.NodeNumber = b.NodeID and a.IfIndex = b.IfIndex;

SELECT "Update Temporary tables";

delete from TEMP_NODEANDIF_TBL where PortID = 0;

SELECT now();

INSERT INTO TRAFFIC_TBL 
            SELECT a.PortID,NodeName,IfDescr,IfSpeed,InErrPkts,RcvOctets,TxOctets,Time_1 FROM ROUTERTRAFFIC_VLANPRT_SCALE1_TBL a JOIN TEMP_NODEANDIF_TBL b ON a.PortID = b.PortID  where Time_1>startTime and Time_1<endTime;

SELECT now();

DROP TEMPORARY TABLE IF EXISTS EXCEED_COUNT;
CREATE TEMPORARY TABLE EXCEED_COUNT (PortID BIGINT(20), Exceed INTEGER);

INSERT INTO EXCEED_COUNT
        select PortID,count(RcvOctets) from TRAFFIC_TBL where (RcvOctets/(IfSpeed*10)>70 or TxOctets/(IfSpeed*10)>70) group by PortID;

INSERT INTO TEMP_TRAFFIC_TBL (PortID, maxTrafficValueIn,maxOutTrafficValueOut,avgTrafficValueIn,avgTrafficValueOut,CRCError,AvgUtilIn,AvgUtilOut,PeakUtilIn,PeakUtilOut)
    SELECT  PortID,max(RcvOctets),max(TxOctets),avg(RcvOctets),avg(TxOctets), sum(InErrPkts),
    IF((IfSpeed=0),"0",(avg(RcvOctets)/(IfSpeed*10))),
    IF((IfSpeed=0),"0",(avg(TxOctets)/(IfSpeed*10))),
    IF((IfSpeed=0),"0",(max(RcvOctets)/(IfSpeed*10))),
    IF((IfSpeed=0),"0",(max(TxOctets)/(IfSpeed*10)))
        from TRAFFIC_TBL group by PortID;

CREATE INDEX portIDIndex2 on TEMP_TRAFFIC_TBL(PortID);
CREATE INDEX portIDIndex3 on EXCEED_COUNT(PortID);

UPDATE TEMP_TRAFFIC_TBL A JOIN TRAFFIC_TBL B ON A.PortId=B.PortId SET inPeakTime=B.Time_1 where B.RcvOctets=A.maxTrafficValueIn;
UPDATE TEMP_TRAFFIC_TBL A JOIN TRAFFIC_TBL B ON A.PortId=B.PortId SET outPeakTime=B.Time_1 where B.TxOctets=A.maxOutTrafficValueOut;

UPDATE TEMP_TRAFFIC_TBL A JOIN EXCEED_COUNT B ON (A.PortID = B.PortID)
    set ThresholdExceed = Exceed;

SELECT substring(NodeName,5,3) as ServiceType,
CASE
WHEN IfAlias like '%SWH%' THEN "Trunk"
WHEN IfAlias like '%AES%' THEN "Backbone"
WHEN IfAlias like '%RTR%' THEN "Back-to-Back"
ELSE "-"
END ,
NodeName,IfDescr,ROUND(maxTrafficValueIn/1000,2),ROUND(maxOutTrafficValueOut/1000,2),ROUND(avgTrafficValueIn/1000,2),ROUND(avgTrafficValueOut/1000,2),ROUND(CRCError/1000,2),0,0,ROUND(AvgUtilIn,2),ROUND(AvgUtilOut,2),ROUND(PeakUtilIn,2),ROUND(PeakUtilOut,2),ThresholdExceed,inPeakTime,outPeakTime  from TEMP_TRAFFIC_TBL a ,TEMP_NODEANDIF_TBL b where a.PortID = b.PortID ;

SELECT now(); 

END |
DELIMITER ;

我的 ROUTERTRAFFIC_VLANPRT_SCALE1_TBL 包含大约 2000 万条记录和 NodeIF_TBL 大约 10 万条记录。我的 VLANPRT_TBL 还包含大约 10 万条记录。 我还在 time_1 上设置了 BTree 索引,这样大表上的连接就不需要时间了

【问题讨论】:

  • 请显示schemaquery,没有它我们无法帮助!!
  • 您能解释一下您要达到的目标吗?为什么要将 2000 万条记录放入一个临时表中?
  • 你如何衡量a lot of time
  • 我有一张表 B,里面有大约 200 万条记录。另一个带有 10 万条记录的 C 表。我加入这些表,并通过 where 子句使用 select 获得 20 万条记录。现在我将它写入一个临时表以对该表进行进一步处理。现在选择语句大约需要 1 秒,而插入大约需要 10 秒,这是我不想要的。我想减少这个插入时间
  • 将其分解为数百个插入的事务可能会加快速度。但是需要测试产生最佳速度的确切插入数量。

标签: mysql


【解决方案1】:

临时表:

临时表从内存中开始,如果超过一定的限制,它们将被写入磁盘。如果选择产生数百万并且您将这些数百万写入磁盘,那么这就是您的问题。

需要一个包含数百万条记录的临时表通常是做错事的迹象。也许您应该在将数据插入临时表进行处理之前进一步过滤数据。

磁盘 I/O 抖动:

我们有几个INSERT 案例:

  1. INSERT INTO ... VALUES
  2. INSERT INGORE INTO ... SELECT
  3. INSERT INTO ... SELECT

对于 1. 每个插入都有自己的事务并单独写入磁盘,这意味着许多微小的 I/O 操作。

对于 2. 同样适用,只是数据也从磁盘读取(I/O 操作稍多)。

对于 3. 整个操作是一个大事务,这意味着在事务的最后(提交)一个大磁盘 I/O。更常见的情况是,大事务中包含的信息不能完全保存在内存中,因此在最终提交之前将其临时写入磁盘,然后从那里复制到它应该驻留的指定区域。

在所有 3 种情况下都会发生磁盘 I/O 抖动,为了防止这种情况,您可以将小型操作分组为平均大小的操作,并将大型操作分解为平均大小的操作。

对于 1. 您需要将每 N 个插入包装在一个事务中。

对于 2. 和 3. 您需要将 LIMIT SELECT 子句添加到 N 并重复 INSERT INTO ... SELECT 并添加 N 的 OFFSET,直到完成所有记录。

如果您使用脚本语言来运行查询,这两种方法都应该很容易做到。

【讨论】:

  • 为什么你认为INSERT INTO SELECT 插入表会逐行写入磁盘?
  • 因为每个插入都保证是一个“原子”操作——一个独立的事务——最后将其数据提交到磁盘。
  • 真的吗?你认为在INSERT INTO SELECT 中你有一堆插入行数的隐式事务吗?
  • 当您INSERT INTO ... SELECT 100 行且第 50 行具有重复的主键时,已经执行的 50 个INSERTs 不会回滚,它们只是留在原处——只有当前的@987654333 @ 被回滚,其余的由于错误没有执行。
  • @HamletHakobyan 我正在测试它——我会回来确认。
【解决方案2】:

它不需要将查询的输出存储在临时表中以便在另一个查询中使用它。你可以这样做:

Select t1.* from (select * from A where condition1)as t1  where condition2

这是我必须做的查询示例。

select avg(v1) as v1avg ,start_date_time as time, timekey as time_key from (select ATable.*, ROUND(UNIX_TIMESTAMP(start_date_time)/(60*60)) as timekey  from ATable where start_date_time between '2014-01-01 00:00:00' and '2014-01-10 00:00:00')as t1 group by timekey;

【讨论】:

  • 听说子查询比较慢。是真的吗?
  • 没有。如果查询优化器无法优化子查询,则“可能”会变慢。
  • 子查询有 3 种类型:SELECT 区域中的子查询(结果中的每一行都需要运行一次); FROM(或JOIN)区域中的子查询需要运行一次以构建结果表; WHERE 区域中的子查询,需要运行一次以构建结果表。
  • @Nipun 想想这三种类型的子查询,想象一下哪一种慢,哪一种慢;想想哪些可以优化,哪些不能等等。
  • 非常感谢米海。最后一个查询。当我们在存储过程中执行 select * 时,它在内部是如何工作的。 mysql 是创建一个临时表,然后在控制台上显示该临时表的行还是有其他一些行为。如果它在内部创建一个临时表,我可以使用该表而不是创建自己的临时表并在其中插入元素
猜你喜欢
  • 2014-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-24
  • 2016-08-13
相关资源
最近更新 更多