Mysql
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Mysql

Xếp hạng với hàng triệu mục nhập

Một lần tìm kiếm đĩa duy nhất là khoảng 15ms, có thể ít hơn một chút với các đĩa cấp máy chủ. Thời gian phản hồi dưới 500ms giới hạn bạn ở khoảng 30 lần truy cập đĩa ngẫu nhiên. Đó không phải là nhiều.

Trên chiếc máy tính xách tay nhỏ bé của mình, tôi có một cơ sở dữ liệu phát triển với

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

và một đĩa máy tính xách tay chậm. Tôi đã tạo một bảng điểm với

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

với điểm số nguyên ngẫu nhiên và giá trị player_id tuần tự. Chúng tôi có

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

Cơ sở dữ liệu duy trì cặp (score, player_id) trong score thứ tự trong chỉ mục score , vì dữ liệu trong chỉ mục InnoDB được lưu trữ trong BTREE và con trỏ hàng (con trỏ dữ liệu) là giá trị khóa chính, do đó định nghĩa KEY (score) kết thúc là KEY(score, player_id) trong nội bộ. Chúng tôi có thể chứng minh điều đó bằng cách xem xét kế hoạch truy vấn để lấy lại điểm số:

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

Như bạn có thể thấy, key: score đang được sử dụng với Using index , nghĩa là không cần truy cập dữ liệu.

Truy vấn xếp hạng cho một player_id không đổi nhất định mất chính xác 500ms trên máy tính xách tay của tôi:

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

Với nhiều bộ nhớ hơn và trên một hộp nhanh hơn, nó có thể nhanh hơn, nhưng nó vẫn là một hoạt động tương đối tốn kém, bởi vì kế hoạch này tệ:

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

Như bạn có thể thấy, bảng thứ hai trong kế hoạch là bảng quét chỉ mục, do đó, truy vấn chậm lại tuyến tính với số lượng người chơi.

Nếu bạn muốn có một bảng thành tích đầy đủ, bạn cần bỏ đi mệnh đề where, sau đó bạn nhận được hai lần quét và thời gian thực hiện bậc hai. Vì vậy, kế hoạch này hoàn thành.

Thời gian để thực hiện thủ tục tại đây:

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

Vì đây là một kế hoạch mang tính thủ tục nên nó không ổn định:

  • Bạn không thể sử dụng LIMIT, vì điều đó sẽ bù lại bộ đếm. Thay vào đó, bạn phải tải tất cả dữ liệu này xuống.
  • Bạn thực sự không thể sắp xếp. ORDER BY này mệnh đề hoạt động, bởi vì nó không sắp xếp, nhưng sử dụng một chỉ mục. Ngay khi bạn thấy using filesort , các giá trị bộ đếm sẽ bị tắt rất nhiều.

Tuy nhiên, đây là giải pháp gần nhất với những gì cơ sở dữ liệu NoSQL (đọc:thủ tục) sẽ làm như một kế hoạch thực thi.

Tuy nhiên, chúng tôi có thể ổn định NoSQL bên trong một truy vấn con và sau đó cắt ra phần mà chúng tôi quan tâm:

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

Truy vấn con sẽ hiện thực hóa tập kết quả trước đây thành một bảng đặc biệt có tên là t, mà sau đó chúng ta có thể truy cập trong truy vấn bên ngoài. Bởi vì nó là một bảng đặc biệt, trong MySQL nó sẽ không có chỉ mục. Điều này hạn chế những gì có thể xảy ra một cách hiệu quả trong truy vấn bên ngoài.

Tuy nhiên, hãy lưu ý cách cả hai truy vấn thỏa mãn hạn chế thời gian của bạn. Đây là kế hoạch:

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

Cả hai thành phần truy vấn (bên trong, DERIVED truy vấn và BETWEEN Tuy nhiên, hạn chế) sẽ chậm hơn đối với những người chơi có xếp hạng kém, và sau đó vi phạm hoàn toàn các hạn chế về thời gian của bạn.

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

Thời gian thực thi cho phương pháp mô tả là ổn định (chỉ phụ thuộc vào kích thước bảng):

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

Cuộc gọi của bạn.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Chuyển tiếp cổng Vagrant cho Mysql

  2. Cố gắng xây dựng một lớp cơ sở dữ liệu tĩnh mà tôi có thể truy cập từ bất kỳ hàm nào bên ngoài lớp

  3. Một thiết kế cơ sở dữ liệu tốt (lược đồ) cho một cơ sở dữ liệu tham dự là gì?

  4. công cụ giám sát truy vấn mysql

  5. cách quản lý kết nối đến cơ sở dữ liệu được tạo động