MST

星途 面试题库

面试题:MariaDB高并发场景下用户统计功能的优化与实现

在高并发写入用户行为数据(如登录、浏览、购买等操作)到MariaDB数据库的场景下,为了高效统计用户相关数据(如日活、月活、不同行为的用户分布等),请设计数据库架构和优化策略,包括但不限于表结构设计、索引优化、缓存策略以及可能用到的存储引擎特性,并阐述如何保证数据一致性和统计结果的准确性。
11.7万 热度难度
数据库MariaDB

知识考点

AI 面试

面试题答案

一键面试

数据库架构设计

  1. 表结构设计
    • 用户行为表
      • 设计一个 user_actions 表,记录用户的每一个行为。
      • 字段包括:action_id(自增主键),user_id(用户标识,非空,可用于关联用户信息表),action_type(行为类型,如登录、浏览、购买等,使用枚举类型限定取值范围),action_time(行为发生时间,使用 DATETIME 类型),action_detail(行为详细信息,如购买商品的ID等,可根据实际情况选择合适的数据类型)。
      • 示例SQL:
CREATE TABLE user_actions (
    action_id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    action_type ENUM('login', 'browse', 'purchase') NOT NULL,
    action_time DATETIME NOT NULL,
    action_detail VARCHAR(255)
);
- **统计结果表**:
    - 设计不同的统计结果表,例如 `daily_active_users` 用于记录日活数据,`monthly_active_users` 用于记录月活数据,`action_distribution` 用于记录不同行为的用户分布。
    - `daily_active_users` 表字段:`date`(日期,`DATE` 类型),`active_users`(当天活跃用户数,`INT` 类型)。
    - 示例SQL:
CREATE TABLE daily_active_users (
    date DATE PRIMARY KEY,
    active_users INT
);
    - `action_distribution` 表字段:`action_type`(行为类型,与 `user_actions` 表中的 `action_type` 一致),`user_count`(该行为的用户数,`INT` 类型)。
    - 示例SQL:
CREATE TABLE action_distribution (
    action_type ENUM('login', 'browse', 'purchase') PRIMARY KEY,
    user_count INT
);
  1. 索引优化
    • user_actions 表的 user_idaction_typeaction_time 字段上创建联合索引。这样在查询特定用户、特定行为以及特定时间范围内的行为数据时能提高查询效率。
      • 示例SQL:
CREATE INDEX idx_user_action_time ON user_actions (user_id, action_type, action_time);
- 在统计结果表的相关查询字段上创建索引,如 `daily_active_users` 表的 `date` 字段索引,`action_distribution` 表的 `action_type` 字段索引,以加快查询统计结果的速度。
    - 示例SQL:
CREATE INDEX idx_date ON daily_active_users (date);
CREATE INDEX idx_action_type ON action_distribution (action_type);
  1. 存储引擎特性
    • MariaDB 常用的存储引擎有 InnoDB 和 MyISAM。在这种高并发写入和统计的场景下,InnoDB 更适合。
    • InnoDB 特性优势
      • 事务支持:InnoDB 支持事务,能保证数据的一致性。在写入用户行为数据时,可以将多个相关操作放在一个事务中,要么全部成功,要么全部回滚。例如,当记录购买行为时,可能涉及更新库存、记录订单等多个操作,事务能确保这些操作的原子性。
      • 行级锁:InnoDB 使用行级锁,在高并发写入时,锁的粒度更小,相比 MyISAM 的表级锁,能减少锁冲突,提高并发性能。

缓存策略

  1. 应用层缓存
    • 在应用程序端使用缓存,如 Redis。对于频繁查询的统计结果,如日活、月活数据,可以先从 Redis 中获取。如果 Redis 中没有,则查询数据库,将结果存入 Redis 并返回给应用。
    • 例如,查询日活数据时:
import redis
import pymysql

redis_client = redis.StrictRedis(host='localhost', port=6379, db = 0)
date = '2023 - 10 - 01'
active_users = redis_client.get(date)
if not active_users:
    conn = pymysql.connect(host='localhost', user='root', password='password', database='your_database')
    cursor = conn.cursor()
    cursor.execute("SELECT active_users FROM daily_active_users WHERE date = %s", (date,))
    result = cursor.fetchone()
    if result:
        active_users = result[0]
        redis_client.set(date, active_users)
    conn.close()
  1. 查询结果缓存
    • 在 MariaDB 层面,可以启用查询缓存(虽然在高并发写入场景下需谨慎使用)。对于不经常变化的查询语句(如统计不同行为的用户分布,在没有新用户行为写入时结果不会变化),查询缓存可以直接返回之前的查询结果,减少数据库的计算压力。
    • 开启查询缓存的配置示例:在 my.cnf 文件中设置 query_cache_type = 1query_cache_size = 64M(根据实际情况调整缓存大小)。

保证数据一致性和统计结果准确性

  1. 事务控制
    • 在写入用户行为数据时,使用事务来保证数据的完整性和一致性。例如,在记录购买行为时,涉及更新库存、记录订单和用户行为,这一系列操作应在一个事务中。
START TRANSACTION;
UPDATE products SET stock = stock - 1 WHERE product_id = 1;
INSERT INTO orders (user_id, product_id, order_time) VALUES (1, 1, NOW());
INSERT INTO user_actions (user_id, action_type, action_time, action_detail) VALUES (1, 'purchase', NOW(), 'product_id = 1');
COMMIT;
  1. 定期数据核对
    • 定期(如每天凌晨业务低峰期)对统计结果表和原始用户行为表进行核对。例如,重新计算日活数据并与 daily_active_users 表中的数据进行对比,如果不一致则进行修正。
    • 可以编写脚本实现这一过程,示例Python脚本:
import pymysql

conn = pymysql.connect(host='localhost', user='root', password='password', database='your_database')
cursor = conn.cursor()

# 重新计算日活数据
cursor.execute("SELECT COUNT(DISTINCT user_id) FROM user_actions WHERE DATE(action_time) = CURDATE()")
new_active_users = cursor.fetchone()[0]

# 获取现有日活数据
cursor.execute("SELECT active_users FROM daily_active_users WHERE date = CURDATE()")
old_active_users = cursor.fetchone()
if old_active_users:
    old_active_users = old_active_users[0]
    if new_active_users != old_active_users:
        cursor.execute("UPDATE daily_active_users SET active_users = %s WHERE date = CURDATE()", (new_active_users,))
        conn.commit()
else:
    cursor.execute("INSERT INTO daily_active_users (date, active_users) VALUES (CURDATE(), %s)", (new_active_users,))
    conn.commit()

conn.close()
  1. 日志记录
    • 开启 MariaDB 的二进制日志(binlog),它记录了数据库的所有变更操作。在出现数据不一致问题时,可以通过重放 binlog 来恢复数据到某个特定状态,从而保证数据一致性和统计结果的准确性。
    • my.cnf 文件中配置 log - bin = /var/lib/mysql/mysql - bin.log 开启 binlog,并设置合适的 server - id 等参数。