慢速运行SQL查询

时间:2018-01-14 15:00:28

标签: mysql query-optimization database-performance

我在mysql上运行这个SQL查询时遇到问题,运行5秒钟只能获取25条记录 - 非常糟糕;

select t.* from table1 t
left join table2 t2 on t.id=t2.transaction_id
where t2.transaction_id is null
and t.custom_type =0 limit 25

所有3个表都估计有1000万条记录。

受影响的表格的结构;

table1 ;
+---------------------+--------------+------+-----+-------------------+----------------+
| Field               | Type         | Null | Key | Default           | Extra          |
+---------------------+--------------+------+-----+-------------------+----------------+
| id                  | int(11)      | NO   | PRI | NULL              | auto_increment |
| loan_application_id | int(11)      | YES  | MUL | NULL              |                |
| loan_repayment_id   | int(11)      | YES  | MUL | NULL              |                |
| person_id           | int(11)      | YES  | MUL | NULL              |                |
| direction           | tinyint(4)   | NO   |     | NULL              |                |
| amount              | float        | NO   |     | NULL              |                |
| sender_phone        | varchar(32)  | YES  | MUL | NULL              |                |
| recipient_phone     | varchar(32)  | YES  | MUL | NULL              |                |
| sender_name         | varchar(128) | YES  |     | NULL              |                |
| recipient_name      | varchar(128) | YES  |     | NULL              |                |
| date_time           | datetime     | NO   | MUL | NULL              |                |
| local_date_time     | datetime     | YES  |     | NULL              |                |
| payment_method      | varchar(128) | YES  |     | NULL              |                |
| project             | varchar(30)  | YES  | MUL | NULL              |                |
| confirmation_number | varchar(64)  | YES  | MUL | NULL              |                |
| reversal_of         | varchar(32)  | YES  |     | NULL              |                |
| custom_type         | int(11)      | YES  |     | 0                 |                |
| timestamp           | timestamp    | NO   |     | CURRENT_TIMESTAMP |                |
+---------------------+--------------+------+-----+-------------------+----------------+

table2;
+---------------------+-------------+------+-----+---------+----------------+
| Field               | Type        | Null | Key | Default | Extra          |
+---------------------+-------------+------+-----+---------+----------------+
| id                  | int(11)     | NO   | PRI | NULL    | auto_increment |
| transaction_id      | int(11)     | YES  | MUL | NULL    |                |
| type                | int(11)     | NO   | MUL | NULL    |                |
| phone_number        | varchar(16) | NO   | MUL | NULL    |                |
| amount              | double      | NO   |     | NULL    |                |
| description         | text        | YES  |     | NULL    |                |
| person_id           | int(11)     | YES  | MUL | NULL    |                |
| loan_application_id | int(11)     | YES  | MUL | NULL    |                |
| repayment_id        | int(11)     | YES  |     | NULL    |                |
| date_time           | datetime    | YES  |     | NULL    |                |
| local_date_time     | datetime    | YES  |     | NULL    |                |
| last_modified_by    | varchar(32) | YES  |     | NULL    |                |
| last_modified       | timestamp   | YES  |     | NULL    |                |
+---------------------+-------------+------+-----+---------+----------------+

table3;
+--------------------------------+--------------+------+-----+---------+-------+
| Field                          | Type         | Null | Key | Default | Extra |
+--------------------------------+--------------+------+-----+---------+-------+
| id                             | int(11)      | NO   | PRI | NULL    |       |
| transaction_type_id            | int(11)      | NO   | MUL | NULL    |       |
| msisdn                         | varchar(32)  | NO   | MUL | NULL    |       |
| amount                         | float        | NO   |     | NULL    |       |
| mobile_money_provider_id       | int(11)      | YES  |     | NULL    |       |
| mobile_money_provider_code     | varchar(32)  | YES  |     | NULL    |       |
| source_external_id             | varchar(128) | YES  |     | NULL    |       |
| source_user_id                 | int(11)      | YES  |     | NULL    |       |
| payment_server_trx_id          | varchar(64)  | YES  | MUL | NULL    |       |
| customer_receipt               | varchar(64)  | YES  | MUL | NULL    |       |
| transaction_account_ref_number | varchar(64)  | YES  |     | NULL    |       |
| status                         | int(11)      | YES  |     | NULL    |       |
| mno_status                     | int(11)      | YES  |     | NULL    |       |
| mno_status_desc                | text         | YES  |     | NULL    |       |
| mno_transaction_id             | varchar(64)  | YES  |     | NULL    |       |
| date_completed                 | timestamp    | YES  |     | NULL    |       |
| date_acknowledged              | timestamp    | YES  |     | NULL    |       |
| created_at                     | timestamp    | YES  |     | NULL    |       |
| updated_at                     | timestamp    | YES  |     | NULL    |       |
| project                        | varchar(32)  | NO   |     | NULL    |       |
| loan_application_id            | int(11)      | YES  | MUL | NULL    |       |
+--------------------------------+--------------+------+-----+---------+-------+

我已经将table1(id,custom_type,confirmation_number)table2(transaction_id)table3(customer_receipt)编入索引,没有任何重大改进。

如何将此查询的执行时间降低到100毫秒以下?

2 个答案:

答案 0 :(得分:1)

这是您的查询:

select t.*
from table1 t left join
     table2 t2
     on t.id = t2.transaction_id left join
     table3 t3
     on t3.customer_receipt = confirmation_number
where t2.transaction_id is null and t.custom_type = 0
limit 25;

首先,您似乎不需要table3,所以让我们删除它:

select t.*
from table1 t left join
     table2 t2
     on t.id = t2.transaction_id 
where t2.transaction_id is null and t.custom_type = 0
limit 25;

对于此查询,您需要table1(custom_type, id)table2(transaction_id)上的索引。

答案 1 :(得分:0)

以下是我要尝试的更改,按照我尝试的顺序。

添加索引

首先,正如Gordon Linoff建议的那样,添加以下索引:

ALTER TABLE table1
ADD INDEX (`custom_type`,`id`)

使列NOT NULL

如果这不能提高效果,那么如果您的业务规则允许,我会将table2.transaction_id更改为NOT NULL

原因在于documentation如何描述您正在使用的反连接是如何执行的(在页面上搜索“不存在”):

  

MySQL能够对查询进行LEFT JOIN优化,并且确实如此   不检查此表中针对上一行组合的更多行   在找到与LEFT JOIN条件匹配的一行之后。这是一个   可以通过这种方式优化的查询类型示例:

SELECT * FROM t1 LEFT JOIN t2 ON t1.id=t2.id
  WHERE t2.id IS NULL;
     

假设t2.id被定义为NOT NULL。在这种情况下,MySQL扫描t1   并使用t1.id的值查找t2中的行。如果MySQL发现   在t2中有一个匹配的行,它知道t2.id永远不会是NULL,并且确实如此   不扫描t2中具有相同id的其余行   值。换句话说,对于t1中的每一行,MySQL只需要做一个   t2中的单个查找,无论t2中实际匹配多少行。

在您的查询中,t2.id列是您的table2.transaction_id列,但可以是NULL。如果可能,请尝试将其表定义更改为NOT NULL,并查看性能是否有所提高。 (如果由于其他原因必须将该列保留为null,那么显然此解决方案将无效。

添加缓存表

剩下的解决方案是在我的工作中对我有用的解决方案。我有一个查询,基本上找到可供用户选择的“项目”。有问题的用户倾向于积极刷新调用此查询的页面以查找其可用项目。

我的查询最初的工作方式与您的相同。它需要一个主表,如table1LEFT OUTER JOIN table2 ... WHERE table2.xxx IS NULL,以排除某些人已经抓过的项目。

但是,由于记录从未从任何一个表中删除,因此当有大约50,000个“抓取”的项目时,它开始减慢。基本上,MySQL花了很长时间来检查所有项目,找到10-100左右尚未被抓取。

解决方案是创建一个仅包含未编辑项目的缓存表。每当有新项目可用时,服务器端代码都会更新为插入两条记录,而不是一条记录。根据您的情况,我们称之为available_table1

CREATE TABLE available_table1 (
`id` INT NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `Table1_AvailableTable1_fk`
FOREIGN KEY (`id`)
REFERENCES `table1` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

使用原始查询填充此表一次,没有限制:

INSERT INTO available_table1
(`id`)
SELECT
t.id
FROM table1 t
left join table2 t2 on t.id=t2.transaction_id
where t2.transaction_id is null
and t.custom_type =0

现在您的查询变为:

select t.* from table1 t
INNER JOIN available_table1 at
ON at.id = t.id
left join table2 t2 on t.id=t2.transaction_id
where t2.transaction_id is null
and t.custom_type =0 limit 25

您需要定期清理此表(我们每晚都会这样做),删除现在存在table2.transaction_id给定ID的所有记录。

DELETE at FROM available_table1 at
INNER JOIN table2 t2
ON t2.transaction_id = at.transaction_id

如果您的代码可以轻松修改,您甚至可以在插入available_table记录时删除table2记录。但是,只要available_table1表中的记录足够少,您就不必过于积极地清理它。

通过这次更改,我们的查询从一个真正让整个应用程序放慢速度的主要问题转变为在我们的慢速日志中不再显示的问题,该日志设置为仅显示超过0.03秒的查询