sql >> データベース >  >> RDS >> Mysql

何百万ものエントリでランキング

    1回のディスクシークは約15ミリ秒ですが、サーバーグレードのディスクの場合は少し短くなります。 500ms未満の応答時間では、約30回のランダムディスクアクセスに制限されます。それほど多くはありません。

    私の小さなラップトップには、開発データベースがあります

    [email protected] [kris]> select @@innodb_buffer_pool_size/1024/1024 as pool_mb;
    +--------------+
    | pool_mb      |
    +--------------+
    | 128.00000000 |
    +--------------+
    1 row in set (0.00 sec)
    

    と遅いラップトップディスク。でスコアテーブルを作成しました

    [email protected] [kris]> show create table score\G
    *************************** 1. row ***************************
           Table: score
    Create Table: CREATE TABLE `score` (
      `player_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `score` int(11) NOT NULL,
      PRIMARY KEY (`player_id`),
      KEY `score` (`score`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2490316 DEFAULT CHARSET=latin1
    1 row in set (0.00 sec)
    

    ランダムな整数スコアと連続したplayer_id値を使用します。

    [email protected] [kris]> select count(*)/1000/1000 as mrows from score\G
    *************************** 1. row ***************************
    mrows: 2.09715200
    1 row in set (0.39 sec)
    

    データベースはペア(score, player_id)を維持します scoreで インデックスの順序score 、InnoDBインデックスのデータはBTREEに格納され、行ポインター(データポインター)が主キー値であるため、定義KEY (score) 最終的にKEY(score, player_id)になります 初めの。スコア取得のクエリプランを確認することで、次のことを証明できます。

    [email protected] [kris]> explain select * from score where score = 17\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: score
             type: ref
    possible_keys: score
              key: score
          key_len: 4
              ref: const
             rows: 29
            Extra: Using index
    1 row in set (0.00 sec)
    

    ご覧のとおり、key: score Using indexで使用されています 、データアクセスが不要であることを意味します。

    特定の定数player_idのランキングクエリ 私のラップトップでは正確に500msかかります:

    [email protected] [kris]>  select p.*, count(*) as rank 
        from score as p join score as s on p.score < s.score 
       where p.player_id = 479269\G
    *************************** 1. row ***************************
    player_id: 479269
        score: 99901
         rank: 2074
    1 row in set (0.50 sec)
    

    より多くのメモリとより高速なボックスを使用すると、より高速になりますが、計画がうまくいかないため、それでも比較的コストのかかる操作です。

    [email protected] [kris]> explain select p.*, count(*) as rank from score as p join score as s on p.score < s.score where p.player_id = 479269;
    +----+-------------+-------+-------+---------------+---------+---------+-------+---------+--------------------------+
    | id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows    | Extra                    |
    +----+-------------+-------+-------+---------------+---------+---------+-------+---------+--------------------------+
    |  1 | SIMPLE      | p     | const | PRIMARY,score | PRIMARY | 4       | const |       1 |                          |
    |  1 | SIMPLE      | s     | index | score         | score   | 4       | NULL  | 2097979 | Using where; Using index |
    +----+-------------+-------+-------+---------------+---------+---------+-------+---------+--------------------------+
    2 rows in set (0.00 sec)
    

    ご覧のとおり、プランの2番目のテーブルはインデックススキャンであるため、クエリはプレーヤーの数に比例して遅くなります。

    完全なリーダーボードが必要な場合は、where句を省略する必要があります。そうすると、2回のスキャンと2次実行時間が得られます。したがって、この計画は完全に崩壊します。

    ここで手続きを進めましょう:

    [email protected] [kris]> set @count = 0; 
        select *, @count := @count + 1 as rank from score where score >= 99901 order by score desc ;
    ...
    |   2353218 | 99901 | 2075 |
    |   2279992 | 99901 | 2076 |
    |   2264334 | 99901 | 2077 |
    |   2239927 | 99901 | 2078 |
    |   2158161 | 99901 | 2079 |
    |   2076159 | 99901 | 2080 |
    |   2027538 | 99901 | 2081 |
    |   1908971 | 99901 | 2082 |
    |   1887127 | 99901 | 2083 |
    |   1848119 | 99901 | 2084 |
    |   1692727 | 99901 | 2085 |
    |   1658223 | 99901 | 2086 |
    |   1581427 | 99901 | 2087 |
    |   1469315 | 99901 | 2088 |
    |   1466122 | 99901 | 2089 |
    |   1387171 | 99901 | 2090 |
    |   1286378 | 99901 | 2091 |
    |    666050 | 99901 | 2092 |
    |    633419 | 99901 | 2093 |
    |    479269 | 99901 | 2094 |
    |    329168 | 99901 | 2095 |
    |    299189 | 99901 | 2096 |
    |    290436 | 99901 | 2097 |
    ...
    

    これは手続き型の計画であるため、不安定です:

    • LIMITを使用すると、カウンターがオフセットされるため、使用できません。代わりに、このすべてのデータをダウンロードする必要があります。
    • 実際に並べ替えることはできません。このORDER BY 句はソートされないため機能しますが、インデックスを使用します。 using filesortが表示されたらすぐに 、カウンター値は大幅にオフになります。

    ただし、これは、NoSQL(読み取り:手続き型)データベースが実行プランとして実行することに最も近いソリューションです。

    ただし、サブクエリ内でNoSQLを安定させてから、関心のある部分を切り取ることができます。

    [email protected] [kris]> set @count = 0; 
        select * from ( 
            select *, @count := @count + 1 as rank 
              from score 
             where score >= 99901 
          order by score desc 
        ) as t 
        where player_id = 479269;
    Query OK, 0 rows affected (0.00 sec)
    +-----------+-------+------+
    | player_id | score | rank |
    +-----------+-------+------+
    |    479269 | 99901 | 2094 |
    +-----------+-------+------+
    1 row in set (0.00 sec)
    
    [email protected] [kris]> set @count = 0; 
        select * from ( 
            select *, @count := @count + 1 as rank 
              from score 
             where score >= 99901 
          order by score desc 
        ) as t 
        where rank between 2090 and 2100;
    Query OK, 0 rows affected (0.00 sec)
    +-----------+-------+------+
    | player_id | score | rank |
    +-----------+-------+------+
    |   1387171 | 99901 | 2090 |
    |   1286378 | 99901 | 2091 |
    |    666050 | 99901 | 2092 |
    |    633419 | 99901 | 2093 |
    |    479269 | 99901 | 2094 |
    |    329168 | 99901 | 2095 |
    |    299189 | 99901 | 2096 |
    |    290436 | 99901 | 2097 |
    +-----------+-------+------+
    8 rows in set (0.01 sec)
    

    サブクエリは、前の結果セットをtという名前のアドホックテーブルとして具体化し、外部クエリでアクセスできるようにします。これはアドホックテーブルであるため、MySQLではインデックスがありません。これにより、外部クエリで効率的に可能なことが制限されます。

    ただし、両方のクエリがタイミング制約をどのように満たすかに注意してください。計画は次のとおりです:

    [email protected] [kris]> set @count = 0; explain select * from ( select *, @count := @count + 1 as rank from score where score >= 99901 order by score desc ) as t where rank between 2090 and 2100\G
    Query OK, 0 rows affected (0.00 sec)
    
    *************************** 1. row ***************************
               id: 1
      select_type: PRIMARY
            table: <derived2>
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 2097
            Extra: Using where
    *************************** 2. row ***************************
               id: 2
      select_type: DERIVED
            table: score
             type: range
    possible_keys: score
              key: score
          key_len: 4
              ref: NULL
             rows: 3750
            Extra: Using where; Using index
    2 rows in set (0.00 sec)
    

    両方のクエリコンポーネント(内部のDERIVED クエリと外部のBETWEEN ただし、ランクの悪いプレーヤーの場合、制約)は遅くなり、タイミングの制約に大きく違反します。

    [email protected] [kris]> set @count = 0; select * from ( select *, @count := @count + 1 as rank from score where score >= 0 order by score desc ) as t;
    ...
    2097152 rows in set (3.56 sec)
    

    記述的アプローチの実行時間は安定しています(テーブルサイズのみに依存します):

    [email protected] [kris]> select p.*, count(*) as rank 
       from score as p join score as s on p.score < s.score 
       where p.player_id = 1134026;
    +-----------+-------+---------+
    | player_id | score | rank    |
    +-----------+-------+---------+
    |   1134026 |     0 | 2097135 |
    +-----------+-------+---------+
    1 row in set (0.53 sec)
    

    お電話。



    1. MySQLでパイプ連結演算子を有効にする方法

    2. Postgresで関数のオーバーロードを無効にする方法はありますか

    3. 行の各列の最後の既知の値を取得します

    4. LinuxにSQLServerAlwaysOn可用性グループを展開する