# 面试问题:如果有一张一亿用户的用户表,你会怎么设计?

# 问题分析

我们在拿到这个问题的时候,需要先分析一下,正常用户表有哪些字段,然后根据这些字段来设计表结构。这样我们就有了一张基础的用户表。

有了用户表之后,就要解决一亿用户的存储问题,一亿用户肯定不能存储一张表中,这样会导致性能问题。这个时候就需要使用分库分表、或者使用分区表。总之,目的是将数据范围缩小,提高查询性能。

解决掉数据存储的问题之后,我们还要回到业务中,一亿用户的系统,对于数据库的操作肯定很频繁。这个时候就需要使用数据库的索引来提高查询性能、读写分离分担数据库压力、以及使用缓存系统来提高系统的响应速度等。

最后建立完善的监控体系,监控数据库的性能、慢查询、异常等,保证系统的稳定性。

# 回答

# 用户表的设计

基础的用户表需要包含用户id、用户名、密码、手机号、邮箱、注册时间、最后登录时间、状态等字段。以及数据库的一些基础字段,比如创建时间、更新时间、删除时间等。

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(50) NOT NULL COMMENT '密码',
  `phone` varchar(20) NOT NULL COMMENT '手机号',
  `email` varchar(50) NOT NULL COMMENT '邮箱',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  `delete_time` datetime DEFAULT NULL COMMENT '删除时间',
  `status` tinyint(4) NOT NULL COMMENT '状态',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_username` (`username`),
  UNIQUE KEY `idx_phone` (`phone`),
  UNIQUE KEY `idx_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
  • id字段使用bigint类型,因为用户量很大,使用bigint可以保证存储用户id的位数。
  • usernamephoneemail字段使用varchar类型,因为用户名、手机号、邮箱都是字符串类型,使用varchar可以保证存储的灵活性。
  • password字段使用varchar类型,因为密码也是字符串类型,使用varchar可以保证存储的灵活性。
  • status字段使用tinyint类型,因为状态只有两种,使用tinyint可以保证存储的灵活性。

有了基础表之后,我们可以对字段类型进行优化:

  • create_timeupdate_timedelete_time字段使用timestamp类型,因为时间戳可以保证存储的精确性。减少存储空间。还可以使用CURRENT_TIMESTAMPON UPDATE CURRENT_TIMESTAMP等默认值,减少数据库操作。

优化后的表结构:

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(50) NOT NULL COMMENT '密码',
  `phone` varchar(20) NOT NULL COMMENT '手机号',
  `email` varchar(50) NOT NULL COMMENT '邮箱',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `delete_time` timestamp NULL DEFAULT NULL COMMENT '删除时间',
  `status` tinyint(4) NOT NULL COMMENT '状态',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_username` (`username`),
  UNIQUE KEY `idx_phone` (`phone`),
  UNIQUE KEY `idx_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

# 数据库的存储设计

# 分库分表

由于用户量很大,单库单表无法满足需求,需要使用分库分表来提高数据库的存储能力。分库分表的方式有很多种,比如按照用户id取模、按照用户注册时间取模等。在这里我们按照用户id取模的方式进行分库分表。

首先我们需要一张用户id生产表,用于生成用户id。表结构如下:

CREATE TABLE `user_id` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户id生产表';

每次新增用户时,先从用户id生产表中获取一个用户id,然后插入到用户表中。

分布分表解决了数据的存储和查询问题,但是如何保证唯一字段的唯一性呢?这个就需要在业务实现上进行控制了。例如使用redis的布隆过滤器来保证用户名、手机号和邮箱的唯一性等。

# 分区表

分区表可以将数据按照一定的规则分散到多个物理文件中,从而提高查询性能。例如,我们可以按照用户ID进行分区,将用户数据分散到不同的分区中,从而提高查询性能。但是根据其他字段的查询性能可能会下降,因为分区表是根据分区键进行分区的,其他字段的查询需要扫描所有分区,所以要根据业务需求来选择是否使用分区表。针对这部分可以单独优化。

# 性能优化措施

  • 索引设计:根据查询需求,合理设计索引,避免全表扫描。例如,根据用户名、手机号、邮箱等字段进行索引设计,提高查询效率。
  • 数据库连接池:使用数据库连接池,减少数据库连接的创建和销毁,提高数据库的访问效率。
  • 主从分离:使用主从分离,将数据库的读操作和写操作分离,提高数据库的并发处理能力。
  • 缓存优化:使用缓存来存储热点数据,如用户信息、用户行为等,从而减少数据库的访问压力。

# 监控和报警

  • 数据库监控:使用数据库监控工具,如MySQL自带的监控工具、Percona Toolkit等,实时监控数据库的性能和健康状况。
  • 系统监控:使用系统监控工具,如Prometheus、Grafana等,实时监控系统的性能和健康状况。