I'm running into an issue with my MySQL database and I would like to know if any of you could please shed some light.
Background:
I currently have entire schema configured with InnoDB. This database is currently being used by a real-time application that generate thousands (close to a million) of records per day in one of the tables (i.e. attempt). We heavily query this table all day long to parse the data and store in a separate table in a JSON format (using the JSON type introduced on version 5.7).
Issue:
For some reason it looks like that the MySQL optimizer is not using the correct indexes (on this case the most important index exists on "created_at"). Keep in mind that we were using this same exact query for almost 2 months but all of a sudden it started to perform very poorly. Below you can find the details:
Query:
SELECT
att.id AS attempt_id,
app.id AS application_id,
ca.id AS campaign_id,
IFNULL(aff.private, 0) AS affiliate_id,
app.ping_tree_id AS ping_tree_id,
att.request_timeout AS request_timeout,
att.response_time AS response_time,
type.state AS state,
att.approved AS approved,
att.price AS price,
att.created_at AS created_at
FROM
attempt att
JOIN
application app ON (app.id = att.application_id)
JOIN
loan_application type ON (type.id = app.id)
JOIN
campaign_payout_rule cpr ON (cpr .id = att.campaign_payout_rule_id)
JOIN
campaign ca ON (ca.id = cpr.campaign_id)
LEFT JOIN
affiliate aff ON (aff.id = app.affiliate_id)
WHERE
att.created_at BETWEEN '2017-08-24 07:31:31' AND '2017-08-24 11:31:31'
AND att.parsed_at IS NULL
AND (
att.parse_status = '1'
OR att.parse_status = '11'
)
AND app.test = 0
AND ca.id = '31'
UNION
SELECT
att.id AS attempt_id,
app.id AS application_id,
ca.id AS campaign_id,
IFNULL(aff.private, 0) AS affiliate_id,
app.ping_tree_id AS ping_tree_id,
att.request_timeout AS request_timeout,
att.response_time AS response_time,
type.state AS state,
att.approved AS approved,
att.price AS price,
att.created_at AS created_at
FROM
attempt att
JOIN
application app ON (app.id = att.application_id)
JOIN
loan_application_external_lookup type ON (type.id = app.id)
JOIN
campaign_payout_rule cpr ON (cpr.id = att.campaign_payout_rule_id)
JOIN
campaign ca ON (ca.id = cpr.campaign_id)
LEFT JOIN
affiliate aff ON (aff.id = app.affiliate_id)
WHERE
att.created_at BETWEEN '2017-08-24 07:31:31' AND '2017-08-24 11:31:31'
AND att.parsed_at IS NULL
AND (
att.parse_status = '1'
OR att.parse_status = '11'
)
AND app.test = 0
AND ca.id = '31'
ORDER BY
approved=1 DESC, created_at ASC
Indexes:
MySQL [db]> show index from attempt;
+---------+------------+----------------------+--------------+-------------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+---------+------------+----------------------+--------------+-------------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| attempt | 0 | PRIMARY | 1 | id | A | 12528955 | NULL | NULL | | BTREE | | |
| attempt | 1 | IDX_18EC02663E030ACD | 1 | application_id | A | 1426571 | NULL | NULL | | BTREE | | |
| attempt | 1 | IDX_18EC02669E82DDEA | 1 | integration_id | A | 8495 | NULL | NULL | | BTREE | | |
| attempt | 1 | IDX_18EC0266FA64550D | 1 | campaign_payout_rule_id | A | 10319 | NULL | NULL | | BTREE | | |
| attempt | 1 | IDX_18EC0266E4640ADF | 1 | campaign_credential_id | A | 11885 | NULL | NULL | | BTREE | | |
| attempt | 1 | IDX_18EC0266A354F9DC | 1 | tier_id | A | 14 | NULL | NULL | | BTREE | | |
| attempt | 1 | IDX_18EC02661500F99B | 1 | request_method | A | 1 | NULL | NULL | | BTREE | | |
| attempt | 1 | IDX_18EC02667C57D81D | 1 | approved | A | 1 | NULL | NULL | | BTREE | | |
| attempt | 1 | IDX_18EC02668B8E8428 | 1 | created_at | A | 3030286 | NULL | NULL | | BTREE | | |
| attempt | 1 | IDX_18EC02665552D714 | 1 | parsed_at | A | 1284185 | NULL | NULL | YES | BTREE | | |
| attempt | 1 | IDX_18EC0266720357E8 | 1 | parse_status | A | 1 | NULL | NULL | | BTREE | | |
+---------+------------+----------------------+--------------+-------------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
Explain:
MySQL [db]> explain extended SELECT att.id AS attempt_id, app.id AS application_id, ca.id AS campaign_id, IFNULL(aff.private, 0) AS affiliate_id, app.ping_tree_id AS ping_tree_id, att.request_timeout AS request_timeout, att.response_time AS response_time, type.state AS state, att.approved AS approved, att.price AS price, att.created_at AS created_at FROM attempt att JOIN application app ON (app.id = att.application_id) JOIN loan_application type ON (type.id = app.id) JOIN campaign_payout_rule cpr ON (cpr .id = att.campaign_payout_rule_id) JOIN campaign ca ON (ca.id = cpr.campaign_id) LEFT JOIN affiliate aff ON (aff.id = app.affiliate_id) WHERE att.created_at BETWEEN '2017-08-24 07:31:31' AND '2017-08-24 11:31:31' AND att.parsed_at IS NULL AND ( att.parse_status = '1' OR att.parse_status = '11' ) AND app.test = 0 AND ca.id = '31' UNION SELECT att.id AS attempt_id, app.id AS application_id, ca.id AS campaign_id, IFNULL(aff.private, 0) AS affiliate_id, app.ping_tree_id AS ping_tree_id, att.request_timeout AS request_timeout, att.response_time AS response_time, type.state AS state, att.approved AS approved, att.price AS price, att.created_at AS created_at FROM attempt att JOIN application app ON (app.id = att.application_id) JOIN loan_application_external_lookup type ON (type.id = app.id) JOIN campaign_payout_rule cpr ON (cpr.id = att.campaign_payout_rule_id) JOIN campaign ca ON (ca.id = cpr.campaign_id) LEFT JOIN affiliate aff ON (aff.id = app.affiliate_id) WHERE att.created_at BETWEEN '2017-08-24 07:31:31' AND '2017-08-24 11:31:31' AND att.parsed_at IS NULL AND ( att.parse_status = '1' OR att.parse_status = '11' ) AND app.test = 0 AND ca.id = '31' ORDER BY approved=1 DESC, created_at ASC;
+----+--------------+------------+------------+--------+----------------------------------------------------------------------------------------------------------+----------------------+---------+-------------------------+------+----------+---------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+--------+----------------------------------------------------------------------------------------------------------+----------------------+---------+-------------------------+------+----------+---------------------------------+
| 1 | PRIMARY | ca | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Using index |
| 1 | PRIMARY | cpr | NULL | ref | PRIMARY,IDX_D5BDECA3F639F774 | IDX_D5BDECA3F639F774 | 4 | const | 1 | 100.00 | Using where; Using index |
| 1 | PRIMARY | att | NULL | ref | IDX_18EC02663E030ACD,IDX_18EC0266FA64550D,IDX_18EC02668B8E8428,IDX_18EC02665552D714,IDX_18EC0266720357E8 | IDX_18EC0266FA64550D | 4 | dois.cpr.id | 1226 | 0.00 | Using where |
| 1 | PRIMARY | type | NULL | eq_ref | PRIMARY | PRIMARY | 4 | dois.att.application_id | 1 | 100.00 | NULL |
| 1 | PRIMARY | app | NULL | eq_ref | PRIMARY,IDX_A45BDDC1D87F7E0C | PRIMARY | 4 | dois.att.application_id | 1 | 50.00 | Using where |
| 1 | PRIMARY | aff | NULL | eq_ref | PRIMARY | PRIMARY | 4 | dois.app.affiliate_id | 1 | 100.00 | NULL |
| 2 | UNION | ca | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Using index |
| 2 | UNION | cpr | NULL | ref | PRIMARY,IDX_D5BDECA3F639F774 | IDX_D5BDECA3F639F774 | 4 | const | 1 | 100.00 | Using where; Using index |
| 2 | UNION | att | NULL | ref | IDX_18EC02663E030ACD,IDX_18EC0266FA64550D,IDX_18EC02668B8E8428,IDX_18EC02665552D714,IDX_18EC0266720357E8 | IDX_18EC0266FA64550D | 4 | dois.cpr.id | 1226 | 0.00 | Using where |
| 2 | UNION | type | NULL | eq_ref | PRIMARY | PRIMARY | 4 | dois.att.application_id | 1 | 100.00 | NULL |
| 2 | UNION | app | NULL | eq_ref | PRIMARY,IDX_A45BDDC1D87F7E0C | PRIMARY | 4 | dois.att.application_id | 1 | 50.00 | Using where |
| 2 | UNION | aff | NULL | eq_ref | PRIMARY | PRIMARY | 4 | dois.app.affiliate_id | 1 | 100.00 | NULL |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary; Using filesort |
+----+--------------+------------+------------+--------+----------------------------------------------------------------------------------------------------------+----------------------+---------+-------------------------+------+----------+---------------------------------+
Once I change the query slightly with the index hint, you can see that the "range" type is added and the query works as expected:
MySQL [db]> explain extended SELECT att.id AS attempt_id, app.id AS application_id, ca.id AS campaign_id, IFNULL(aff.private, 0) AS affiliate_id, app.ping_tree_id AS ping_tree_id, att.request_timeout AS request_timeout, att.response_time AS response_time, type.state AS state, att.approved AS approved, att.price AS price, att.created_at AS created_at FROM attempt att USE INDEX (IDX_18EC02668B8E8428) JOIN application app ON (app.id = att.application_id) JOIN loan_application type ON (type.id = app.id) JOIN campaign_payout_rule cpr ON (cpr .id = att.campaign_payout_rule_id) JOIN campaign ca ON (ca.id = cpr.campaign_id) LEFT JOIN affiliate aff ON (aff.id = app.affiliate_id) WHERE att.created_at BETWEEN '2017-08-24 07:31:31' AND '2017-08-24 11:31:31' AND att.parsed_at IS NULL AND ( att.parse_status = '1' OR att.parse_status = '11' ) AND app.test = 0 AND ca.id = '31' UNION SELECT att.id AS attempt_id, app.id AS application_id, ca.id AS campaign_id, IFNULL(aff.private, 0) AS affiliate_id, app.ping_tree_id AS ping_tree_id, att.request_timeout AS request_timeout, att.response_time AS response_time, type.state AS state, att.approved AS approved, att.price AS price, att.created_at AS created_at FROM attempt att USE INDEX (IDX_18EC02668B8E8428) JOIN application app ON (app.id = att.application_id) JOIN loan_application_external_lookup type ON (type.id = app.id) JOIN campaign_payout_rule cpr ON (cpr.id = att.campaign_payout_rule_id) JOIN campaign ca ON (ca.id = cpr.campaign_id) LEFT JOIN affiliate aff ON (aff.id = app.affiliate_id) WHERE att.created_at BETWEEN '2017-08-24 07:31:31' AND '2017-08-24 11:31:31' AND att.parsed_at IS NULL AND ( att.parse_status = '1' OR att.parse_status = '11' ) AND app.test = 0 AND ca.id = '31' ORDER BY approved=1 DESC, created_at ASC;
+----+--------------+------------+------------+--------+------------------------------+----------------------+---------+-------------------------+-------+----------+---------------------------------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+--------+------------------------------+----------------------+---------+-------------------------+-------+----------+---------------------------------------------------------------------------+
| 1 | PRIMARY | ca | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Using index |
| 1 | PRIMARY | cpr | NULL | ref | PRIMARY,IDX_D5BDECA3F639F774 | IDX_D5BDECA3F639F774 | 4 | const | 1 | 100.00 | Using where; Using index |
| 1 | PRIMARY | att | NULL | range | IDX_18EC02668B8E8428 | IDX_18EC02668B8E8428 | 4 | NULL | 78308 | 0.00 | Using index condition; Using where; Using join buffer (Block Nested Loop) |
| 1 | PRIMARY | type | NULL | eq_ref | PRIMARY | PRIMARY | 4 | dois.att.application_id | 1 | 100.00 | NULL |
| 1 | PRIMARY | app | NULL | eq_ref | PRIMARY,IDX_A45BDDC1D87F7E0C | PRIMARY | 4 | dois.att.application_id | 1 | 50.00 | Using where |
| 1 | PRIMARY | aff | NULL | eq_ref | PRIMARY | PRIMARY | 4 | dois.app.affiliate_id | 1 | 100.00 | NULL |
| 2 | UNION | ca | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Using index |
| 2 | UNION | cpr | NULL | ref | PRIMARY,IDX_D5BDECA3F639F774 | IDX_D5BDECA3F639F774 | 4 | const | 1 | 100.00 | Using where; Using index |
| 2 | UNION | att | NULL | range | IDX_18EC02668B8E8428 | IDX_18EC02668B8E8428 | 4 | NULL | 78308 | 0.00 | Using index condition; Using where; Using join buffer (Block Nested Loop) |
| 2 | UNION | type | NULL | eq_ref | PRIMARY | PRIMARY | 4 | dois.att.application_id | 1 | 100.00 | NULL |
| 2 | UNION | app | NULL | eq_ref | PRIMARY,IDX_A45BDDC1D87F7E0C | PRIMARY | 4 | dois.att.application_id | 1 | 50.00 | Using where |
| 2 | UNION | aff | NULL | eq_ref | PRIMARY | PRIMARY | 4 | dois.app.affiliate_id | 1 | 100.00 | NULL |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary; Using filesort |
+----+--------------+------------+------------+--------+------------------------------+----------------------+---------+-------------------------+-------+----------+---------------------------------------------------------------------------+
A few notes:
- We're currently using AWS RDS
- As of right now, the attempt table has 15097025 records
- As of right now, the attempt table is using 50.8GB of storage
- The server currently has 550GB available of storage.
- I defragmented all the tables last night according to this article: https://dev.mysql.com/doc/refman/5.7/en/innodb-file-defragmenting.html (I assume is the same thing as OPTIMIZE?)
Questions:
- Is using Index hint a bad practice? We're currently using an ORM to build some of these queries which makes a little harder to add it if you decide to use their query builder. Although we can extend or change the queries to a raw format.
- Would you recommend me doing this query in any other way? Do you believe the number of joins could affect the performance?
Any thoughts? Thank you in advance.
Renato.