运行缓慢的 SQL 查询
Posted
技术标签:
【中文标题】运行缓慢的 SQL 查询【英文标题】:Slow running SQL Query 【发布时间】:2018-01-14 15:00:28 【问题描述】:我在运行 5 秒以仅获取 25 条记录的 mysql 上运行此 SQL 查询时遇到问题 - 非常糟糕;
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 毫秒以下?
【问题讨论】:
只是一个建议,请记住限制 25 ...仅显示 25 但获取所有 .. 行导致查询 我在查询中只看到 2 个表。请使用SHOW CREATE TABLE
,它比DESCRIBE
更具描述性。
【参考方案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)
上的索引。
【讨论】:
已经完成了,查询在没有 table3 的情况下执行 5 秒,这太慢了 @xcoder - 所以请编辑您的问题以反映这一变化。【参考方案2】:以下是我将尝试的更改,按我尝试的顺序。
添加索引
首先,按照 Gordon Linoff 的建议,添加以下索引:
ALTER TABLE table1
ADD INDEX (`custom_type`,`id`)
使列不为空
如果这还不足以提高性能,那么如果您的业务规则允许,我会将 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
并查看性能是否有所提高。 (如果由于其他原因必须将该列设为空,那么显然此解决方案将不起作用。
添加缓存表
剩下的解决方案在我的工作中对我很有效。我有一个查询,基本上可以找到可供用户选择的可用“项目”。有问题的用户往往会积极地快速刷新调用此查询的页面以查找他们的可用项目。
我的查询最初与您的查询方式相同。它需要一个主表,比如你的table1
,和一个LEFT 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
您需要定期清理此表(我们每晚都会这样做),方法是删除给定 ID 现在存在 table2.transaction_id
的所有记录。
DELETE at FROM available_table1 at
INNER JOIN table2 t2
ON t2.transaction_id = at.transaction_id
如果您的代码可以很容易地修改,您甚至可以在插入table2
记录时删除available_table
记录。但是,只要available_table1
表中的记录足够少,就不必过于激进地清理它。
通过此更改,我们的查询从一个真正拖慢整个应用程序的主要问题变成了一个甚至不再显示在我们的慢日志中的查询,该日志设置为仅显示超过 0.03 秒的查询.
【讨论】:
以上是关于运行缓慢的 SQL 查询的主要内容,如果未能解决你的问题,请参考以下文章