簡體   English   中英

提高MySQL sum查詢性能

[英]Improving Mysql sum query performance

因此,我有一個大約有4800萬行的表。

User    Count
1232    12
12331   4534
...     ...
...     ....

這是一個只讀表。 不會再有其他數據了。 我需要找到兩個用戶標識之間所有用戶的計數列總和...

我現在有的查詢是:

Select sum(count) from table where user between x and Y.

但這對每個查詢來說都需要2秒鍾以上的時間。 我需要把它大大降低。 有什么辦法可以做到嗎? 我是否需要創建聚簇索引或您可以想到的任何其他配置?

更新:我已經在用戶列上有一個索引

如果使用適當的索引無法獲得令人滿意的性能,並且數據庫是只讀的,則可以考慮我在注釋中提到的方法:預先計算塊的總和。 它比分區更進一步:分區可以並行計算您的總和,但是預先計算的總和大大超過了它。 理想的塊大小在表中行數的平方根附近。

說這是你的桌子:

CREATE TABLE foo (
  user INTEGER AUTO_INCREMENT PRIMARY KEY,
  cnt INTEGER
);

INSERT INTO foo (cnt) VALUES (1), (4), (9), (16), (25), (36), (49), (64), (81), (100);

現在制作一個預先計算的塊總和表。 為了清楚起見,我在這里使用SQL變量,您可能不需要使用這些變量,因為您將從另一種編程語言構造查詢:

SET @block = 3;

CREATE TABLE foosums (
  block INTEGER PRIMARY KEY,
  cntsum INTEGER
)
SELECT FLOOR((user - 1) / @block) AS block, SUM(cnt) AS cntsum
FROM foo GROUP BY block;

現在,要計算@from@to之間的總和,您將獲取這兩個之間的所有完整塊的總和,並添加在這些塊之前和之后的所有單獨行。 在此示例中,要添加行1..10,我們將獲取塊1 ... 3,塊4..6,塊7..9和單個行10。

SET @from=1, @to=10;

SELECT
  COALESCE((
    SELECT SUM(cnt)
    FROM foo
    WHERE user >= @from AND user < CEILING((@from - 1) / @block) * @block + 1
  ), 0)
+ COALESCE((
    SELECT SUM(cntsum)
    FROM foosums
    WHERE block >= CEILING((@from - 1) / @block) AND block < FLOOR(@to / @block)
  ), 0)
+ COALESCE((
    SELECT SUM(cnt)
    FROM foo
    WHERE user > FLOOR(@to / @block) * @block AND user <= @to
  ), 0)
AS blocked_total;

為了驗證所有功能是否正常運行,這是未優化的查詢,不使用塊總和:

SELECT SUM(cnt) AS individual_total FROM foo WHERE user >= @from AND user <= @to;

最后,一個可視化圖表可幫助您准確查看優化查詢所包含的數據:

SELECT * FROM foo WHERE user >= @from AND user < CEILING((@from - 1) / @block) * @block + 1;
SELECT * FROM foosums WHERE block >= CEILING((@from - 1) / @block) AND block < FLOOR(@to / @block);
SELECT * FROM foo WHERE user > FLOOR(@to / @block) * @block AND user <= @to;

SQLFiddle


*)“塊” ==“塊”。 我在文本之前編寫了代碼,不想改變術語:p

答案中的一點點高科技可能來自不熟悉某些可用的更先進技術的自學成才的人。 免責聲明已完成。 這就是我要做的,如果我知道數據將永遠不會改變。

我將創建一個腳本,將其分成幾十個甚至一百個單獨的表,並根據范圍進行命名。 例如,表一可以命名為“ cluster_1_to_10000”,也可以根據您對范圍和用戶編號的了解而命名為“ cluster_1_to_10000”。

這樣,當您進行查詢時,您可以根據范圍使表名在代碼中動態化,並節省大量無關數據的過濾時間。 它將使PHP? 更復雜,尤其是當范圍介於多個表之間時,但是我認為額外30行左右的PHP代碼值得每次跳過幾十萬行。

不知道這是否解釋正確,如果您願意,我可以提供一些偽代碼示例。

編輯偽代碼

名為:“ table_1_to_499999”,“ table_500000_to_999999”等的表。您將需要編寫腳本來拆分這些表並創建每個表,並且顯然保留原始表,以防萬一。

偽代碼(抱歉,對Java不熟悉):

Var StartTable="";
Var EndTable="";

var Table=array();

Table=//populate table from select tables statement to get them in order
//make each tables key the start value as it pulls the table list from your database
//for example 
Table[1]="table_1_to_499999"
Table[500000]="table_500000_to_999999", etc.

//now you have your two user id's
user-id1 and user-id2;

var table1='';
var table2='';

var key1=0;
var key2=0;

foreach(Table as key=>val)
{
   if(user_id1>key)
   {
      table1=val;
      key1=key;
   }
}

foreach(Table as key=>val)
{
   if(user_id2>key)
   {
      table2=val;
      key2=key;
   }
}

if(key1==key2)
{
   //do your query here, all from the same table, both id's are in the same table, so you can query either table1 or table2 to get your data
}
else if(key1>key2)
{
   //query all results greater than or equal to user-id1 in table 1
   //query all results less than or equal to user-id2 in table 2
   //add the two results together
}
else if(key1<key2)
{
   //query all results less than or equal to user-id1 in table 1
   //query all results greater than or equal to user-id2 in table 2
   //add the two results together
}

我認為可以解決這個問題……也許我要去吃午飯了,但是我認為這樣做雖然會增加一次復雜性,但可以大大節省查詢量。 您可能必須執行兩次查詢才能提取數據,從而增加了一些開銷,但是您只查詢了上百萬行而不是上千萬行。 較少,取決於您如何拆分它們。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM