[英]Doctrine / MySQL Slow query even when using indexes
我稍微清理了這個問題,因為它變得非常大且無法閱讀。
在我的本地主機上運行。
如下圖所示,當從包含 15000 行的表 Job 中選擇時,查詢需要755.15 ms
(where 條件返回 6650)
表 Company 包含 1000 行。 表 geo__name 包含大約 84300 行,並沒有給我任何問題,所以我相信問題出在數據庫結構或其他方面。
這兩個表的結構如下:
表工作是:
CREATE TABLE `job` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`company_id` int(11) NOT NULL,
`activity_sector_id` int(11) DEFAULT NULL,
`status` int(11) NOT NULL,
`active` datetime NOT NULL,
`contract_type_id` int(11) NOT NULL,
`salary_type_id` int(11) NOT NULL,
`workday_id` int(11) NOT NULL,
`geoname_id` int(11) NOT NULL,
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`minimum_experience` int(11) DEFAULT NULL,
`min_salary` decimal(7,2) DEFAULT NULL,
`max_salary` decimal(7,2) DEFAULT NULL,
`zip_code` int(11) DEFAULT NULL,
`vacancies` int(11) DEFAULT NULL,
`show_salary` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
KEY `created_at` (`created_at`,`active`,`status`) USING BTREE,
CONSTRAINT `FK_FBD8E0F823F5422B` FOREIGN KEY (`geoname_id`) REFERENCES `geo__name` (`id`),
CONSTRAINT `FK_FBD8E0F8398DEFD0` FOREIGN KEY (`activity_sector_id`) REFERENCES `activity_sector` (`id`),
CONSTRAINT `FK_FBD8E0F85248165F` FOREIGN KEY (`salary_type_id`) REFERENCES `job_salary_type` (`id`),
CONSTRAINT `FK_FBD8E0F8979B1AD6` FOREIGN KEY (`company_id`) REFERENCES `company` (`id`),
CONSTRAINT `FK_FBD8E0F8AB01D695` FOREIGN KEY (`workday_id`) REFERENCES `workday` (`id`),
CONSTRAINT `FK_FBD8E0F8CD1DF15B` FOREIGN KEY (`contract_type_id`) REFERENCES `job_contract_type` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
餐桌公司是:
CREATE TABLE `company` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`logo` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`website` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`user_id` int(11) NOT NULL,
`phone` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`cifnif` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`type` int(11) NOT NULL,
`subscription_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQ_4FBF094FA76ED395` (`user_id`),
KEY `IDX_4FBF094F9A1887DC` (`subscription_id`),
KEY `name` (`name`(191)),
CONSTRAINT `FK_4FBF094F9A1887DC` FOREIGN KEY (`subscription_id`) REFERENCES `subscription` (`id`),
CONSTRAINT `FK_4FBF094FA76ED395` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
查詢如下:
SELECT
j0_.id AS id_0,
j0_.status AS status_1,
j0_.title AS title_2,
j0_.min_salary AS min_salary_3,
j0_.max_salary AS max_salary_4,
c1_.id AS id_5,
c1_.name AS name_6,
c1_.logo AS logo_7,
a2_.id AS id_8,
a2_.name AS name_9,
g3_.id AS id_10,
g3_.name AS name_11,
j4_.id AS id_12,
j4_.name AS name_13,
j5_.id AS id_14,
j5_.name AS name_15,
w6_.id AS id_16,
w6_.name AS name_17
FROM
job j0_
INNER JOIN company c1_ ON j0_.company_id = c1_.id
INNER JOIN activity_sector a2_ ON j0_.activity_sector_id = a2_.id
INNER JOIN geo__name g3_ ON j0_.geoname_id = g3_.id
INNER JOIN job_salary_type j4_ ON j0_.salary_type_id = j4_.id
INNER JOIN job_contract_type j5_ ON j0_.contract_type_id = j5_.id
INNER JOIN workday w6_ ON j0_.workday_id = w6_.id
WHERE
j0_.active >= CURRENT_TIMESTAMP
AND j0_.status = 1
ORDER BY
j0_.created_at DESC
執行上述查詢時,我有以下結果:
在 MYSQL 工作台中: 0.578 sec / 0.016 sec
755.15 ms
0.578 sec / 0.016 sec
在 Symfony 分析器中: 755.15 ms
問題是:這個查詢的持續時間是否正確? 如果沒有,如何提高查詢速度? 似乎太多了。
Symfony 調試工具欄(如果有幫助):
如下圖所示,我只獲取了我真正需要的數據:
解釋查詢:
時間線:
MySQL 服務器無法處理施加在其上的負載。 這可能是由於資源爭用,或者因為它沒有進行適當的調整,也可能是您的硬盤驅動器的問題。
Symfony 運行緩慢的原因有很多。
1.服務器故障
首先,可能是服務器故障。 服務器性能可能會影響您的查詢時間。
2. 數據大小和延遲渲染
然后是數據大小。 如下圖所示,對我的一個項目的查詢有 50Mb 的數據大小(目前大約 20k 行)。
在 HTML 中解析 50Mb 可能需要一些時間,主要是因為循環。
盡管如此,還是有一些解決方案,比如延遲渲染。
延遲渲染非常簡單,而不是在你的樹枝中解析數據,
將所有數據發送到 javascript 變量,並在加載 DOM 后使用 javascript 解析/呈現數據。
3.查詢優化
正如我在評論中所寫,您可以查看以下問題,我解釋了為什么自定義查詢很重要。
Doctrine 關系是否會影響應用程序性能?
在這個問題中,你會讀到那個順序問題……它實際上是最重要的事情。
雖然數據庫中的靜態數據通常以正確的順序插入,
動態數據(用戶在網站生命周期中提供的數據)很少出現這種情況
這就是為什么在查詢中使用ORDER BY
通常會加快頁面呈現的速度,
因為學說不會自己做額外的查詢。
例如,我的站點之一在索引上顯示了大約 700 個條目。
首先,這是使用findAll()
時的查詢計數:
它在 144 毫秒內顯示 254 個查詢(253 個重復),加上 39 個渲染時間。
接下來,使用findBy()
的第二個參數ORDER BY
,我得到以下結果:
您可以在此處查看完整查詢(sreenshot 很大)
更好的是,1 次查詢僅在 8 毫秒內進行,並且渲染時間大致相同。
但是,在這里,我不使用關聯中的任何字段。
從我開始做的那一刻起,doctor qui 會做一些額外的查詢,查詢次數和時間會暴漲。
最后,它會變回findAll()
東西
最后,這是自定義查詢:
在這個自定義查詢中,查詢時間從 8 毫秒變為 38 毫秒。
但是,與之前的查詢不同,我在結果中獲得了更多數據,
這將阻止學說進行額外的查詢。
同樣, ORDER BY()
在這個查詢中很重要。 沒有它,我會猛增到 84 個查詢。
4. 部分
當您進行自定義查詢時,您可以加載部分對象而不是完整數據。
正如您在問題中所說, description
字段似乎減慢了您的加載速度,
使用partials,可以避免從表中加載某些字段,這將加快查詢速度。
首先,這不是您的常規語法,而是您將如何創建查詢構建器:
$em=$this->getEntityManager();
$qb=$em->createQueryBuilder();
以防萬一,我更願意將$em
作為一個單獨的變量(例如,如果我想獲取某個類存儲庫)。
然后你可以開始你的部分select
。 小心,第一次select
不能包含任何關聯字段:
$qb->select("partial job.{id, status, title, minimum_experience, min_salary, max_salary, zip_code, vacancies")
->from(Job::class, "job");
然后你可以添加你的關聯:
$qb->addSelect("company")
->join("job.company", "company");
或者甚至添加部分關聯,以防您不需要關聯的所有數據:
$qb->addSelect("partial activitySector.{id}")
->join("job.activitySector", "activitySector");
$qb->addSelect("partial job.{id, company_id, activity_sector_id, status, active, contract_type_id, salary_type_id, workday_id, geoname_id, title, minimum_experience, min_salary, max_salary, zip_code, vacancies, show_salary");
5. 緩存
您還可以使用各種緩存,例如用於 PHP 的 Zend OPCache,您會在以下問題中找到一些建議: Why Symfony3 so慢?
還有 SQL 緩存 Varnish。
這是關於我可以分享的所有內容以減少您的加載時間。
希望它會被證明是有用的,並且您將能夠解決您的問題。
首先,我將通過添加 MySQL 關鍵字“STRAIGHT_JOIN”來開始你的表現,它告訴 MySQL 按照我提供的順序查詢數據,不要試圖為我考慮關系。 但是,在您的數據集如此小且已經 1/2 秒的情況下,不知道這是否會有幫助,但在較大的數據集上,我知道它可以顯着提高性能。
接下來,您似乎正在獲取基於 PK/FK 關系結果的查找描述。 沒有看到這些表上的索引,我建議覆蓋包含鍵和描述的索引,以便連接可以從它用於 JOIN 的索引頁中獲取數據,而不是使用索引頁,找到要獲取的實際數據頁描述並繼續。
最后,如果索引的索引為 ( status
, active
, created_at
),則索引為 ( created_at
, active
, status
) 的作業表可能會表現得更好。
使用您現有的索引,這樣想,每天的數據都放在一個盒子里。 在按活動時間戳排序的每一天框中(即使按活動日期簡化),然后是狀態。 因此,對於 CREATED 的每一天,您都會打開一個盒子。 查看輔助框,每個“活動”時間戳對應一個框(例如:按天)。 在每個活動時間戳(天)中,只有現在才能看到“Status = 1”是否記錄。 因此,打開每個活動時間戳日,評估 Status = 1,然后關閉每個創建的日期框並轉到下一個創建的日期框並重復。 所以看看每天打開每個盒子的勞動強度,當天內每個活動的盒子。
現在,在以狀態開頭的建議索引下。 您現在擁有的盒子數量非常有限,每個狀態對應一個盒子。 只打開 1 個狀態框 = 1 這些是您唯一要考慮的...所有其他您不關心的。 在里面,你有基於 ACTIVE Timestamp 的實際記錄,並且是子排序的。 從那里,您可以直接跳轉到當前時間戳的那些。 從第一條記錄和框中的其余記錄開始,您現在擁有所有符合條件的記錄。 完畢。 由於這些記錄(索引)也將 Created_at 作為索引的一部分,因此可以使用降序對其進行優化。
為了確保其他查找表的“覆蓋索引”(如果它們尚不存在),我建議如下。
table index
company ( id, name, logo )
activity_sector (id, name )
geo__name ( id, name )
job_salary_type ( id, name )
job_contract_type ( id, name )
workday ( id, name )
還有 MySQL 關鍵字...
SELECT STRAIGHT_JOIN (rest of query...)
這么多鍵,盡量減少鍵的數量。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.