MySQL SQL Performance need some improvement - mysql

I have worked my way around many challenges with MySQL, and i think right now i am able to build everything that i need, to get something to work. But now, for a pretty huge SQL statement that returns a lot of data, i need to work on MySQL performance for the first time.
I was hoping someone here could help me find out why the following statement is so incredibly slow. It takes over 3 minutes to collect 740 results out of different tables. The biggest table beeing the "reports" table, consisting of somewhere over 20.000 entries at the moment.
I can also educate myself if someone could just point me in the right direction. I don't even know where to search for answers for my current problem.
Okay, so here is the statement that i am talking about. Maybe, if someone has enough experience with SQL performance, something just right away jumps at them. I would be happy for any kind of feedback. I'll elaborate on the statement right after the code itself:
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
So what i have here is the following:
reports (R_*) - This is the main table that is queried. I need some of it's data, but it's also the filter, since i only need results between specific timestamps.
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_*) - Delivers some data that is calculated from the sourcedata in reports
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_*) - Same as userreports, but with calculated data from the customers perspective
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_*) - Obviously delivers Data to the user that created the report, e.g. name,...
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_*) - Same as users, but for the data of the customer that the user worked on
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;
integration (I_*) - Provides data on whether or not the report is already integrated (and can no longer be changed)
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_*) - In this case only provides if the specified customer needs to sign the report
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;
The server is running MySQL 5.7, has 4 processors at 4,6Ghz, and 16GB of RAM available.
Since this is a hobby project, that i am supporting small care-businesses with, to allow them easier management of their daily tasks, i can change everything here. Code, Database Layout, you name it. As long as the poor people in the office don't have to wait for 5 minutes, just to sometimes even only get a timeout...
I'll add the result of EXPLAIN as image, since i can't get it to look good
otherwise...
+─────+──────────────+──────────────────+─────────────+─────────+──────────────────────+──────────+──────────+──────────────────────────+───────+───────────+─────────────────────────────────────────────────────+
| 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) |
+─────+──────────────+──────────────────+─────────────+─────────+──────────────────────+──────────+──────────+──────────────────────────+───────+───────────+─────────────────────────────────────────────────────+
Is there any way to consolidate all this data faster, but as reliable?
Thanks a lot in advance, for any help or idea on this.

Let's start by adding an index for each of the foreign keys used in your query -
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`);
Please add these indices and then add the updated EXPLAIN output plus the result of the following query to your question.
-- 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 is sluggish. Is there some way to get rid of R_CID = 0? If not, we can talk about UNION.
Rewrite the rest thus:
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')
)
Is R_From guaranteed to be < R_To? If so, would this simplification (OR-removal) do the same
AND R_From < '2021-03-01'
AND R_To >= '2021-02-01'
This requires two passes over the intermediate results:
GROUP BY R_ID
ORDER BY R_From ASC
This requires one pass, while usually giving the same results, maybe even better results:
GROUP BY R_From, R_ID
ORDER BY R_From, R_ID
(Pet peeve: Don't prefix column names with the table name (or 'R_'); Do use aliases for all columns when JOINing: SELECT R.ID ... FROM reports AS R JOIN ... )
Another Answer has mentioned some INDEXes; that may give you a lot of speed-up. After some of my suggestions, there may be more index tips.
TEXT columns have some overhead; many of the cases you list could be done with something smaller, like VARCHAR(100). For example, currently, the longest "city" name in the world has only 91 chars: "Poselok Uchebnogo Khozyaystva Srednego Professionalno-Tekhnicheskoye Uchilishche Nomer Odin"
You seem to be running an old version of MySQL? Else you might have been dinged for the GROUP BY; cf "only_full_group_by".

Related

Why using inner join itself is more faster than where

Here is my table:
CREATE TABLE `cerp_oms_order` (
`id` bigint NOT NULL,
`company_id` bigint NOT NULL,
`order_no` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`source_type` int NOT NULL,
`shop_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`outer_shop` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`origin_status` int NOT NULL,
`system_status` int NOT NULL,
`created_time` datetime DEFAULT NULL,
`paid_time` datetime DEFAULT NULL,
`sent_time` datetime DEFAULT NULL,
`end_time` datetime DEFAULT NULL,
`modify_time` datetime DEFAULT NULL,
`delivery_deadline_time` datetime DEFAULT NULL,
`amount` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`spu_kind` int NOT NULL,
`sku_kind` int NOT NULL,
`total_quantity` decimal(16,4) NOT NULL,
`buyer_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`outer_buyer_identifier` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
`tax_info` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`warehouse_owner` int DEFAULT NULL,
`warehouse_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`logistics_type` int NOT NULL,
`logistics_outer_info` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`delivery_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`delivery_no` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`wave_no` varchar(64) DEFAULT '',
`is_deleted` tinyint NOT NULL DEFAULT '0',
`backend_processing_type` tinyint NOT NULL,
`create_type` tinyint NOT NULL,
`is_hang_up` tinyint NOT NULL,
`hang_up_case_type` smallint DEFAULT NULL,
`hang_up_case_id` bigint DEFAULT NULL,
`rc_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`rm_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`vat` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`weight` decimal(16,4) NOT NULL DEFAULT '0.0000',
`volume` decimal(16,4) NOT NULL DEFAULT '0.0000',
`is_abnormal` tinyint NOT NULL DEFAULT '0',
`estimate_profit` decimal(16,4) NOT NULL DEFAULT '0.0000',
`business_man_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`business_man` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`currency` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`net_amount_summary` decimal(19,4) NOT NULL,
`domestic_amount` decimal(19,4) NOT NULL,
`secret_key` varchar(64) DEFAULT '',
`secretKey` varchar(255) DEFAULT NULL,
`sale_id` bigint DEFAULT NULL,
`total_refund_include_tax` decimal(16,4) NOT NULL DEFAULT '0.0000',
`total_refund_money` decimal(16,4) NOT NULL DEFAULT '0.0000',
`total_refund_tax` decimal(16,4) NOT NULL DEFAULT '0.0000',
`total_return_goods` decimal(16,2) NOT NULL DEFAULT '0.00',
PRIMARY KEY (`id`),
UNIQUE KEY `key_order_no` (`order_no`),
KEY `idx_order_company_id` (`company_id`,`created_time`),
KEY `IDX_RM_TIME` (`rm_time`),
KEY `IDX_IS_ABNORMAL` (`is_abnormal`),
KEY `cerp_oms_order_company_id_index` (`company_id`),
KEY `idx_order_company_status_deleted` (`company_id`,`is_deleted`,`system_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='system order table'
explain select *
from cerp_oms_order
inner join (select id
from cerp_oms_order
where source_type = 43
order by created_time) as tmp using (id);
id
select_type
table
partitions
type
possible_keys
key
key_len
ref
rows
filtered
Extra
1
SIMPLE
cerp_oms_order
null
ALL
PRIMARY
null
null
null
60787
10
Using where
1
SIMPLE
cerp_oms_order
null
eq_ref
PRIMARY
PRIMARY
8
cerp_oms_1.cerp_oms_order.id
1
100
null
using inner join execution: 7 ms, fetching: 109 ms
VS
explain
select *
from cerp_oms_order
where source_type = 43
order by created_time;
id
select_type
table
partitions
type
possible_keys
key
key_len
ref
rows
filtered
Extra
1
SIMPLE
cerp_oms_order
null
ALL
null
null
null
null
60787
10
Using where; Using filesort
using simple where clause execution: 80 ms, fetching: 138 ms
I don't understand why use inner join can speed up my sql?
If you had
INDEX(source_type, created_time)
both formulations would run faster. And, I think, the second would be faster.
As for "why". Look at what the second does:
Scan the entire table ("table scan") looking for rows with source_type = 43.
For each matching row, gather all the columns (*) from the row, into a temp table.
Sort that temp table. Note that this is bulkier than with the subquery that hauls around only id.
Deliver the results.
With the INDEX I suggest:
Look in the index's B+Tree for all 'rows' with source_type = 43.
Since those rows are sorted by created_time, not "sort" pass will be needed now or later.
Each of those index rows will contain id. (That's the way InnoDB gets to the rest of the columns), so
Look up the desired rows in the data's B+Tree and pull out all (*) the columns.
Deliver them.
Note that the JOIN version works essentially the same, but, technically speaking, the ORDER BY may get lost. If it does get lost, you would need to add a second ORDER BY, thereby forcing a sort.
(Unrelated) Your "KEY cerp_oms_order_company_id_index (company_id)," can be dropped since there are two other indexes starting with company_id.
More on optimizing indexes: Index Cookbook

How do I improve this SQL query?

I have the following query:
select
`stories`.*
from
`stories`
inner join `communities` on `stories`.`community_id` = `communities`.`id`
inner join `communities_followers` on `communities`.`id` = `communities_followers`.`community_id`
where
`is_published` = 1
and `communities_followers`.`user_id` = 1
and `communities_followers`.`status` = 1
order by
`stories`.`created_at` desc
limit
20 offset 0
With an single index on communities_followers.user_id and a compound index on ['user_id', 'status']
When doing an explain on the query, this is the result:
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------------------+------------+--------+--------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------+---------+---------------------------------------------+------+----------+----------------------------------------------+
| 1 | SIMPLE | communities_followers | NULL | ref | communities_followers_user_id_index,communities_followers_community_id_index,communities_followers_community_id_user_id_status_index | communities_followers_user_id_index | 4 | const | 77 | 10.00 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | communities | NULL | eq_ref | PRIMARY,communities_id_default_community_index | PRIMARY | 4 | grepless.communities_followers.community_id | 1 | 100.00 | Using index |
| 1 | SIMPLE | stories | NULL | ref | stories_community_id_index | stories_community_id_index | 4 | grepless.communities_followers.community_id | 3968 | 100.00 | NULL |
+----+-------------+-----------------------+------------+--------+--------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------+---------+---------------------------------------------+------+----------+----------------------------------------------+
Explain
What else can I do to improve this? communities_followers does contain a ton of records.
Stories table:
CREATE TABLE `stories` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`user_id` int unsigned NOT NULL,
`publisher_id` int unsigned DEFAULT NULL,
`community_id` int unsigned NOT NULL,
`content_type_id` int unsigned NOT NULL DEFAULT '10',
`title` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`description` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
`meta_description` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
`score` int NOT NULL DEFAULT '0',
`score_alternate` int NOT NULL DEFAULT '0',
`views` int NOT NULL DEFAULT '0',
`url` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`source_url` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
`picture` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`meta` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
`embed` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
`picture_original` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`picture_huge` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`picture_big` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`picture_small` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`picture_extra` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`slug` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`is_published` tinyint(1) NOT NULL DEFAULT '1',
`is_summarized` tinyint(1) NOT NULL DEFAULT '0',
`language` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`show_in_feed` tinyint(1) NOT NULL DEFAULT '1',
`is_pinned` tinyint(1) NOT NULL DEFAULT '0',
`has_pictures_localized` tinyint(1) NOT NULL DEFAULT '0',
`has_pictures_optimized` tinyint(1) NOT NULL DEFAULT '0',
`has_audio` tinyint(1) NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL,
`author` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`comments_count` bigint NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `stories_created_at_index` (`created_at`),
KEY `stories_user_id_index` (`user_id`),
KEY `stories_community_id_index` (`community_id`),
KEY `stories_content_type_id_index` (`content_type_id`),
KEY `stories_score_index` (`score`),
KEY `stories_slug_index` (`slug`),
KEY `stories_show_in_feed_index` (`show_in_feed`),
KEY `stories_publisher_id_index` (`id`),
KEY `stories_deleted_at_is_published_content_type_id_index` (`deleted_at`,`is_published`,`content_type_id`),
KEY `stories_publisher_id_deleted_at_index` (`publisher_id`,`deleted_at`),
KEY `stories_is_published_deleted_at_index` (`is_published`,`deleted_at`,`community_id`) USING BTREE
) ENGINE=MyISAM AUTO_INCREMENT=952978 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
communities table
communities | CREATE TABLE `communities` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`user_id` int unsigned DEFAULT NULL,
`name` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
`slug` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`header` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`color` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`background` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`background_cover` tinyint(1) NOT NULL DEFAULT '1',
`picture_big` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`picture_small` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`has_pictures_optimized` tinyint(1) NOT NULL DEFAULT '0',
`default_community` tinyint(1) NOT NULL DEFAULT '0',
`is_popular` tinyint(1) NOT NULL DEFAULT '0',
`status` smallint NOT NULL DEFAULT '1',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`stories_count` bigint NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `communities_user_id_index` (`user_id`),
KEY `communities_name_index` (`name`),
KEY `communities_slug_index` (`slug`),
KEY `communities_status_index` (`status`),
KEY `communities_default_community_index` (`default_community`),
KEY `communities_id_default_community_index` (`id`,`default_community`)
) ENGINE=MyISAM AUTO_INCREMENT=183 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci |
Communities followers table
communities_followers | CREATE TABLE `communities_followers` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`user_id` int unsigned NOT NULL,
`community_id` int unsigned NOT NULL,
`status` smallint NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `communities_followers_user_id_index` (`user_id`),
KEY `communities_followers_community_id_index` (`community_id`),
KEY `communities_followers_community_id_user_id_status_index` (`community_id`,`user_id`,`status`)
) ENGINE=MyISAM AUTO_INCREMENT=326484 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci |
SELECT
`stories`.*
FROM
`communities_followers`
INNER JOIN
`stories`
ON `stories`. `community_id` = `communities_followers`.`community_id`
WHERE
`communities_followers`.`user_id` = 1
AND `communities_followers`.`status` = 1
AND `stories`.`is_published` = 1
ORDER BY
`stories`.`created_at` DESC
LIMIT
20 OFFSET 0
Then to speed up the initial filter index...
communities_followers(user_id, status, community_id)
Then to speed up the join AND the ordering and limit...
stories(community_id, is_published, created_at)
OR
stories(is_published, created_at, community_id)
But I'd use EXISTS, as it will still work if you query against multiple users (without causing duplication in the results)...
SELECT
`stories`.*
FROM
`stories`
WHERE
`stories`.`is_published` = 1
AND EXISTS (
SELECT *
FROM `communities_followers`
WHERE `communities_followers`.`user_id` = 1
AND `communities_followers`.`status` = 1
AND `communities_followers`.`community_id` = `stories`.`community_id`
)
ORDER BY
`stories`.`created_at` DESC
LIMIT
20 OFFSET 0

mysql query tree struct with mptt slower than 2D relational table

My business scenario is shown in the figure above. A user can create multiple products, a product can have multiple modules, and a module can have multiple parameters. The parameters include variable types, variable names, and variable values.
Before starting I thought the query speed of mptt was better than 2D relational table, but the result is completely opposite.
I now have two data table designs.
Option One:
CREATE TABLE `products` (
`product_id` bigint(20) NOT NULL AUTO_INCREMENT,
`product_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`cfdversion` bigint(20) NULL DEFAULT NULL,
`product_info` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`is_activated` tinyint(1) NULL DEFAULT NULL,
PRIMARY KEY (`product_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 226 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
CREATE TABLE `person_param` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`product_id` bigint(20) NULL DEFAULT NULL,
`param_name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`var_type` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`var_value` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`var_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`is_activated` tinyint(1) NULL DEFAULT 1,
`compute_value` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`module_name` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `product_id`(`product_id`) USING BTREE,
CONSTRAINT `person_param_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`product_id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 19 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
I connect the id of the product information with the parameter table.
Option two:
products table is same just table name is different.
person_paramlike this:
CREATE TABLE `mptt_param` (
`node_id` bigint(20) NOT NULL AUTO_INCREMENT,
`node_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`lft` bigint(20) NOT NULL,
`rgt` bigint(20) NOT NULL,
`node_level` bigint(20) NOT NULL,
PRIMARY KEY (`node_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
I added 200 products with 3 modules per product and 10 parameters per module.
option 1's sql statement:SELECT * from `products` a RIGHT JOIN `person_param` b ON a.product_id=b.product_id WHERE a.product_id=246;
option 2's sql statement:SELECT * FROM mptt_param WHERE lft>=(SELECT lft FROM mptt_param WHERE node_name='246') AND rgt<=(SELECT rgt FROM mptt_param WHERE node_name='246') ;
I don't know what the problem is, hope you can give me some advice

mariadb - join/order by clauses cause slow query

have a small blog site, host provider runs Server version: 5.5.5-10.3.34-MariaDB-cll-lve MariaDB Server. The last weeks i noticed fetching the posts is slower, and it turns out the query now takes 6 seconds.
the in question query:
SELECT * FROM POSTS
JOIN POST_CATEGORIES on POST_CATEGORIES.CATEG_ID = POSTS.POST_CATEGORY
LEFT JOIN USERS on POSTS.POST_USERNAME = USERS.USER_USERNAME
WHERE POST_STATUS = 'ENA'
AND POST_IS_PUBLIC = 'Y'
ORDER BY POST_IS_STICKY DESC, (TIMESTAMPDIFF(SECOND, POST_CREATION_DATE, now())-(POST_LIKES-POST_DISLIKES)*3600) ASC
LIMIT 0, 12;
if i remove the order by the query time drops to 0.04 sec, but obviously it is needed for proper sorting. Same effect happens if i remove the left join to USERS, but i would like to focus on the order by clause.
Table rows:
POSTS: 62K
POST_CATEGORIES: 20
USERS: 180
Interesting finding:
There are already indexes (primary/unique/foreign key) on the tables, but not on the columns involved, for the simplicity i am not adding them here.
But i did try adding the index:
INDEX `POSTS_OPTIMIZING_INDEX` (`POST_CREATION_DATE`, `POST_LIKES`, `POST_DISLIKES`) USING BTREE,
and FORCE INDEX (POSTS_OPTIMIZING_INDEX) and saw something interesting:
on the MariaDB, literally no difference.
on mysql community server Server version: 5.7.20 MySQL Community Server (GPL) that i have home, the query duration from 6 seconds went down to 0.12sec, which is definitely acceptable.
Any suggestions on optimizing the SQL, or tuning mariadb?
EDIT
the table definitions:
POSTS:
CREATE TABLE `POSTS` (
`POST_ID` int(11) NOT NULL AUTO_INCREMENT,
`POST_URL_ID` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`POST_TYPE` varchar(3) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`POST_CATEGORY` int(3) DEFAULT NULL,
`POST_IS_PUBLIC` varchar(1) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`POST_IS_STICKY` varchar(1) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`POST_FREE_TEXT` varchar(5000) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`POST_IMAGE_FILE` varchar(1000) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`POST_VIDEO_URL` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`POST_IS_FROM_USER` varchar(1) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`POST_USERNAME` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`POST_CREATION_DATE` timestamp NOT NULL DEFAULT current_timestamp(),
`POST_STATUS` varchar(3) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'ENA',
`POST_LIKES` int(5) DEFAULT 0,
`POST_DISLIKES` int(5) DEFAULT 0,
`POST_ADDITIONAL_INFO` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`POST_FB_OBJ_ID` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`POST_FB_OBJ_TYPE` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`POST_FB_IMG_LAST_CHECKED` timestamp NULL DEFAULT NULL,
`POST_FB_IMG_LAST_CHECK_RESULT` varchar(3) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`POST_TWITTER_OBJ_ID` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`POST_ID`),
UNIQUE KEY `POST_URL_ID` (`POST_URL_ID`),
KEY `FK_POSTS_POST_CATEGORIES` (`POST_CATEGORY`),
CONSTRAINT `FK_POSTS_POST_CATEGORIES` FOREIGN KEY (`POST_CATEGORY`) REFERENCES `POST_CATEGORIES` (`CATEG_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=62529 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
POST_CATEGORIES:
CREATE TABLE `POST_CATEGORIES` (
`CATEG_ID` int(11) NOT NULL AUTO_INCREMENT,
`CATEG_URL_NAME` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
`CATEG_NAME` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`CATEG_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=51 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
USERS:
CREATE TABLE `USERS` (
`USER_ID` int(11) NOT NULL AUTO_INCREMENT,
`USER_MAIL_ADDRESS` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
`USER_USERNAME` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
`USER_PASSWORD` varchar(60) COLLATE utf8_unicode_ci DEFAULT NULL,
`USER_CREATION_DATE` timestamp NULL DEFAULT current_timestamp(),
`USER_LAST_LOGIN_DATE` timestamp NULL DEFAULT NULL,
`USER_FROM_FB` varchar(1) COLLATE utf8_unicode_ci DEFAULT NULL,
`USER_FB_MAIL_ADDRESS` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
`USER_FB_FULL_NAME` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
`USER_SPECIAL` varchar(1) COLLATE utf8_unicode_ci DEFAULT NULL,
`USER_ACCT_STATUS` varchar(3) COLLATE utf8_unicode_ci DEFAULT 'ENA',
`USER_POSTS_COUNT` int(5) DEFAULT 0,
`USER_NAME` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
`USER_QUOTE` varchar(200) COLLATE utf8_unicode_ci DEFAULT NULL,
`USER_ABOUT_MYSELF` varchar(500) COLLATE utf8_unicode_ci DEFAULT NULL,
`USER_WEBSITE` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
`USER_FB` varchar(150) COLLATE utf8_unicode_ci DEFAULT NULL,
`USER_TWITTER` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
`USER_LOCATION` varchar(150) COLLATE utf8_unicode_ci DEFAULT NULL,
`USER_BIRTHDAY` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL,
`USER_PROFILE_PIC` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
`USER_PROFILE_PIC_FROM_FB` varchar(1) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`USER_ID`),
UNIQUE KEY `USER_USERNAME` (`USER_USERNAME`)
) ENGINE=InnoDB AUTO_INCREMENT=188 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
the EXPLAIN:
mysql> EXPLAIN
-> SELECT * FROM POSTS
-> JOIN POST_CATEGORIES on POST_CATEGORIES.CATEG_ID = POSTS.POST_CATEGORY
-> LEFT JOIN USERS on POSTS.POST_USERNAME = USERS.USER_USERNAME
-> WHERE POST_STATUS = 'ENA'
-> AND POST_IS_PUBLIC = 'Y'
-> ORDER BY POST_IS_STICKY DESC, (TIMESTAMPDIFF(SECOND, POST_CREATION_DATE, now())-(POST_LIKES-POST_DISLIKES)*3600) ASC
-> LIMIT 0, 12;
+------+-------------+-----------------+--------+--------------------------+---------+---------+--------------------------------+-------+-------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-----------------+--------+--------------------------+---------+---------+--------------------------------+-------+-------------------------------------------------+
| 1 | SIMPLE | POSTS | ALL | FK_POSTS_POST_CATEGORIES | NULL | NULL | NULL | 61407 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | POST_CATEGORIES | eq_ref | PRIMARY | PRIMARY | 4 | MY_DB.POSTS.POST_CATEGORY | 1 | |
| 1 | SIMPLE | USERS | ALL | NULL | NULL | NULL | NULL | 178 | Using where; Using join buffer (flat, BNL join) |
+------+-------------+-----------------+--------+--------------------------+---------+---------+--------------------------------+-------+-------------------------------------------------+
3 rows in set (0.03 sec)
mysql>
and removing the 2 joins:
mysql> EXPLAIN
-> SELECT * FROM POSTS
-> WHERE POST_STATUS = 'ENA'
-> AND POST_IS_PUBLIC = 'Y'
-> ORDER BY POST_IS_STICKY DESC, (TIMESTAMPDIFF(SECOND, POST_CREATION_DATE, now())-(POST_LIKES-POST_DISLIKES)*3600) ASC
-> LIMIT 0, 12;
+------+-------------+-------+------+---------------+------+---------+------+-------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+------+---------------+------+---------+------+-------+-----------------------------+
| 1 | SIMPLE | POSTS | ALL | NULL | NULL | NULL | NULL | 61407 | Using where; Using filesort |
+------+-------------+-------+------+---------------+------+---------+------+-------+-----------------------------+
1 row in set (0.02 sec)
mysql>
TIA
"Explode-implode" syndrome. Rewrite the query to get the 12 rows first, then JOIN to the other tables.
SELECT ...
FROM ( SELECT user_id,
is_sticky,
(TIMESTAMPDIFF(SECOND, POST_CREATION_DATE, now()) -
(POST_LIKES-POST_DISLIKES)*3600) AS metric
FROM POSTS
WHERE POST_STATUS = 'ENA'
AND POST_IS_PUBLIC = 'Y'
ORDER BY
POST_IS_STICKY DESC, metric ASC
LIMIT 0, 12
) AS x
JOIN POSTS AS p USING(user_id)
JOIN POST_CATEGORIES AS pc ON pc.CATEG_ID = p.POST_CATEGORY
LEFT JOIN USERS AS u ON p.POST_USERNAME = u.USER_USERNAME
ORDER BY x.POST_IS_STICKY DESC, x.metric ASC
Replace the elipsis with the columns you really need.
Yes, POSTS is looked into a second time, but only for 12 rows, and by the PK. Similarly, the other rows are reached into only 12 times.
Yes, the ORDER BY needs to be repeated. The LIMIT may need repeating if the JOIN creates extra rows (eg, multiple categories).
Consider using
SELECT ...
( SELECT GROUP_CONCAT(CATEG_NAME) FROM POST_CATEGORIES
WHERE categ_id = p.post_categories ) AS Categories,
...
instead of JOINing to POST_CATEGORIES, especially if there are multiple categories.
This may help with performance, but only if it is selective enough:
INDEX(post_tatus, post_is_public)

Query too big and slow on multiple tables, how to optimize that?

I have a store system with orders, which have status for both the order and it's items. I have these six tables:
Order (pedido).
Order item (pedido_item).
Order status (status_pedido).
Order item status (status).
Order status log (pedido_status_pedido).
Order item status log (pedido_item_status).
So, when I have to update the status from the order, I insert a new row on pedido_status_pedido and I update the status_pedido_id on table pedido. Same goes for items.
One order status have many item status associated, for example, the order status "Pending" is related to the item statuses "Waiting for file", "File with error" and "File approved".
The current order status is based on it's current items status, on the most "delayed" status, for example, and item "File with error" is behind an "Finished production" item. For that there are ordenation columns both on order and item status.
So, if I have 3 items on an order, with the status "File with error", "In production", "Finished production", the order status is "Pending", because it's the correspondent order status for the order item status "File with error", which is further behind.
The problem is when I have to update an specific order status. I came up with a query that got so complex I need to SET SQL_BIG_SELECTS=1 to make it run. Obviously the query is so slow it's making my entire site slow (it's called every 10 minutes, for a large number of orders)
Here is my query, explained:
INSERT INTO pedido_status_pedido (pedido_id, status_pedido_id) VALUES ({$this->pedido_id}, ( --Insert into order status log the id, and the status id
SELECT sp.status_pedido_id FROM `status` s --Subquery for the order status id, get it from the relationship inside the item status
LEFT JOIN status_pedido sp ON sp.status_pedido_id = s.status_pedido_id
WHERE s.status_id = ( --Subquery for the further behind item status
SELECT s.status_id FROM pedido_item_status p1
LEFT JOIN `status` s ON s.status_id = p1.status_id
LEFT JOIN pedido_item ON pedido_item.pedido_item_id = p1.pedido_item_id
INNER JOIN ( --Get the LATEST status of each item and compare
SELECT MAX( si.sta_ordem ) AS maxordem, pedido_item_id FROM pedido_item_status pi
LEFT JOIN status si ON pi.status_id = si.status_id
WHERE pi.excluido IS NULL AND pi.pedido_id = {$this->pedido_id}
GROUP BY pi.pedido_item_id
) p2 ON ( s.sta_ordem = p2.maxordem ) AND p1.excluido IS NULL AND p1.pedido_item_id = p2.pedido_item_id
WHERE p1.pedido_id = {$this->pedido_id}
ORDER BY s.sta_ordem ASC
LIMIT 1
)
)
And here are the tables definitions (sorry, it's a bit large):
CREATE TABLE `pedido` (
`pedido_id` int(11) NOT NULL AUTO_INCREMENT,
`cliente_id` int(11) NULL DEFAULT NULL,
`forma_envio_id` int(11) NULL DEFAULT NULL,
`balcao_retirada_id` int(11) NULL DEFAULT NULL,
`ped_responsavel_retirada` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`ped_codigo_rastreio` varchar(50) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`ped_prazo_entrega` int(11) NULL DEFAULT NULL,
`ped_prazo_producao` int(11) NULL DEFAULT 0,
`ped_data` datetime(0) NULL DEFAULT '0000-00-00 00:00:00',
`ped_data_producao` date NULL DEFAULT NULL,
`ped_data_entrega` date NULL DEFAULT NULL,
`ped_notificado` char(1) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT 'N',
`ped_transacao_pagarme` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`ped_cmd` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT '',
`ped_data_confirmacao_pagamento` datetime(0) NULL DEFAULT NULL,
`forma_pagamento_id` int(11) NULL DEFAULT NULL,
`ped_vencimento_boleto` date NULL DEFAULT NULL,
`ped_comprovante` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`ped_pago` char(1) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT 'N',
`status_id` int(11) NOT NULL DEFAULT 0,
`status_pedido_id` int(11) NOT NULL DEFAULT 0,
`ped_valor_adicionais` decimal(10, 2) NULL DEFAULT NULL,
`ped_valor_frete` decimal(10, 2) NULL DEFAULT NULL,
`ped_valor_produtos` decimal(10, 2) NULL DEFAULT 0.00,
`ped_valor_desconto` decimal(10, 2) NULL DEFAULT 0.00,
`ped_valor_total` decimal(10, 2) NULL DEFAULT 0.00,
`excluido` datetime(0) NULL DEFAULT NULL,
`cadastrado` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP,
`atualizado` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0),
PRIMARY KEY (`pedido_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 15876 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Compact;
CREATE TABLE `pedido_item` (
`pedido_item_id` int(11) NOT NULL AUTO_INCREMENT,
`pedido_id` int(11) NULL DEFAULT NULL,
`pei_indice` int(11) NULL DEFAULT NULL,
`produto_id` int(11) NULL DEFAULT NULL,
`produto_variacao_id` int(11) NULL DEFAULT NULL,
`produto_preco_id` int(11) NULL DEFAULT NULL,
`tipo_arquivo_id` int(11) NULL DEFAULT NULL,
`pei_arquivo` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`pei_arquivo_nome` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`pei_nome` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`pei_quantidade` int(11) NULL DEFAULT NULL,
`pei_valor_unitario` decimal(10, 2) NULL DEFAULT NULL,
`pei_valor_total` decimal(10, 2) NULL DEFAULT NULL,
`pei_valor_frete` decimal(10, 2) NULL DEFAULT NULL,
`pei_codigo_preco` int(11) NULL DEFAULT NULL,
`pei_codigo_interno` varchar(40) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`status_id` int(11) NOT NULL DEFAULT 0,
`excluido` timestamp(0) NULL DEFAULT NULL,
`cadastrado` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP,
`atualizado` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0),
`pei_producao_finalizada` char(1) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT 'N',
`pei_arquivo_erro` char(1) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
PRIMARY KEY (`pedido_item_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 17528 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Compact;
CREATE TABLE `pedido_item_status` (
`pedido_item_status_id` int(11) NOT NULL AUTO_INCREMENT,
`pedido_id` int(11) NOT NULL,
`pedido_item_id` int(11) NOT NULL,
`status_id` int(11) NOT NULL,
`pis_texto` text CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL,
`pis_notificar_cliente` char(1) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT 'S',
`excluido` datetime(0) NULL DEFAULT NULL,
`cadastrado` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP,
`atualizado` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0),
PRIMARY KEY (`pedido_item_status_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 35743 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Compact;
CREATE TABLE `pedido_status_pedido` (
`pedido_status_pedido_id` int(11) NOT NULL AUTO_INCREMENT,
`pedido_id` int(11) NULL DEFAULT NULL,
`status_pedido_id` int(11) NULL DEFAULT NULL,
`psp_notificar_cliente` char(1) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT 'S',
`psp_texto` text CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL,
`excluido` timestamp(0) NULL DEFAULT NULL,
`cadastrado` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP,
`atualizado` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0),
PRIMARY KEY (`pedido_status_pedido_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 38216 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Compact;
CREATE TABLE `status` (
`status_id` int(11) NOT NULL AUTO_INCREMENT,
`sta_nome` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`sta_observacao` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`sta_ordem` int(11) NULL DEFAULT NULL,
`status_pedido_id` int(11) NULL DEFAULT NULL,
`excluido` datetime(0) NULL DEFAULT NULL,
`cadastrado` datetime(0) NULL DEFAULT NULL,
`atualizado` datetime(0) NULL DEFAULT NULL,
`sta_cor` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`sta_icon` varchar(50) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`sta_alert` varchar(50) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
PRIMARY KEY (`status_id`) USING BTREE,
INDEX `idx_1`(`excluido`, `status_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Compact;
INSERT INTO `status` VALUES (1, 'Aguardando pagamento', NULL, 1, 1, NULL, NULL, NULL, '#CACACA', 'fa-clock-o', 'alert-warning');
INSERT INTO `status` VALUES (2, 'Aguardando arquivo', NULL, 2, 2, NULL, NULL, NULL, '#FAC08C', 'fa-file-image-o', 'alert-warning');
INSERT INTO `status` VALUES (3, 'Arquivo em análise', NULL, 4, 2, NULL, NULL, NULL, '#8CBCFA', 'fa-spinner', 'alert-info');
INSERT INTO `status` VALUES (4, 'Produção finalizada', NULL, 9, 4, NULL, NULL, NULL, '#DCBCA5', 'check-square-o', 'alert-info');
INSERT INTO `status` VALUES (5, 'Arquivo com erro', NULL, 5, 2, NULL, NULL, NULL, '#FF8C8C', 'fa-exclamation-circle', 'alert-danger');
INSERT INTO `status` VALUES (6, 'Em produção', NULL, 7, 3, NULL, NULL, NULL, '#8CBCFA', 'fa-cogs', 'alert-info');
INSERT INTO `status` VALUES (7, 'Em transporte', NULL, 11, 5, NULL, NULL, NULL, '#DCA5A5', 'fa-truck', 'alert-info');
INSERT INTO `status` VALUES (8, 'Entregue', NULL, 12, 6, NULL, NULL, NULL, '#5CCE90', 'fa-check-circle-o', 'alert-success');
INSERT INTO `status` VALUES (9, 'Cancelado', NULL, 13, 7, NULL, NULL, NULL, '#FF7979', 'fa-times-circle-o', 'alert-danger');
INSERT INTO `status` VALUES (10, 'Pronto para retirada', NULL, 10, 4, NULL, NULL, NULL, '#FFD24D', 'fa-check-circle-o', 'alert-info');
INSERT INTO `status` VALUES (11, 'Produto com defeito', NULL, 8, 3, NULL, NULL, NULL, '#FF8C8C', 'fa-exclamation-circle', 'alert-danger');
INSERT INTO `status` VALUES (12, 'Arquivo aprovado', NULL, 6, 20, NULL, NULL, NULL, '#C0ED85', 'fa-check-circle-o', 'alert-info');
INSERT INTO `status` VALUES (13, 'Em Espera', NULL, 3, 1, NULL, NULL, NULL, '#8CBCFA', 'fa-spinner', 'alert-info');
CREATE TABLE `status_pedido` (
`status_pedido_id` int(11) NOT NULL AUTO_INCREMENT,
`stp_nome` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`stp_observacao` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`stp_ordem` int(11) NULL DEFAULT NULL,
`excluido` datetime(0) NULL DEFAULT NULL,
`cadastrado` datetime(0) NULL DEFAULT NULL,
`atualizado` datetime(0) NULL DEFAULT NULL,
`stp_cor` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`stp_icon` varchar(50) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`stp_alert` varchar(50) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
PRIMARY KEY (`status_pedido_id`) USING BTREE,
INDEX `idx_1`(`excluido`, `status_pedido_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 21 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Compact;
INSERT INTO `status_pedido` VALUES (1, 'Aguardando pagamento', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `status_pedido` VALUES (2, 'Pendente', NULL, 2, NULL, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `status_pedido` VALUES (3, 'Em produção', NULL, 4, NULL, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `status_pedido` VALUES (4, 'Pronto', NULL, 5, NULL, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `status_pedido` VALUES (5, 'Em transporte', NULL, 6, NULL, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `status_pedido` VALUES (6, 'Entregue', NULL, 7, NULL, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `status_pedido` VALUES (7, 'Cancelado', NULL, 8, NULL, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `status_pedido` VALUES (20, 'Aprovado', NULL, 3, NULL, NULL, NULL, NULL, NULL, NULL);
The query itself works fine, but I need a way to make it faster. Perhaps it would be better if I break it down on smaller queries? Or I'm using too much unnecessary data?
EDIT:
Following the answer, the new query now looks like this:
SELECT s.status_pedido_id AS status_pedido_atual, p.status_pedido_id AS status_pedido_anterior FROM `status` s
LEFT JOIN pedido p ON p.pedido_id = {$_POST['pedido_id']}
WHERE s.sta_ordem =
(
SELECT MAX( si.sta_ordem ) AS max_ordem
FROM pedido_item_status pis
LEFT JOIN `status` si ON si.status_id = pis.status_id
WHERE pis.pedido_id = {$_POST['pedido_id']}
AND pis.excluido IS NULL
GROUP BY pis.pedido_item_id
ORDER BY max_ordem ASC
LIMIT 1
)
And I insert the order status log on another query using the result. I got much faster. Also the were other problems with the logic of the whole process I solved.
Well, my guess is to run EXPLAIN ... and run it for your INSERT ... query
+---+----------+----------------------+--+--------+-------------+-------------+--+--+---+--------+----------------------------------------------+
| 1 | INSERT | pedido_status_pedido | | ALL | | | | | | | |
+---+----------+----------------------+--+--------+-------------+-------------+--+--+---+--------+----------------------------------------------+
| 2 | SUBQUERY | | | | | | | | | | no matching row in const table |
| 3 | SUBQUERY | p1 | | ALL | | | | | 1 | 100.00 | Using where; Using temporary; Using filesort |
| 3 | SUBQUERY | s | | eq_ref | PRIMARY | PRIMARY | | | 1 | 100.00 | Using where |
| 3 | SUBQUERY | pedido_item | | eq_ref | PRIMARY | PRIMARY | | | 1 | 100.00 | Using index |
| 3 | SUBQUERY | <derived4> | | ref | <auto_key0> | <auto_key0> | | | 2 | 100.00 | Using index |
| 4 | DERIVED | pi | | ALL | | | | | 1 | 100.00 | Using where; Using temporary; Using filesort |
| 4 | DERIVED | si | | eq_ref | PRIMARY | PRIMARY | | | 1 | 100.00 | |
+---+----------+----------------------+--+--------+-------------+-------------+--+--+---+--------+----------------------------------------------+
I ran with all your data and found that (possibly) you're having 3 of 7 subqueries with no possible key for searching
I would start de-compositing your query and checking them with EXPLAIN for possible bottlenecks.
Otherwise I ran this query (previosly I've filled some fake data in tables of course) and it performed for 0.016sec. That's nuisance. And that's why another guess is in backend code AND/OR volume of your data / your server capabilities.
So, at the end:
Check query with EXPLAIN
Decompose your complex query, check inner SELECTs for possible bottleneck
Change your logic to avoid bottlenecks - break complex queries into simple ones. For example, you can use trigger and set variable to store MAX( si.sta_ordem ) without recalculating it for every query. If you have constantly running service - you can use temporary tables for caching purposes.
If your MySQL query is ok, check you backend code (PHP). Maybe you're having a deadlock here.
Check you data volume.
If it's possible, reboot server and check how it's performing under load. Performance counters are available for all OSes