MySQL性能優化的最佳經驗

来源:http://www.cnblogs.com/qingdouchukais/archive/2016/01/28/5166138.html
-Advertisement-
Play Games

1、為查詢優化你的查詢 大多數的MySQL伺服器都開啟了查詢緩存。這是提高性最有效的方法之一,而且這是被MySQL的資料庫引擎處理的。當有很多相同的查詢被執行了多次的時候,這些查詢結果會被放到一個緩存中,這樣,後續的相同的查詢就不用操作表而直接訪問緩存結果了。 這裡最主要的問題是,對於程式員來說,這


1、為查詢優化你的查詢 
大多數的MySQL伺服器都開啟了查詢緩存。這是提高性最有效的方法之一,而且這是被MySQL的資料庫引擎處理的。當有很多相同的查詢被執行了多次的時候,這些查詢結果會被放到一個緩存中,這樣,後續的相同的查詢就不用操作表而直接訪問緩存結果了。 

這裡最主要的問題是,對於程式員來說,這個事情是很容易被忽略的。因為,我們某些查詢語句會讓MySQL不使用緩存。請看下麵的示例: 

// 查詢緩存不開啟 
$r = mysql_query("SELECT username FROM user WHERE     signup_date >= CURDATE()"); 

// 開啟查詢緩存 
$today = date("Y-m-d"); 
$r = mysql_query("SELECT username FROM user WHERE signup_date >= '$today'"); 
上面兩條SQL語句的差別就是 CURDATE() ,MySQL的查詢緩存對這個函數不起作用。所以,像 NOW() 和 RAND() 或是其它的諸如此類的SQL函數都不會開啟查詢緩存,因為這些函數的返回是會不定的易變的。所以,你所需要的就是用一個變數來代替MySQL的函數,從而開啟緩存。 

2、EXPLAIN 你的SELECT查詢 
使用EXPLAIN關鍵字可以讓你知道MySQL是如何處理你的SQL語句的。 

有表關聯的查詢,如下列: 

select username, group_name 
from users u 
joins groups g on (u.group_id = g.id) 
發現查詢緩慢,然後在group_id欄位上增加索引,則會加快查詢 

3、當只要一行數據時使用LIMIT 1 下載地址   
當你查詢表的有些時候,你已經知道結果只會有一條結果,單因為你可能需要去fetch游標,或是你也許會去檢查返回的記錄數。 
在這種情況下,加上LIMIT 1 可以增加性能。這樣一樣, MySQL資料庫引擎會在找到一條數據後停止搜索,而不是繼續往後查找下一條符合記錄的數據。 
下麵的示例,只是為了找一下是否有“中國”的用戶,很明顯,後面的會比前面的更有效率。(請註意,第一條中是Select *,第二條是Select 1) 

// 沒有效率的: 
$r = mysql_query("SELECT * FROM user WHERE country = 'China'"); 
if (mysql_num_rows($r) > 0) { 
    // ... 


// 有效率的: 
$r = mysql_query("SELECT 1 FROM user WHERE country = 'China' LIMIT 1"); 
if (mysql_num_rows($r) > 0) { 
// ... 

宣傳語 
歷經兩個半月的準備,三次大改版,十七次小改版。le1024終於要和大家見面了。 

le1024每天推薦1~3段,有趣、有愛、有故事的視頻。 

為您工作、學習、生活之餘增加一點快樂的感覺。程式員必看的快樂視頻網站 

4、為搜索欄位建索引 
索引並不一定就是給主鍵或是唯一的欄位。如果在你的表中,有某個欄位你總要會經常用來做搜索,那麼,請為其建立索引吧。 
5、在Join表的時候使用相當類型的列,並將其索引 
如果你的應用程式有很多JOIN查詢,你應該確認兩個表中Join的欄位是被建過索引的。這樣,MySQL內部會啟動為你優化Join的SQL語句的機制。 
而且,這些被用來Join的欄位,應該是相同的類型的。例如:如果你要把DECIMAL欄位和一個INT欄位JOIN在一起,MYSQL就無法使用他們的索引。對於那些STRING類型,還需要有相同的字元集才行(兩個表的字元集有可能不一樣) 
6、千萬不要ORDER BY RAND() 
7、避免SELECT * 
從資料庫里讀出越多的數據,那麼查詢就會變得越慢。並且,如果你的資料庫伺服器和WEB伺服器是兩台獨立的伺服器的話,這還會增加網路傳輸的負載。 

所以,你應該養成一個需要什麼就取什麼的好的習慣。 

// 不推薦 
$r = mysql_query("SELECT * FROM user WHERE user_id = 1"); 
$d = mysql_fetch_assoc($r); 
echo "Welcome {$d['username']}"; 

// 推薦 
$r = mysql_query("SELECT username FROM user WHERE user_id = 1"); 
$d = mysql_fetch_assoc($r); 
echo "Welcome {$d['username']}"; 
8、永遠為兩張表設置一個ID 
我們應該為資料庫里的每張表都設置一個ID作為其主鍵,而最好的是一個INT型(推薦使用UNSIGNED),並設置上自動增長的AUTO INCREMENT標誌。 
就算是你 users 表有一個主鍵叫 “email”的欄位,你也別讓它成為主鍵。使用 VARCHAR 類型來當主鍵會使用得性能下降。另外,在你的程式中,你應該使用表的ID來構造你的數據結構。 
而且,在MySQL數據引擎下,還有一些操作需要使用主鍵,在這些情況下,主鍵的性能和設置變得非常重要,比如,集群,分區…… 

9、使用 ENUM 而不是 VARCHAR ? 
ENUM 類型是非常快和緊湊的。在實際上,其保存的是 TINYINT,但其外表上顯示為字元串。這樣一來,用這個欄位來做一些選項列表變得相當的完美。 

如果你有一個欄位,比如“性別”,“國家”,“民族”,“狀態”或“部門”,你知道這些欄位的取值是有限而且固定的,那麼,你應該使用 ENUM 而不是 VARCHAR。 

10、從 PROCEDURE ANALYSE() 取得建議 ? 
PROCEDURE ANALYSE() 會讓 MySQL 幫你去分析你的欄位和其實際的數據,並會給你一些有用的建議。只有表中有實際的數據,這些建議才會變得有用,因為要做一些大的決定是需要有數據作為基礎的。 

例如,如果你創建了一個 INT 欄位作為你的主鍵,然而並沒有太多的數據,那麼,PROCEDURE ANALYSE()會建議你把這個欄位的類型改成 MEDIUMINT 。或是你使用了一個 VARCHAR 欄位,因為數據不多,你可能會得到一個讓你把它改成 ENUM 的建議。這些建議,都是可能因為數據不夠多,所以決策做得就不夠準。 

11、儘可能的使用 NOT NULL 
除非你有一個很特別的原因去使用 NULL 值,你應該總是讓你的欄位保持 NOT NULL。這看起來好像有點爭議,請往下看。 

首先,問問你自己“Empty”和“NULL”有多大的區別(如果是INT,那就是0和NULL)?如果你覺得它們之間沒有什麼區別,那麼你就不要使用NULL。(你知道嗎?在 Oracle 里,NULL 和 Empty 的字元串是一樣的!) 

不要以為 NULL 不需要空間,其需要額外的空間,並且,在你進行比較的時候,你的程式會更複雜。 當然,這裡並不是說你就不能使用NULL了,現實情況是很複雜的,依然會有些情況下,你需要使用NULL值。 

下麵摘自MySQL自己的文檔 

“NULL columns require additional space in the row to record whether their values are NULL. For MyISAM tables, each NULL column takes one bit extra, rounded up to the nearest byte.” 
12、把IP地址存成 UNSIGNED INT 
很多程式員都會創建一個 VARCHAR(15) 欄位來存放字元串形式的IP而不是整形的IP。如果你用整形來存放,只需要4個位元組,並且你可以有定長的欄位。而且,這會為你帶來查詢上的優勢,尤其是當你需要使用這樣的WHERE條件:IP between ip1 and ip2。 
我們必需要使用UNSIGNED INT,因為 IP地址會使用整個32位的無符號整形 

13、固定長度的表會更快 
如果表中的所有欄位都是“固定長度”的,整個表會被認為是 “static” 或 “fixed-length”。 例如,表中沒有如下類型的欄位: VARCHAR,TEXT,BLOB。只要你包括了其中一個這些欄位,那麼這個表就不是“固定長度靜態表”了,這樣,MySQL 引擎會用另一種方法來處理。 
固定長度的表會提高性能,因為MySQL搜尋得會更快一些,因為這些固定的長度是很容易計算下一個數據的偏移量的,所以讀取的自然也會很快。而如果欄位不是定長的,那麼,每一次要找下一條的話,需要程式找到主鍵。 

並且,固定長度的表也更容易被緩存和重建。不過,唯一的副作用是,固定長度的欄位會浪費一些空間,因為定長的欄位無論你用不用,他都是要分配那麼多的空間。 

14、垂直分割 
“垂直分割”是一種把資料庫中的表按列變成幾張表的方法,這樣可以降低表的複雜度和欄位的數目,從而達到優化的目的。(以前,在銀行做過項目,見過一張表有100多個欄位,很恐怖) 

示例一:在Users表中有一個欄位是家庭地址,這個欄位是可選欄位,相比起,而且你在資料庫操作的時候除了個人信息外,你並不需要經常讀取或是改寫這個欄位。那麼,為什麼不把他放到另外一張表中呢? 這樣會讓你的表有更好的性能,大家想想是不是,大量的時候,我對於用戶表來說,只有用戶ID,用戶名,口令,用戶角色等會被經常使用。小一點的表總是會有好的性能。 

示例二: 你有一個叫 “last_login” 的欄位,它會在每次用戶登錄時被更新。但是,每次更新時會導致該表的查詢緩存被清空。所以,你可以把這個欄位放到另一個表中,這樣就不會影響你對用戶ID,用戶名,用戶角色的不停地讀取了,因為查詢緩存會幫你增加很多性能。 

另外,你需要註意的是,這些被分出去的欄位所形成的表,你不會經常性地去Join他們,不然的話,這樣的性能會比不分割時還要差,而且,會是極數級的下降。 

15、拆分大的 DELETE 或 INSERT 語句 
如果你需要在一個線上的網站上去執行一個大的 DELETE 或 INSERT 查詢,你需要非常小心,要避免你的操作讓你的整個網站停止相應。因為這兩個操作是會鎖表的,表一鎖住了,別的操作都進不來了。 

Apache 會有很多的子進程或線程。所以,其工作起來相當有效率,而我們的伺服器也不希望有太多的子進程,線程和資料庫鏈接,這是極大的占伺服器資源的事情,尤其是記憶體。 

如果你把你的表鎖上一段時間,比如30秒鐘,那麼對於一個有很高訪問量的站點來說,這30秒所積累的訪問進程/線程,資料庫鏈接,打開的文件數,可能不僅僅會讓你泊WEB服務Crash,還可能會讓你的整台伺服器馬上掛了。

所以,如果你有一個大的處理,你定你一定把其拆分,使用 LIMIT 條件是一個好的方法。下麵是一個示例: 

while (1) { 
//每次只做1000條 
mysql_query("DELETE FROM logs WHERE log_date <= '2009-11-01' LIMIT 1000"); 
if (mysql_affected_rows() == 0) { 
    // 沒得可刪了,退出! 
    break; 

// 每次都要休息一會兒 
usleep(50000); 


16、 越小的列會越快 
對於大多數的資料庫引擎來說,硬碟操作可能是最重大的瓶頸。所以,把你的數據變得緊湊會對這種情況非常有幫助,因為這減少了對硬碟的訪問。 

參看 MySQL 的文檔 Storage Requirements 查看所有的數據類型。 

如果一個表只會有幾列罷了(比如說字典表,配置表),那麼,我們就沒有理由使用 INT 來做主鍵,使用 MEDIUMINT, SMALLINT 或是更小的 TINYINT 會更經濟一些。如果你不需要記錄時間,使用 DATE 要比 DATETIME 好得多。 

當然,你也需要留夠足夠的擴展空間,不然,你日後來乾這個事,你會死的很難看,參看Slashdot的例子(2009年11月06日),一個簡單的ALTER TABLE語句花了3個多小時,因為裡面有一千六百萬條數據。 

17、選擇一個正確的存儲引擎 
在 MySQL 中有兩個存儲引擎 MyISAM 和 InnoDB,每個引擎都有利有弊。酷殼以前文章《MySQL: InnoDB 還是 MyISAM?》討論和這個事情。 

MyISAM 適合於一些需要大量查詢的應用,但其對於有大量寫操作並不是很好。甚至你只是需要update一個欄位,整個表都會被鎖起來,而別的進程,就算是讀進程都無法操作直到讀操作完成。另外,MyISAM 對於 SELECT COUNT(*) 這類的計算是超快無比的。 

InnoDB 的趨勢會是一個非常複雜的存儲引擎,對於一些小的應用,它會比 MyISAM 還慢。他是它支持“行鎖” ,於是在寫操作比較多的時候,會更優秀。並且,他還支持更多的高級應用,比如:事務。 

18、小心“永久鏈接” 
“永久鏈接”的目的是用來減少重新創建MySQL鏈接的次數。當一個鏈接被創建了,它會永遠處在連接的狀態,就算是資料庫操作已經結束了。而且,自從我們的Apache開始重用它的子進程後——也就是說,下一次的HTTP請求會重用Apache的子進程,並重用相同的 MySQL 鏈接。 

PHP手冊:mysql_pconnect() 
在理論上來說,這聽起來非常的不錯。但是從個人經驗(也是大多數人的)上來說,這個功能製造出來的麻煩事更多。因為,你只有有限的鏈接數,記憶體問題,文件句柄數,等等。 

而且,Apache 運行在極端並行的環境中,會創建很多很多的了進程。這就是為什麼這種“永久鏈接”的機制工作地不好的原因。在你決定要使用“永久鏈接”之前,你需要好好地考慮一下你的整個系統的架構。 

參考 

19、當查詢較慢的時候,可用Join來改寫一下該查詢來進行優化 
    mysql> select sql_no_cache * from guang_deal_outs where deal_id in (select id from guang_deals where id = 100017151) ; 
Empty set (18.87 sec) 

    mysql> select sql_no_cache a.* from guang_deal_outs a inner join guang_deals b on a.deal_id = b.id where b.id = 100017151; 
    Empty set (0.01 sec) 

原因 
mysql> desc select sql_no_cache * from guang_deal_outs where deal_id in (select id from guang_deals where id = 100017151) ; 
+----+--------------------+-----------------+-------+---------------+---------+---------+-------+----------+-------------+ 
| id | select_type        | table           | type  | possible_keys | key     | key_len | ref   | rows     | Extra       | 
+----+--------------------+-----------------+-------+---------------+---------    +---------+-------+----------+-------------+ 
|  1 | PRIMARY            | guang_deal_outs | ALL   | NULL          | NULL    |     NULL    | NULL  | 18633779 | Using where | 
|  2 | DEPENDENT SUBQUERY | guang_deals     | const | PRIMARY       | PRIMARY |     4       | const |        1 | Using index | 
+----+--------------------+-----------------+-------+---------------+---------    +---------+-------+----------+-------------+ 
2 rows in set (0.04 sec) 

mysql> desc select sql_no_cache a.* from guang_deal_outs a inner join guang_deals b on a.deal_id = b.id where b.id = 100017151; 
+----+-------------+-------+-------+----------------------    +----------------------+---------+-------+------+-------------+ 
| id | select_type | table | type  | possible_keys        | key                      | key_len | ref   | rows | Extra       | 
+----+-------------+-------+-------+----------------------    +----------------------+---------+-------+------+-------------+ 
|  1 | SIMPLE      | b     | const | PRIMARY              | PRIMARY                  | 4       | const |    1 | Using index | 
|  1 | SIMPLE      | a     | ref   | idx_guang_dlout_dlid |     idx_guang_dlout_dlid | 4       | const |    1 |             | 
+----+-------------+-------+-------+----------------------    +----------------------+---------+-------+------+-------------+  
2 rows in set (0.05 sec) 
其實在  guang_deal_outs 在deal_id 上也是有索引的。 
其實我想把子查詢設置為 【下載地址】   

    select * from guang_deal_outs where deal_id in (select id from guang_deals where id = 100017151); 
變成下麵的樣子 

select * from guang_deal_outs where deal_id in (100017151); 
但不幸的是,實際情況正好相反。MySQL試圖讓它和外面的表產生聯繫來“幫助”優化查詢,它認為下麵的exists形式更有效率 

select * from guang_deal_outs where exists (select * from guang_deals where id = 100017151 and id = guang_deal_outs.deal_id); 
這種in子查詢的形式,在外部表(比如上面的guang_deals)數據量比較大的時候效率是很差的(如果對於較小的表,不會造成顯著地影響) 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、簡介 Redis的replication機制允許slave從master那裡通過網路傳輸拷貝到完整的數據備份。具有以下特點: 非同步複製 可以配置一主多從 可以配置從伺服器可以級聯從伺服器,既 M->S->S M replication時是非阻塞的(在replication期間,M依然能夠處理客戶
  • 分片(partitioning)就是將你的數據拆分到多個 Redis 實例的過程,這樣每個實例將只包含所有鍵的子集。本文第一部分將向你介紹分片的概念,第二部分將向你展示 Redis 分片的可選方案。 分片能做什麼 Redis 的分片承擔著兩個主要目標: 允許使用很多電腦的記憶體總和來支持更大的資料庫。
  • 概述 本篇文章主要介紹Mycat以月進行分片的方法,包括配置方法、註意事項等。 mycat版本:1.4 數據節點:dn1,dn2,dn3 架構:主從 配置 創建測試表 CREATE TABLE `tdate` ( `id` int(11) NOT NULL, `createdate` datetim
  • 一、 Storm整體介紹 Storm 是一個類似Hadoop MapReduce的系統, 用戶按照指定的介面實現一個任務,然後將這個任務遞交給JStorm系統,Jstorm將這個任務跑起來,並且按7 * 24小時運行起來,一旦中間一個Worker 發生意外故障, 調度器立即分配一個新的Worker替
  • 前陣子搞了下資料庫同步,大概意思就是伺服器上有一個資料庫,與本地資料庫進行同步,伺服器上的資料庫有什麼改變,可以同步到本地資料庫中。做之前百度了下,流程分以下三步, 第一步: 伺服器上的資料庫進行發佈 第二步: 本地資料庫進行訂閱 第三步: 把冰箱門關上 看起來很簡單,實際上遠不是這麼一回事,把哥都
  • 有一張資金記錄表,一張金幣記錄表,想以時間為單位,降序合併排列他們之間的信息,查詢SQL如下:select * from (select * from t_money_logs union select * from t_gold_logs) as c order by create_time de
  • pg_dump 是一個用於備份PostgreSQL資料庫的工具. 該工具生成的轉儲格式可以分為兩種, 腳本 : 其中腳本格式是包含許多SQL命令的純文本格式 (常用) 歸檔文件: 需要重建資料庫就必須和pg_restore工具一起使用 下麵按照這兩種方式進行備份恢復操作。 環境簡介: 操作對象:數據
  • mysql 命令導入sql腳本數據
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...