我正在为一款在线游戏开发一个服务器,它应该能够处理数百万玩家.现在,游戏需要排行榜,并且希望能够显示玩家当前的位置,以及可能靠近当前玩家位置的其他玩家,以及玩家朋友的位置.

我以前在MySQL中做过这样的事情,我知道这在技术上是如何可能的,但是我想既然这是很多在线游戏的常见做法,那么一定有专门用于这一目的的现有库或数据库?

有谁能告诉我哪种数据库最适合这些类型的查询,以及可能已经做了大量这方面工作的任何现有库?有API访问权限的第三方服务也可以.

希望能得到一些好的建议,谢谢!

编辑:

为了澄清,我需要一个数据库,它可以容纳数百万个条目(到目前为止,MySQL是很好的),我可以很容易地获得排名的结果.例如,如果我从"排行榜"表中获得特定的行,我需要知道该行的排名.无论数据库的大小如何,此查询都必须小于500ms.

或者,使用当前排名信息更新表的方法太长了,因为此更新查询不会锁定整个表,并且更新查询在30秒内运行.

任何关于使用哪种数据库/机制或第三方服务的 idea 都将不胜感激!

推荐答案

单个磁盘寻道的时间约为15ms,使用服务器级磁盘时可能会稍微少一些.小于500ms的响应时间限制您只能进行大约30次随机磁盘访问.那不是很多.

在我的小笔记本电脑上,我有一个开发数据库,其中包含

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

还有一个慢速笔记本电脑磁盘.我创建了一个分数表

root@localhost [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值.我们有

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

由于InnoDB索引中的数据存储在BTREE中,并且行指针(数据指针)是主键值,因此数据库在索引score中以score的顺序维护对(score, player_id),从而定义KEY (score)在内部结束为KEY(score, player_id).我们可以通过查看分数检索的查询计划来证明这一点:

root@localhost [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: scoreUsing index一起使用,这意味着不需要访问数据.

在我的笔记本电脑上,给定常数player_id的排名查询需要500毫秒:

root@localhost [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)

有了更多的内存和更快的机器,它可能会更快,但这仍然是一个相对昂贵的操作,因为计划很糟糕:

root@localhost [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)

如您所见,计划中的第二个表是索引扫描,因此查询速度会随着玩家数量的增加而线性降低.

如果您想要一个完整的排行榜,您需要go 掉WHERE子句,然后您会得到两次扫描和二次执行时间.所以这个计划完全崩溃了.

这里是进入程序化程序的时候了:

root@localhost [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,然后切掉我们感兴趣的部分,不过:

root@localhost [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)

root@localhost [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中没有索引.这限制了在外部查询中有效地实现的功能.

不过,请注意这两个查询是如何满足时间限制的.计划如下:

root@localhost [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个查询约束)都会变慢,然后严重违反您的时间约束.

root@localhost [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)

描述性方法的执行时间是稳定的(仅取决于表的大小):

root@localhost [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)

你的电话.

Database相关问答推荐

如何将初始数据放入数据库

mongodb聚合从另一个查询中获取值

Mongodb聚合$group,限制数组长度

苹果 ios 购买收据数据的可能最大长度是多少?

在 postgresql 中将列从字符串更改为字符串数组

Oracle 数据库统计信息应该多久运行一次?

SQLite 中内存数据库的优势

保存图像:文件还是 blob?

锁定机制(悲观/乐观)如何与数据库事务隔离级别相关?

MS Access 迁移的前端?

如何更改 MySQL DB 中所有表的前缀?

Android 在用户之间同步数据

为数据库应用程序留下审计跟踪/更改历史的有效策略?

数据完整性和数据一致性之间有什么区别吗?

如何将sqlite表从磁盘数据库复制到python中的内存数据库?

如何在 SQL Server 中总结时间字段

如何在 SQL Server 中生成并手动插入唯一标识符?

将查询限制为一条记录会提高性能吗

为什么实体框架连接需要元数据属性?

从 SQLite 导出到 SQL Server