sql >> Database >  >> RDS >> Mysql

Ranking met miljoenen inzendingen

Een enkele schijf zoeken is ongeveer 15 ms, misschien iets minder met schijven van serverkwaliteit. Een responstijd van minder dan 500 ms beperkt je tot ongeveer 30 willekeurige schijftoegangen. Dat is niet veel.

Op mijn kleine laptop heb ik een ontwikkelingsdatabase met

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

en een trage laptopschijf. Ik heb een scoretabel gemaakt met

[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)

met willekeurige integerscores en opeenvolgende player_id-waarden. We hebben

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

De database onderhoudt het paar (score, player_id) in score volgorde in de index score , aangezien gegevens in een InnoDB-index worden opgeslagen in een BTREE, en de rijaanwijzer (gegevensaanwijzer) de primaire sleutelwaarde is, zodat de definitie KEY (score) wordt uiteindelijk KEY(score, player_id) intern. We kunnen dat bewijzen door te kijken naar het zoekplan voor het ophalen van een score:

[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)

Zoals je kunt zien, is de key: score wordt gebruikt met Using index , wat betekent dat er geen gegevenstoegang nodig is.

De rangschikkingsquery voor een gegeven constante player_id duurt precies 500 ms op mijn laptop:

[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)

Met meer geheugen en op een snellere box kan het sneller, maar het is nog steeds een relatief dure operatie, omdat het plan waardeloos is:

[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)

Zoals je kunt zien, is de tweede tabel in het plan een indexscan, dus de zoekopdracht vertraagt ​​lineair met het aantal spelers.

Als je een volledig scorebord wilt, moet je de waar-clausule weglaten en dan krijg je twee scans en kwadratische uitvoeringstijden. Dit plan stort dus volledig in.

Tijd om hier procedureel te gaan:

[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 |
...

Omdat dit een procedureel plan is, is het onstabiel:

  • Je kunt LIMIT niet gebruiken, want daarmee wordt de teller gecompenseerd. In plaats daarvan moet je al deze gegevens downloaden.
  • Je kunt niet echt sorteren. Deze ORDER BY clausule werkt, omdat het niet sorteert, maar een index gebruikt. Zodra je using filesort . ziet , zullen de tellerwaarden enorm afwijken.

Het is echter de oplossing die het dichtst in de buurt komt van wat een NoSQL (lees:procedurele) database zal doen als uitvoeringsplan.

We kunnen de NoSQL in een subquery stabiliseren en vervolgens het deel dat voor ons van belang is eruit snijden:

[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)

De subquery zal de voormalige resultatenset materialiseren als een ad-hoctabel met de naam t, waartoe we dan toegang hebben in de buitenste query. Omdat het een ad-hoctabel is, heeft deze in MySQL geen index. Dit beperkt wat efficiënt mogelijk is in de buitenste query.

Merk echter op hoe beide query's voldoen aan uw timingbeperking. Hier is het plan:

[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)

Beide zoekcomponenten (de binnenste, DERIVED query en de buitenste BETWEEN constraint) wordt echter langzamer voor slecht gerangschikte spelers en schendt dan uw timingbeperkingen op grove wijze.

[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)

De uitvoeringstijd voor de beschrijvende benadering is stabiel (alleen afhankelijk van de tabelgrootte):

[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)

Uw oproep.



  1. Kan vooraf gemaakte db niet kopiëren van activa

  2. Hoe krijg ik CakePHP bake om mysql.sock te vinden en MySQL te herkennen terwijl ik MAMP gebruik op Mac OSX?

  3. Milliseconden in mijn DateTime-wijzigingen wanneer opgeslagen in SQL Server

  4. Verkrijg het verschil tussen twee datums, zowel in maanden als dagen in sql