MySQL SQL 性能需要改进

MySQL SQL Performance need some improvement

我已经通过 MySQL 解决了许多挑战,我认为现在我能够构建我需要的一切,让一些东西发挥作用。但是现在,对于一个相当大的 SQL 声明 returns 大量数据,我需要第一次处理 MySQL 性能。

我希望这里有人能帮我找出为什么下面的语句如此慢。从不同的 table 中收集 740 个结果需要 3 分钟多的时间。最大的 table 是“报告”table,目前包含超过 20.000 个条目。

如果有人能给我指出正确的方向,我也可以自学。我什至不知道去哪里寻找我当前问题的答案。

好的,这就是我正在谈论的声明。也许,如果有人在 SQL 性能方面有足够的经验,一些东西就会立即跳到他们身上。我很乐意提供任何反馈。我将在代码本身之后详细说明声明:

SELECT 
    R_ID,
    R_From,
    R_To,
    SUM(UR_TotalTime) AS UR_TotalTime,
    R_Reported,
    U_ID,
    U_Lastname,
    U_Firstname,
    C_ID,
    C_Lastname,
    C_Firstname,
    R_Breaks,
    MAX(CR_BID) AS CR_BID,
    R_Type,
    R_Distance,
    R_AdditionalDistance,
    R_Activities,
    R_Description,
    R_Signature,
    CT_SigReq,
    MAX(I_LastIntegration) AS I_LastIntegration
FROM
    reports
        LEFT JOIN
    userreports ON R_ID = UR_RID
        LEFT JOIN
    users ON R_UID = U_ID
        LEFT JOIN
    customers ON R_CID = C_ID
        LEFT JOIN
    customerterms ON CT_CID = R_CID
        LEFT JOIN
    integration ON R_UID = I_UID
        LEFT JOIN
    customerreports ON R_ID = CR_RID
WHERE
    (CAST(R_From AS DATE) BETWEEN CT_From AND CT_To
        OR R_CID = 0)
        AND ((R_From BETWEEN '2021-02-01 00.00.00' AND '2021-02-28 23.59.59')
        OR (R_To BETWEEN '2021-02-01 00.00.00' AND '2021-02-28 23.59.59')
        OR (R_From <= '2021-02-01 00.00.00'
        AND R_To >= '2021-02-28 23.59.59'))
GROUP BY R_ID
ORDER BY R_From ASC

所以我这里有以下内容: reports (R_*) - 这是查询的主要table。我需要它的一些数据,但它也是过滤器,因为我只需要特定时间戳之间的结果。

CREATE TABLE `reports`  (
  `R_ID` int(100) NOT NULL AUTO_INCREMENT,
  `R_Type` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `R_UID` int(6) NOT NULL,
  `R_CID` int(10) NOT NULL,
  `R_From` datetime(0) NOT NULL,
  `R_To` datetime(0) NOT NULL,
  `R_Traveltime` int(11) NOT NULL,
  `R_Breaks` int(11) NOT NULL DEFAULT 0,
  `R_PayoutFlextime` decimal(20, 2) NOT NULL DEFAULT 0.00,
  `R_Distance` int(11) NOT NULL DEFAULT 0,
  `R_AdditionalDistance` int(11) NOT NULL DEFAULT 0,
  `R_Activities` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `R_Description` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `R_Signature` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '0',
  `R_SignatureDate` datetime(0) DEFAULT NULL,
  `R_Reported` datetime(0) NOT NULL,
  `R_Status` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'New',
  `R_EditedBy` int(11) DEFAULT NULL,
  `R_EditedDateTime` datetime(0) DEFAULT NULL,
  PRIMARY KEY (`R_ID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

userreports (UR_*) - 提供一些根据报告中的源数据计算的数据

CREATE TABLE `userreports`  (
  `UR_ID` int(11) NOT NULL AUTO_INCREMENT,
  `UR_RID` int(100) NOT NULL,
  `UR_UID` int(6) NOT NULL,
  `UR_Date` date NOT NULL,
  `UR_From` time(0) NOT NULL,
  `UR_To` time(0) NOT NULL,
  `UR_ReportedTime` decimal(20, 5) DEFAULT NULL,
  `UR_ReportedTravel` decimal(20, 5) NOT NULL,
  `UR_ReportedBreaks` decimal(20, 5) DEFAULT NULL,
  `UR_TotalPercentageSurcharge` decimal(20, 2) DEFAULT NULL,
  `UR_TotalTime` decimal(20, 5) DEFAULT NULL,
  `UR_PercentageSurchargeTypes` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `UR_Distance` decimal(20, 2) DEFAULT NULL,
  `UR_AdditionalDistance` decimal(20, 2) DEFAULT NULL,
  `UR_TravelCompensation` decimal(20, 2) DEFAULT NULL,
  PRIMARY KEY (`UR_ID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

customerreports (CR_*) - 与用户报告相同,但包含从客户角度计算的数据

CREATE TABLE `customerreports`  (
  `CR_ID` int(11) NOT NULL AUTO_INCREMENT,
  `CR_RID` int(100) NOT NULL,
  `CR_CID` int(6) NOT NULL,
  `CR_Date` date NOT NULL,
  `CR_From` time(0) NOT NULL,
  `CR_To` time(0) NOT NULL,
  `CR_ReportedTime` decimal(20, 2) DEFAULT NULL,
  `CR_ReportedBreaks` decimal(20, 2) DEFAULT NULL,
  `CR_Hourly` decimal(20, 2) DEFAULT NULL,
  `CR_Salary` decimal(20, 2) DEFAULT NULL,
  `CR_TotalPercentageSurcharge` decimal(20, 2) DEFAULT NULL,
  `CR_TotalFixedSurcharge` decimal(20, 2) DEFAULT NULL,
  `CR_TotalTime` decimal(20, 2) DEFAULT NULL,
  `CR_TotalSalary` decimal(20, 2) DEFAULT NULL,
  `CR_FixedSurchargeTypes` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `CR_PercentageSurchargeTypes` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `CR_Distance` decimal(20, 2) DEFAULT NULL,
  `CR_AdditionalDistance` decimal(20, 2) DEFAULT NULL,
  `CR_TravelCompensation` decimal(20, 2) DEFAULT NULL,
  `CR_BID` int(11) NOT NULL DEFAULT 0,
  PRIMARY KEY (`CR_ID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

users (U_*) - 显然将数据传递给创建报告的用户,例如姓名,...

CREATE TABLE `users`  (
  `U_ID` int(6) NOT NULL AUTO_INCREMENT,
  `U_PW` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_PWInitial` tinyint(1) NOT NULL,
  `U_FailedAttempts` int(1) NOT NULL,
  `U_Email` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_Title` text CHARACTER SET utf8 COLLATE utf8_general_ci,
  `U_Firstname` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_Lastname` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_ETC` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_Street` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_Housenumber` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_Code` varchar(5) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_City` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_Phone` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_Mobile` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_Birthdate` date NOT NULL,
  `U_Sex` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_Maritalstatus` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_Severelydisabled` tinyint(1) NOT NULL,
  `U_Severelydisabledspecify` int(3) NOT NULL,
  `U_Citizenship` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_Education` varchar(70) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_Vocationaltraining` varchar(70) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_CLID` tinyint(1) NOT NULL,
  `U_CLSpecify` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_IBAN` varchar(27) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_BIC` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_INID` int(11) DEFAULT NULL,
  `U_Insurancenumber` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_Insurancetype` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_Taxidentificationnumber` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_Confession` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_Entry` date NOT NULL,
  `U_TEntry` date NOT NULL,
  `U_Exit` date NOT NULL DEFAULT '9999-12-31',
  `U_Hourscarryover` decimal(20, 2) NOT NULL,
  `U_TotalHolidayCarryover` int(11) NOT NULL DEFAULT 0,
  `U_UsedHolidayCarryover` int(11) NOT NULL DEFAULT 0,
  `U_SIN` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `U_RVBDone` tinyint(1) NOT NULL DEFAULT 0,
  `U_ClosedMonth` date NOT NULL DEFAULT '1970-01-01',
  `U_DeleteDate` date DEFAULT NULL,
  PRIMARY KEY (`U_ID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

customers (C_*) - 与用户相同,但针对用户工作的客户数据

CREATE TABLE `customers`  (
  `C_ID` int(10) NOT NULL AUTO_INCREMENT,
  `C_MID` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `C_Active` tinyint(1) NOT NULL,
  `C_Email` text CHARACTER SET utf8 COLLATE utf8_general_ci,
  `C_Title` text CHARACTER SET utf8 COLLATE utf8_general_ci,
  `C_Firstname` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `C_Lastname` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `C_Birthdate` date NOT NULL,
  `C_ETC` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `C_Street` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `C_Housenumber` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `C_Code` varchar(5) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `C_City` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `C_Phone` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `C_Mobile` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `C_IBAN` varchar(27) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `C_BIC` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `C_Insurancenumber` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `C_INID` int(11) DEFAULT NULL,
  `C_Insurancetype` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `C_Sex` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `C_Contact1` text CHARACTER SET utf8 COLLATE utf8_general_ci,
  `C_Contact2` text CHARACTER SET utf8 COLLATE utf8_general_ci,
  `C_ContactChoice` int(1) DEFAULT 0,
  `C_DeleteDate` date DEFAULT NULL,
  `C_DeactivationDate` date DEFAULT NULL,
  `C_CreationDate` date DEFAULT NULL,
  `C_DeceasedDate` date DEFAULT NULL,
  PRIMARY KEY (`C_ID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

集成 (I_*) - 提供有关报告是否已集成(且无法再更改)的数据

CREATE TABLE `integration`  (
  `I_ID` int(11) NOT NULL AUTO_INCREMENT,
  `I_UID` int(11) NOT NULL,
  `I_LastIntegration` date NOT NULL DEFAULT '1970-01-01',
  `I_SumFlextime` decimal(20, 2) NOT NULL DEFAULT 0.00,
  `I_OldHolidays` int(5) NOT NULL DEFAULT 0,
  PRIMARY KEY (`I_ID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

customerterms (CT_*) - 在这种情况下仅在指定客户需要签署报告时提供

CREATE TABLE `customerterms`  (
  `CT_ID` int(50) NOT NULL AUTO_INCREMENT,
  `CT_CID` int(10) NOT NULL,
  `CT_From` date NOT NULL,
  `CT_To` date NOT NULL,
  `CT_Hourly` decimal(20, 2) NOT NULL,
  `CT_FixedTravelCompensation` decimal(20, 2) NOT NULL,
  `CT_PerKMCompensationBase` decimal(20, 2) NOT NULL,
  `CT_PerKMCompensationAdditional` decimal(20, 2) NOT NULL,
  `CT_MaxTravelCompensationReport` decimal(20, 2) DEFAULT NULL,
  `CT_MaxTravelCompensationMonthly` decimal(20, 2) DEFAULT NULL,
  `CT_FixedSaturdaySurcharge` decimal(20, 2) NOT NULL DEFAULT 0.00,
  `CT_PercentageSaturdaySurcharge` decimal(20, 2) NOT NULL DEFAULT 1.00,
  `CT_FixedSundaySurcharge` decimal(20, 2) NOT NULL DEFAULT 0.00,
  `CT_PercentageSundaySurcharge` decimal(20, 2) NOT NULL DEFAULT 1.00,
  `CT_FixedHolidaySurcharge` decimal(20, 2) NOT NULL DEFAULT 0.00,
  `CT_PercentageHolidaySurcharge` decimal(20, 2) NOT NULL DEFAULT 1.00,
  `CT_SigReq` int(1) NOT NULL,
  `CT_NighttimeFrom` time(0) NOT NULL DEFAULT '00:00:00',
  `CT_NighttimeTo` time(0) NOT NULL DEFAULT '00:00:00',
  `CT_FixedNighttimeSurcharge` decimal(20, 2) NOT NULL DEFAULT 0.00,
  `CT_PercentageNighttimeSurcharge` decimal(20, 2) NOT NULL DEFAULT 1.00,
  `CT_StackingSurcharge` tinyint(1) NOT NULL DEFAULT 0,
  `CT_MinimumTime` int(11) NOT NULL DEFAULT 1,
  `CT_TimeIncrement` int(11) NOT NULL DEFAULT 1,
  PRIMARY KEY (`CT_ID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

服务器是 运行 MySQL 5.7,有 4 个 4.6Ghz 处理器和 16GB 可用内存。

由于这是一个业余项目,我正在支持小型护理企业,让他们更轻松地管理他们的日常任务,我可以改变这里的一切。代码、数据库布局,应有尽有。只要办公室里的穷人不用等5分钟,就为了有时甚至只能得到一个超时...

我会将 EXPLAIN 的结果添加为图像,因为我无法让它看起来很好 否则...

+─────+──────────────+──────────────────+─────────────+─────────+──────────────────────+──────────+──────────+──────────────────────────+───────+───────────+─────────────────────────────────────────────────────+
| id  | select_type  | table            | partitions  | type    | possible_keys        | key      | key_len  | ref                      | rows  | filtered  | Extra                                               |
+─────+──────────────+──────────────────+─────────────+─────────+──────────────────────+──────────+──────────+──────────────────────────+───────+───────────+─────────────────────────────────────────────────────+
| 1   | SIMPLE       | reports          | NULL        | ALL     | PRIMARY,R_From,R_To  | NULL     | NULL     | NULL                     | 22249 | 29.76     | Using where; Using temporary; Using filesort        |
| 1   | SIMPLE       | userreports      | NULL        | ALL     | NULL                 | NULL     | NULL     | NULL                     | 21359 | 100.00    | Using where; Using join buffer (Block Nested Loop)  |
| 1   | SIMPLE       | users            | NULL        | eq_ref  | PRIMARY              | PRIMARY  | 4        | dbs671769.reports.R_UID  | 1     | 100.00    | NULL                                                |
| 1   | SIMPLE       | customers        | NULL        | eq_ref  | PRIMARY              | PRIMARY  | 4        | dbs671769.reports.R_CID  | 1     | 100.00    | NULL                                                |
| 1   | SIMPLE       | customerterms    | NULL        | ALL     | NULL                 | NULL     | NULL     | NULL                     | 1429  | 100.00    | Using where; Using join buffer (Block Nested Loop)  |
| 1   | SIMPLE       | integration      | NULL        | ALL     | NULL                 | NULL     | NULL     | NULL                     | 1134  | 100.00    | Using where; Using join buffer (Block Nested Loop)  |
| 1   | SIMPLE       | customerreports  | NULL        | ALL     | NULL                 | NULL     | NULL     | NULL                     | 9078  | 100.00    | Using where; Using join buffer (Block Nested Loop)  |
+─────+──────────────+──────────────────+─────────────+─────────+──────────────────────+──────────+──────────+──────────────────────────+───────+───────────+─────────────────────────────────────────────────────+

有没有什么方法可以更快、更可靠地整合所有这些数据?

在此先感谢您对此的任何帮助或想法。

让我们首先为查询中使用的每个外键添加索引 -

ALTER TABLE `userreports`
    ADD INDEX `FK_UR_RID` (`UR_RID`);

ALTER TABLE `customerterms`
    ADD INDEX `FK_CT_CID` (`CT_CID`);

ALTER TABLE `integration`
    ADD INDEX `FK_I_UID` (`I_UID`);

ALTER TABLE `customerreports`
    ADD INDEX `FK_CR_RID` (`CR_RID`);

请添加这些索引,然后将更新后的 EXPLAIN 输出以及以下查询的结果添加到您的问题中。

-- this just retrieves some basic stats about size of each table used in your query
SELECT TABLE_NAME, ENGINE, VERSION, TABLE_ROWS, AVG_ROW_LENGTH, DATA_LENGTH, INDEX_LENGTH
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'dbs671769'
AND TABLE_NAME IN('customerreports', 'customers', 'customerterms', 'integration', 'reports', 'userreports', 'users');
    WHERE  (CAST(R_From AS DATE) BETWEEN CT_From AND CT_To
              OR  R_CID = 0
           )

OR 反应迟钝。有什么方法可以摆脱 R_CID = 0 吗?如果没有,我们可以谈谈UNION

重写其余部分:

           R_From >= CT_From  AND  R_From < CT_To + INTERVAL 1 DAY


      AND  ((R_From BETWEEN '2021-02-01 00.00.00'
                        AND '2021-02-28 23.59.59')
          OR  (R_To BETWEEN '2021-02-01 00.00.00'
                        AND '2021-02-28 23.59.59')
          OR  (R_From    <= '2021-02-01 00.00.00'
               AND  R_To >= '2021-02-28 23.59.59')
           )

R_From能保证是< R_To吗?如果是这样,这种简化(或删除)是否会做同样的事情

   AND R_From  < '2021-03-01'
   AND R_To   >= '2021-02-01'

这需要对中间结果进行两次传递:

    GROUP BY  R_ID
    ORDER BY  R_From ASC

这需要一次通过,但通常会给出相同的结果,甚至可能会得到更好的结果:

    GROUP BY  R_From, R_ID
    ORDER BY  R_From, R_ID

(让人恼火的是:不要在列名前加上 table 名称(或 'R_');在 JOINing 时对所有列使用别名:SELECT R.ID ... FROM reports AS R JOIN ... )

另一个回答提到了一些INDEXes;这可能会给你很多加速。经过我的一些建议,可能会有更多的索引提示。

TEXT 列有一些开销;您列出的许多案例都可以用更小的东西来完成,比如 VARCHAR(100)。例如,目前世界上最长的“城市”名称只有 91 个字符:“Poselok Uchebnogo Khozyaystva Srednego Professionalno-Tekhnicheskoye Uchilishche Nomer Odin”

你好像是运行旧版的MySQL?否则你可能会因为 GROUP BY;比照“only_full_group_by”。