4

毎日数千万行ずつ増加するテーブルがあります。表の行には、ページ ビュー トラフィックに関する時間別情報が含まれています。

テーブルのインデックスは、url と datetime にあります。

時間単位ではなく日単位で情報を集計したい。どうすればいいですか?これは、私がやろうとしていることを例示するクエリです:

SELECT url, sum(pageviews), sum(int_views), sum(ext_views)
FROM news
WHERE datetime >= "2012-08-29 00:00:00" AND datetime <= "2012-08-29 23:00:00"
GROUP BY url
ORDER BY pageviews DESC
LIMIT 10;

ただし、上記のクエリは決して終了しません。テーブルには何百万もの行があります。この集計データを取得するより効率的な方法はありますか?

4

2 に答える 2

6

1 日あたり数千万行というのは、かなりの数です。

仮定:

  • 1 日あたりわずか 1,000 万件の新しいレコード。
  • テーブルには、質問で言及した列のみが含まれています。
  • urlTEXT平均 (Punycode) 長さが ~ 77 文字のタイプです。
  • pageviewsタイプINTです。
  • int_viewsタイプINTです。
  • ext_viewsタイプINTです。と
  • datetimeタイプですDATETIME

その場合、毎日のデータは約 9.9 × 10 8バイトを占めることになり、これはほぼ 1GiB/日になります。上記の仮定は非常に保守的であるため、実際にはこれよりもかなり多くなる可能性があります。

MySQL の最大テーブル サイズは、とりわけ、そのデータ ファイルが存在する基礎となるファイル システムによって決まります。Windows または Linux でパーティショニングを行わずに MyISAM エンジン (以下のコメントで示唆されているように) を使用している場合、数 GiB の制限は珍しくありません。これは、テーブルが 1 週間以内に十分に容量に達することを意味します。

@Gordon Linoff が述べたように、テーブルを分割する必要があります。ただし、各テーブルには1024 個のパーティションの制限があります。1 日あたり 1 パーティション (これはあなたのケースでは差し迫って賢明なことです) では、パーティションが再利用され始める前に、1 つのテーブルに 3 年未満のデータを保存することに制限されます。

したがって、毎年のデータを独自のテーブルに保持し、それぞれを日ごとに分割することをお勧めします。さらに、@Ben が説明したように、複合インデックスが役立ちます (クエリの実行時に MySQL がパーティションをプルーニングできるようになるため、実際に列を作成してインデックスを(datetime, url)作成することを提案します)。また、行レベルのロックとトランザクションの整合性が重要でない場合 (この種のテーブルの場合、重要ではない可能性があります)、MyISAM を使用するのは簡単なことではありません。dateDATE(datetime)

CREATE TABLE news_2012 (
  INDEX (date, url(100))
)
Engine = MyISAM
PARTITION BY HASH(TO_DAYS(date)) PARTITIONS 366
SELECT *, DATE(datetime) AS date FROM news WHERE YEAR(datetime) = 2012;

CREATE TRIGGER news_2012_insert BEFORE INSERT ON news_2012 FOR EACH ROW
  SET NEW.date = DATE(NEW.datetime);

CREATE TRIGGER news_2012_update BEFORE UPDATE ON news_2012 FOR EACH ROW
  SET NEW.date = DATE(NEW.datetime);

MyISAM を使用することを選択した場合、( を使用して) 完成した年をアーカイブするだけでなく、元のテーブルを、基礎となるすべての年テーブルを含むものmyisampackに置き換えることもできます(InnoDB でも機能する別の方法は、 、しかし、ビューは更新も挿入もできないため、ステートメントにのみ役立ちます):MERGEUNIONVIEWSELECTUNION

DROP TABLE news;
CREATE TABLE news (
  date DATE,
  INDEX (date, url(100))
)
Engine = MERGE
INSERT_METHOD = FIRST
UNION = (news_2012, news_2011, ...)
SELECT * FROM news_2012 WHERE FALSE;

次に、このマージ テーブルで上記のクエリを (他のクエリと一緒に) 実行できます。

SELECT   url, SUM(pageviews), SUM(int_views), SUM(ext_views)
FROM     news
WHERE    date = '2012-08-29'
GROUP BY url
ORDER BY SUM(pageviews) DESC
LIMIT    10;
于 2012-08-30T22:49:38.560 に答える
5

いくつかのポイント:

  1. また、フィルタリングしている唯一の述語として、おそらくdatetime最初の列としてインデックスが必要です。
  2. で注文していpageviewsます。で注文したいと思っていましたsum(pageviews)
  3. <24 時間ではなく 23 時間のデータをクエリしています。何かが欠落しないように、次の日の真夜中から、明示的な未満を使用することをお勧めします。
SELECT url, sum(pageviews), sum(int_views), sum(ext_views)
  FROM news
 WHERE datetime >= '2012-08-29 00:00:00'
   AND datetime < '2012-08-30 00:00:00'
 GROUP BY url
 ORDER BY sum(pageviews) DESC
 LIMIT 10;

これにインデックスを付けることができますdatetime, url, pageviews, int_views, ext_viewsが、それはやり過ぎだと思います。したがって、インデックスが大きすぎない場合はdatetime, url、良い方法のようです。確実にする唯一の方法は、それをテストして、クエリのパフォーマンスの向上がインデックスのメンテナンスに余分な時間を費やす価値があるかどうかを判断することです。

Gordon がコメントで述べたように、 partitioningを調べる必要があるかもしれません。これにより、大きなテーブルの一部である小さな「テーブル」をクエリできます。すべてのクエリが日レベルに基づいている場合、毎日新しいクエリを作成する必要があるように思えます。

于 2012-08-30T21:09:26.660 に答える