数据库架构设计
- 表结构设计
- 用户行为表:
- 设计一个
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
);
- 索引优化
- 在
user_actions
表的 user_id
、action_type
和 action_time
字段上创建联合索引。这样在查询特定用户、特定行为以及特定时间范围内的行为数据时能提高查询效率。
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);
- 存储引擎特性
- MariaDB 常用的存储引擎有 InnoDB 和 MyISAM。在这种高并发写入和统计的场景下,InnoDB 更适合。
- InnoDB 特性优势:
- 事务支持:InnoDB 支持事务,能保证数据的一致性。在写入用户行为数据时,可以将多个相关操作放在一个事务中,要么全部成功,要么全部回滚。例如,当记录购买行为时,可能涉及更新库存、记录订单等多个操作,事务能确保这些操作的原子性。
- 行级锁:InnoDB 使用行级锁,在高并发写入时,锁的粒度更小,相比 MyISAM 的表级锁,能减少锁冲突,提高并发性能。
缓存策略
- 应用层缓存:
- 在应用程序端使用缓存,如 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()
- 查询结果缓存:
- 在 MariaDB 层面,可以启用查询缓存(虽然在高并发写入场景下需谨慎使用)。对于不经常变化的查询语句(如统计不同行为的用户分布,在没有新用户行为写入时结果不会变化),查询缓存可以直接返回之前的查询结果,减少数据库的计算压力。
- 开启查询缓存的配置示例:在
my.cnf
文件中设置 query_cache_type = 1
和 query_cache_size = 64M
(根据实际情况调整缓存大小)。
保证数据一致性和统计结果准确性
- 事务控制:
- 在写入用户行为数据时,使用事务来保证数据的完整性和一致性。例如,在记录购买行为时,涉及更新库存、记录订单和用户行为,这一系列操作应在一个事务中。
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;
- 定期数据核对:
- 定期(如每天凌晨业务低峰期)对统计结果表和原始用户行为表进行核对。例如,重新计算日活数据并与
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()
- 日志记录:
- 开启 MariaDB 的二进制日志(binlog),它记录了数据库的所有变更操作。在出现数据不一致问题时,可以通过重放 binlog 来恢复数据到某个特定状态,从而保证数据一致性和统计结果的准确性。
- 在
my.cnf
文件中配置 log - bin = /var/lib/mysql/mysql - bin.log
开启 binlog,并设置合适的 server - id
等参数。