五個容易錯過的 PostgreSQL 查詢效能瓶頸
PostgreSQL 查詢計劃器充滿了驚喜,因此編寫高效能查詢的常識性方法有時會產生誤導。在這篇博文中,我將描述藉助 EXPLAIN ANALYZE 和 Postgres 後設資料分析最佳化看似顯而易見的查詢的示例。
1. 透過函式呼叫搜尋
透過使用 PostgreSQL 函式呼叫修改的值進行搜尋是很常見的。讓我們看一下透過小寫值搜尋列的查詢計劃:
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM users WHERE lower(email) = 'email@example.com' ; -> Parallel Seq Scan on users Filter: (lower((email)::text) = 'email@example.com'::text) Rows Removed by Filter: 333667 Buffers: shared hit=1248 read=41725 Execution Time: 180.813 ms |
該報告表明查詢計劃器執行了低效的Seq Scan操作Filter來執行查詢。由於在BUFFERS查詢中新增了選項,我們可以看到資料庫必須使用慢速磁碟讀取操作來獲取超過 40k 的資料頁,並且其中只有大約 1k 被快取在記憶體中。
按函式搜尋的查詢不能使用標準索引。因此,您需要新增自定義索引以使其高效。
但是,在每個查詢的基礎上新增自定義索引並不是一種非常可擴充套件的方法。您可能會發現自己有多個冗餘索引,這會顯著減慢寫入操作。
如果大小寫字母無關緊要,您可以執行遷移以小寫所有值並使標準索引起作用。但是,如果您仍想在資料庫中儲存大寫字元,您可以考慮使用CITEXT 副檔名。它建立了一個不區分大小寫的列,可以在不建立自定義索引的情況下進行高效搜尋。
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM users WHERE email = 'Email@exaMple.Com' ; Index Scan using index_users_on_email on users Index Cond: (email = 'Email@exaMple.Com'::citext) Buffers: shared hit=3 Execution Time: 0.128 ms |
原始查詢的180 毫秒執行時間可能看起來並不多。但我們剛剛設法將其加速了幾個數量級,降至 1毫秒以下!
無論資料大小如何,新解決方案都將保持高效能,並且查詢僅從記憶體快取中獲取三個緩衝區塊。此外,透過利用擴充套件,我們可以避免新增額外的索引。
2. 按模式搜尋
LIKE和ILIKE查詢經常被使用,但並不總是很明顯需要額外的設定來有效地執行它們。讓我們看看示例查詢在標準 B 樹索引下的表現:
EXPLAIN ANALYZE SELECT * FROM users WHERE email LIKE '%@example.com'; -> Parallel Seq Scan on users Filter: ((email)::text ~~ '%@example.com'::text) Execution Time: 111.263 ms |
和以前一樣,查詢計劃器無法利用索引,不得不求助於低效Seq Scan的Filter.
為了加快這個查詢的速度,我們必須新增一個自定義擴充套件和索引型別。執行以下命令:
CREATE EXTENSION IF NOT EXISTS btree_gin; CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE INDEX index_users_on_email_gin ON users USING gin (email gin_trgm_ops); |
讓我們重新執行我們的查詢:
EXPLAIN ANALYZE SELECT * FROM users WHERE email LIKE '%@example.com'; Bitmap Heap Scan on users Recheck Cond: ((email)::text ~~ '%@example.com'::text) -> Bitmap Index Scan on index_users_on_email_gin Index Cond: ((email)::text ~~ '%@example.com'::text) Execution Time: 0.206 ms |
現在它在1ms以下執行。請記住,gin索引的更新速度比標準的要慢。因此,您應該避免將它們新增到經常更新的表中。
3. Ordering by NULLS LAST
除非列配置為NOT NULL,否則在使用它進行排序時必須小心。
預設ASC順序將始終在結果末尾返回NULL值。
但是,如果您想按降序對潛在可能是NULL的字串進行排序但將所有NULLs保留在末尾怎麼辦?最初的方法可能是利用NULLS LAST自定義排序順序。讓我們仔細看看EXPLAIN ANALYZE此類查詢會產生的輸出:
EXPLAIN ANALYZE SELECT * FROM users ORDER BY email DESC LIMIT 10; -> Index Scan Backward using index_users_on_email on users Execution Time: 0.641 ms |
我們可以看到一個Index Scan Backward條目,因此我們的查詢正確地使用了索引,並且幾乎立即執行。但是,此查詢的結果將始終從NULL值開始。因此,如果我們想將它們移動到響應的末尾,我們可以像這樣重寫它:
EXPLAIN ANALYZE SELECT * FROM users ORDER BY email DESC NULLS LAST LIMIT 10; -> Sort (cost=41482.85..42525.55 rows=417083 width=41) (actual time=5572.524..5572.589 rows=8 loops=3) Sort Key: email DESC NULLS LAST Sort Method: top-N heapsort Memory: 26kB -> Parallel Seq Scan on users (cost=0.00..32469.83 rows=417083 width=41) (actual time=0.037..2793.011 rows=333667 loops=3) Execution Time: 5578.725 ms |
正如您所看到的,查詢現在執行了超過5 SECONDS。
儘管email列被索引,但標準索引不能用於帶有NULLS LAST選項的排序。相反,資料庫必須在記憶體中對整個表進行排序,或者退回到更慢的磁碟排序。它不僅會降低效能,而且還會顯著增加整體記憶體使用量。
您可以透過新增自定義索引來修復它,但是,就像在按函式搜尋的情況下一樣,在每個查詢的基礎上新增自定義索引是一種不好的做法。
獲得所需結果的一種簡單方法是編寫兩個查詢。第一個將獲取已排序的非空值。如果結果不滿足LIMIT,則另一個查詢會獲取剩餘的帶有NULL值的行。
SELECT * FROM users ORDER BY email DESC WHERE email IS NOT NULL LIMIT 10; SELECT * FROM users WHERE email IS NULL LIMIT 10; |
4. Bloated null_indexes
新增正確的索引可以顯著提高查詢執行時間。但是,過度使用索引會大大增加資料庫的大小並增加維護記憶體的使用。此外,必須在每次寫入操作時更新索引。所以限制它們的數量和範圍通常是一個好方法。
您的資料庫可能有一些所謂的(我認為)“NULL 索引”。這些是包含高比率NULL值的索引。
根據業務邏輯,NULL可能會使用一個值進行搜尋,因此這些索引是正確的。但是,通常,您不會編寫查詢來搜尋包含特定NULL值的行。如果是這種情況,重新建立索引以排除NULLs 將減少磁碟使用量並限制必須更新的頻率。
您可以執行以下命令來刪除和重建索引以僅包含NOT NULL行:
DROP INDEX CONCURRENTLY users_reset_token_ix; CREATE INDEX CONCURRENTLY users_reset_token_ix ON users(reset_token) WHERE reset_token IS NOT NULL; |
值得注意的是,這個索引仍然可以被顯式搜尋所有NOT NULL值的查詢使用。
您可以檢視PG Extrasnull_indexes方法(或執行其原始 SQL 原始碼)以檢視您的資料庫是否有許多可以削減的索引以及預期的磁碟空間節省。
您可以在這篇博文中閱讀更多關於使用 PG Extras 最佳化 PostgreSQL 效能的資訊。
5.更新事務範圍
通常推薦的做法是將資料庫提交的數量保持在最低限度。這意味著將多個更新查詢包裝到單個事務中應該可以提高寫入效能。
對於許多常見場景,這是一個最佳策略。但是,使用單個事務進行大量資料更新可能會導致所謂的鎖問題。那麼讓我們看看在單個事務中更新超過 100k 行有什麼影響:
UPDATE messages SET status = 'archived';
當事務仍處於掛起狀態時,您可以使用PG Extraslocks方法(或執行其原始 SQL 原始碼)調查它生成的鎖。
您可能沒有足夠大的資料集來locks在更新事務仍在執行時手動執行 SQL。在這種情況下,您可以像這樣在單個事務中偽造緩慢的執行時間:
BEGIN; UPDATE messages SET status = 'archived'; SELECT pg_sleep(15); COMMIT; |
現在,執行locksSQL 應該會返回類似的輸出:
relname | mode | query_snippet ------------------------------------------------------------------------------- messages | RowExclusiveLock | UPDATE "messages" SET "status" = $1 index_messages_on_status | RowExclusiveLock | UPDATE "messages" SET "status" = $1 index_messages_on_text | RowExclusiveLock | UPDATE "messages" SET "status" = $1 index_messages_on_time | RowExclusiveLock | UPDATE "messages" SET "status" = $1 ... |
可以看到更新操作獲取了錶行上的RowExclusiveLock鎖,並鎖定了對應的索引。
這意味著在漫長的單事務更新過程中嘗試更新相同行的任何其他程式都必須等待它完成。因此,後臺工作程式執行的大規模更新可能會使 Web 伺服器程式超時並導致面向使用者的應用程式中斷。
為避免此問題,您可以使用類似的 SQL 將批處理新增到更新操作:
UPDATE messages SET status = 'archived' WHERE id IN (SELECT ID FROM messages ORDER BY ID LIMIT 10000 OFFSET 0); UPDATE messages SET status = 'archived' WHERE id IN (SELECT ID FROM messages ORDER BY ID LIMIT 10000 OFFSET 10000); UPDATE messages SET status = 'archived' WHERE id IN (SELECT ID FROM messages ORDER BY ID LIMIT 10000 OFFSET 20000); ... |
上面的示例一次更新 10k 的行。整個操作可能需要比在單個事務中執行更長的時間。但是,每個更新步驟都會快速提交資料庫更改,因此其他程式不會卡住。
如果您懷疑您的應用程式的效能因鎖定事務而下降,您可以結合使用locksPG blockingExtras 方法來監控長期表鎖。
相關文章
- linux高階工具命令(三)使用gprof和oprofile查詢效能瓶頸Linux
- Linux 磁陣效能瓶頸定位過程Linux
- 影響你網站效能的 5 個瓶頸網站
- 分享發現的一個效能瓶頸問題
- 利用PerfDog分析遊戲效能瓶頸遊戲
- 用 pprof 找出程式碼效能瓶頸
- Chrome執行時效能瓶頸分析Chrome
- wait event監測效能瓶頸AI
- 如何正確定義效能瓶頸
- 實用技巧:快速定位Zuul的效能瓶頸Zuul
- PHP的curl造成效能瓶頸,如何優化?PHP優化
- JVM 效能調優實戰之:一次系統效能瓶頸的尋找過程JVM
- 效能課堂-TPS 瓶頸精準定位
- 效能測試-服務端瓶頸分析思路服務端
- 漫談前端效能 突破 React 應用瓶頸前端React
- 突破效能瓶頸,實現流程自動化
- LightDB資料庫效能瓶頸分析(一)資料庫
- MySQL 效能優化之硬體瓶頸分析MySql優化
- 資料庫效能監控瓶頸理論資料庫
- 伺服器IO瓶頸對MySQL效能的影響伺服器MySql
- 使用vmstat標識linux系統的效能瓶頸Linux
- 利用Windows效能計數器分析軟體產品的效能瓶頸Windows
- Oracle/MySQL/PostgreSQL 簡單查詢的效能對比OracleMySql
- loadrunner 關於計算及瓶頸識別(五)
- 擴充套件jwt解決oauth2 效能瓶頸套件JWTOAuth
- 使用 sar 和 kSar 來發現 Linux 效能瓶頸Linux
- web效能優化系列之網站瓶頸識別Web優化網站
- 前端瓶頸如何打破???前端
- NVMe儲存效能瓶頸的主要來源:檔案系統
- 使用chrome開發者工具中的performance皮膚解決效能瓶頸ChromeORM
- 打破Kafka帶來的瓶頸?Kafka
- 通過專案管理軟體克服管理瓶頸的方法?專案管理
- 透過預熱來最佳化POSTGRESQL的查詢SQL
- Redis效能瓶頸揭秘:如何最佳化大key問題?Redis
- [轉]檢測SQLSERVER資料庫CPU瓶頸及記憶體瓶頸SQLServer資料庫記憶體
- 效能分析(6)- 如何迅速分析出系統 CPU 的瓶頸在哪裡
- 流量高峰時期的效能瓶頸有哪些、以及如何來解決
- TextView效能瓶頸,渲染優化,以及StaticLayout的一些用處TextView優化