MST

星途 面试题库

面试题:MariaDB TABLE结构设计在高并发场景下的优化策略

假设有一个高并发的在线游戏平台,使用MariaDB存储用户游戏数据。主要涉及的表有users(user_id, username等基本用户信息)、games(game_id, game_name等游戏信息)以及game_records(record_id, user_id, game_id, score, play_time等用户游戏记录)。在高并发场景下,大量用户同时进行游戏,记录游戏成绩。请从TABLE结构设计角度出发,提出一套完整的优化策略,包括但不限于表的分区、索引设计、字段类型选择等,以确保数据库在高并发环境下能够高效稳定运行,且能快速查询用户游戏记录及统计信息。
45.9万 热度难度
数据库MariaDB

知识考点

AI 面试

面试题答案

一键面试

表结构设计优化策略

  1. 字段类型选择
    • user_id:使用INT UNSIGNED类型,假设用户数量不会超过20亿左右,该类型可以节省空间且性能较好。对于game_id同理,使用INT UNSIGNED
    • record_id:若业务上记录ID自增且不会有并发插入冲突问题,使用BIGINT UNSIGNED AUTO_INCREMENT,因为游戏记录数量可能较大,BIGINT能支持更大范围。
    • username:根据实际情况,如果用户名长度基本固定且较短,如不超过20个字符,可使用CHAR(20);若长度不固定,使用VARCHAR(255)VARCHAR类型根据实际存储数据长度分配空间,节省空间但查询时可能稍慢。
    • game_name:同样若长度基本固定,使用CHAR;否则用VARCHAR
    • score:如果分数范围较小,如0 - 10000,可使用SMALLINT UNSIGNED,占用空间小;若分数范围大,使用INT UNSIGNED
    • play_time:使用DATETIME类型记录游戏时间,精确到秒;如果只需要记录日期,使用DATE类型,可节省空间。
  2. 索引设计
    • users表
      • 为主键user_id添加主键索引,这是默认且必须的,能加速对单个用户基本信息的查询。
      • 若经常根据username查询用户,对username添加普通索引,如CREATE INDEX idx_username ON users (username);,提高按用户名查找用户的速度。
    • games表
      • game_id添加主键索引。
      • 若常根据game_name查找游戏,对game_name添加普通索引,如CREATE INDEX idx_game_name ON games (game_name);
    • game_records表
      • record_id添加主键索引。
      • user_id添加索引,因为经常需要根据用户ID查询其游戏记录,如CREATE INDEX idx_user_id ON game_records (user_id);
      • game_id添加索引,若需要统计某个游戏的所有记录等场景会用到,CREATE INDEX idx_game_id ON game_records (game_id);
      • 组合索引,若经常根据user_idplay_time查询用户在某个时间段内的游戏记录,可创建组合索引CREATE INDEX idx_user_time ON game_records (user_id, play_time);,注意索引字段顺序,将选择性高的字段放在前面。
  3. 表的分区
    • 按时间分区:由于play_time字段记录游戏时间,可按时间对game_records表进行分区。例如按月份分区,每个月的数据存储在一个分区中。
      CREATE TABLE game_records (
          record_id BIGINT UNSIGNED AUTO_INCREMENT,
          user_id INT UNSIGNED,
          game_id INT UNSIGNED,
          score INT UNSIGNED,
          play_time DATETIME,
          PRIMARY KEY (record_id, play_time)
      )
      PARTITION BY RANGE (YEAR(play_time) * 100 + MONTH(play_time)) (
          PARTITION p0 VALUES LESS THAN (202301),
          PARTITION p1 VALUES LESS THAN (202302),
          PARTITION p2 VALUES LESS THAN (202303),
          -- 以此类推,根据实际需要划分足够多的分区
          PARTITION pmax VALUES LESS THAN (MAXVALUE)
      );
      
      这样在查询某个时间段的游戏记录时,数据库可以快速定位到相关分区,减少扫描的数据量,提高查询性能。对于历史数据的清理也更方便,直接删除对应的分区即可。
    • 按用户ID分区:如果用户量非常大,也可以考虑按user_id进行分区。例如按user_id的取值范围进行分区,假设user_id从1开始连续递增,可按每10000个用户为一个分区。
      CREATE TABLE game_records (
          record_id BIGINT UNSIGNED AUTO_INCREMENT,
          user_id INT UNSIGNED,
          game_id INT UNSIGNED,
          score INT UNSIGNED,
          play_time DATETIME,
          PRIMARY KEY (record_id, user_id)
      )
      PARTITION BY RANGE (user_id) (
          PARTITION p0 VALUES LESS THAN (10000),
          PARTITION p1 VALUES LESS THAN (20000),
          PARTITION p2 VALUES LESS THAN (30000),
          -- 以此类推,根据实际用户量划分足够多的分区
          PARTITION pmax VALUES LESS THAN (MAXVALUE)
      );
      
      这种分区方式在查询单个用户或特定用户组的游戏记录时,能快速定位到对应的分区,提高查询效率。同时,在高并发写入时,不同用户的记录可以分散到不同分区,一定程度上减轻单个分区的写入压力。