Wyszukiwanie pojedynczego dysku trwa około 15 ms, może trochę mniej w przypadku dysków klasy serwerowej. Czas odpowiedzi krótszy niż 500 ms ogranicza Cię do około 30 losowych dostępów do dysku. To niewiele.
Na moim małym laptopie mam programistyczną bazę danych z
example@sqldat.com [kris]> select @@innodb_buffer_pool_size/1024/1024 as pool_mb;
+--------------+
| pool_mb |
+--------------+
| 128.00000000 |
+--------------+
1 row in set (0.00 sec)
i powolny dysk laptopa. Utworzyłem tabelę wyników za pomocą
example@sqldat.com [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)
z losowymi wynikami liczb całkowitych i kolejnymi wartościami player_id. Mamy
example@sqldat.com [kris]> select count(*)/1000/1000 as mrows from score\G
*************************** 1. row ***************************
mrows: 2.09715200
1 row in set (0.39 sec)
Baza danych utrzymuje parę (score, player_id) w score kolejność w indeksie score , ponieważ dane w indeksie InnoDB są przechowywane w BTREE, a wskaźnik wiersza (wskaźnik danych) jest wartością klucza podstawowego, tak że definicja KEY (score) kończy się jako KEY(score, player_id) wewnętrznie. Możemy to udowodnić, patrząc na plan zapytań w celu uzyskania wyniku:
example@sqldat.com [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)
Jak widać, key: score jest używany z Using index , co oznacza, że dostęp do danych nie jest konieczny.
Zapytanie rankingowe dla danej stałej player_id trwa dokładnie 500 ms na moim laptopie:
example@sqldat.com [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)
Przy większej ilości pamięci i szybszym pudełku może być szybciej, ale nadal jest to stosunkowo kosztowna operacja, ponieważ plan jest do bani:
example@sqldat.com [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)
Jak widać, druga tabela w planie to skanowanie indeksu, więc zapytanie spowalnia liniowo wraz z liczbą graczy.
Jeśli chcesz mieć pełną tabelę wyników, musisz pominąć klauzulę where, a następnie otrzymasz dwa skany i kwadratowe czasy wykonania. Więc ten plan całkowicie się imploduje.
Czas przejść do procedury:
example@sqldat.com [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 |
...
Ponieważ jest to plan proceduralny, jest niestabilny:
- Nie możesz użyć LIMIT, ponieważ spowoduje to przesunięcie licznika. Zamiast tego musisz pobrać wszystkie te dane.
- Tak naprawdę nie możesz sortować. To
ORDER BYklauzula działa, ponieważ nie sortuje, ale używa indeksu. Jak tylko zobaczyszusing filesort, wartości liczników będą szalenie wyłączone.
Jest to jednak rozwiązanie, które jest najbliższe temu, co baza danych NoSQL (czytaj:proceduralna) zrobi jako plan wykonania.
Możemy jednak ustabilizować NoSQL w podzapytaniu, a następnie wyciąć część, która nas interesuje:
example@sqldat.com [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)
example@sqldat.com [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)
Podzapytanie zmaterializuje poprzedni zestaw wyników jako tabelę ad hoc o nazwie t, do której możemy następnie uzyskać dostęp w zapytaniu zewnętrznym. Ponieważ jest to tabela ad-hoc, w MySQL nie będzie miała indeksu. To ogranicza to, co jest możliwe efektywnie w zewnętrznym zapytaniu.
Zauważ jednak, że oba zapytania spełniają twoje ograniczenia czasowe. Oto plan:
example@sqldat.com [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)
Oba składniki zapytania (wewnętrzny, DERIVED zapytanie i zewnętrzny BETWEEN ograniczenie) będzie jednak wolniejsze dla graczy o słabym rankingu, a następnie rażąco naruszy ograniczenia czasowe.
example@sqldat.com [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)
Czas wykonania dla podejścia opisowego jest stabilny (zależny tylko od rozmiaru tabeli):
example@sqldat.com [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)
Twój telefon.