在线投票系统需要支持创建投票、用户参与投票、实时统计票数、防止重复投票等核心功能,合理的mysql数据库设计是系统稳定运行的基础。下面我们逐步拆解设计过程。

核心功能需求梳理
在动手设计数据库之前,先明确系统需要实现的核心功能:
- 支持创建投票主题,设置投票选项
- 用户可以查看投票详情和选项列表
- 用户可以对指定选项进行投票,每个用户同一投票只能投一次
- 实时展示每个选项的得票数
- 支持查看投票的参与总人数
mysql表结构设计
根据功能需求,我们需要设计三张核心表,分别是投票主题表、投票选项表、投票记录表。
1. 投票主题表 vote_topic
存储投票的基本信息,表结构如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int(11) | 主键,自增 |
| title | varchar(200) | 投票主题标题 |
| description | text | 投票描述 |
| start_time | datetime | 投票开始时间 |
| end_time | datetime | 投票结束时间 |
| create_time | datetime | 创建时间 |
2. 投票选项表 vote_option
存储每个投票对应的选项信息,表结构如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int(11) | 主键,自增 |
| topic_id | int(11) | 关联的投票主题id |
| option_content | varchar(500) | 选项内容 |
| vote_count | int(11) | 该选项的得票数,默认0 |
3. 投票记录表 vote_record
存储用户的投票记录,用于防止重复投票,表结构如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int(11) | 主键,自增 |
| topic_id | int(11) | 关联的投票主题id |
| option_id | int(11) | 用户选择的选项id |
| user_id | int(11) | 投票用户的id |
| vote_time | datetime | 投票时间 |
为了让同一用户同一投票只能投一次,我们需要给vote_record表添加唯一索引,索引字段为topic_id和user_id。
-- 创建唯一索引,防止重复投票 ALTER TABLE vote_record ADD UNIQUE INDEX idx_topic_user (topic_id, user_id);
核心功能实现示例
创建投票主题和选项
创建投票时,需要同时往vote_topic表和vote_option表插入数据,示例代码如下:
<?php
// 假设已经建立mysql连接,$conn为连接对象
function createVote($title, $description, $startTime, $endTime, $options) {
// 开启事务
mysqli_begin_transaction($conn);
try {
// 插入投票主题
$sql = "INSERT INTO vote_topic (title, description, start_time, end_time, create_time)
VALUES (?, ?, ?, ?, NOW())";
$stmt = mysqli_prepare($conn, $sql);
mysqli_stmt_bind_param($stmt, 'ssss', $title, $description, $startTime, $endTime);
mysqli_stmt_execute($stmt);
$topicId = mysqli_insert_id($conn);
// 插入投票选项
$optionSql = "INSERT INTO vote_option (topic_id, option_content, vote_count) VALUES (?, ?, 0)";
$optionStmt = mysqli_prepare($conn, $optionSql);
foreach ($options as $optionContent) {
mysqli_stmt_bind_param($optionStmt, 'is', $topicId, $optionContent);
mysqli_stmt_execute($optionStmt);
}
// 提交事务
mysqli_commit($conn);
return $topicId;
} catch (Exception $e) {
// 回滚事务
mysqli_rollback($conn);
return false;
}
}
// 调用示例
$options = ['选项一', '选项二', '选项三'];
createVote('最喜欢的编程语言', '投票选出你最喜欢的编程语言', '2024-01-01 00:00:00', '2024-12-31 23:59:59', $options);
?>
用户投票逻辑实现
用户投票时需要先校验投票是否在进行中,再校验用户是否已经投过票,最后插入投票记录并更新选项票数,示例代码如下:
<?php
function userVote($topicId, $optionId, $userId) {
// 1. 校验投票是否在进行中
$checkTopicSql = "SELECT id FROM vote_topic
WHERE id = ? AND start_time <= NOW() AND end_time >= NOW()";
$stmt = mysqli_prepare($conn, $checkTopicSql);
mysqli_stmt_bind_param($stmt, 'i', $topicId);
mysqli_stmt_execute($stmt);
$topicRes = mysqli_stmt_get_result($stmt);
if (mysqli_num_rows($topicRes) == 0) {
return '投票未开始或已结束';
}
// 2. 校验用户是否已经投过该投票
$checkRecordSql = "SELECT id FROM vote_record WHERE topic_id = ? AND user_id = ?";
$stmt = mysqli_prepare($conn, $checkRecordSql);
mysqli_stmt_bind_param($stmt, 'ii', $topicId, $userId);
mysqli_stmt_execute($stmt);
$recordRes = mysqli_stmt_get_result($stmt);
if (mysqli_num_rows($recordRes) > 0) {
return '您已经参与过该投票';
}
// 3. 开启事务执行投票操作
mysqli_begin_transaction($conn);
try {
// 插入投票记录
$insertRecordSql = "INSERT INTO vote_record (topic_id, option_id, user_id, vote_time)
VALUES (?, ?, ?, NOW())";
$stmt = mysqli_prepare($conn, $insertRecordSql);
mysqli_stmt_bind_param($stmt, 'iii', $topicId, $optionId, $userId);
mysqli_stmt_execute($stmt);
// 更新选项票数
$updateOptionSql = "UPDATE vote_option SET vote_count = vote_count + 1 WHERE id = ?";
$stmt = mysqli_prepare($conn, $updateOptionSql);
mysqli_stmt_bind_param($stmt, 'i', $optionId);
mysqli_stmt_execute($stmt);
mysqli_commit($conn);
return '投票成功';
} catch (Exception $e) {
mysqli_rollback($conn);
return '投票失败,请重试';
}
}
// 调用示例:用户id为1,给投票id为1的选项id为2投票
userVote(1, 2, 1);
?>
查询投票结果
查询投票结果时,需要关联查询投票主题、选项信息以及总投票人数,示例代码如下:
-- 查询指定投票的结果,包含选项内容和得票数
SELECT
vo.option_content,
vo.vote_count,
(SELECT COUNT(DISTINCT user_id) FROM vote_record WHERE topic_id = 1) AS total_voters
FROM vote_option vo
WHERE vo.topic_id = 1;
设计注意事项
在实际项目中,还需要注意以下几点:
- 如果投票参与人数较多,更新
vote_count字段时可以考虑使用redis缓存票数,定时同步到mysql,减轻数据库压力 - 投票记录表的数据量会随用户量增长,建议定期归档历史数据,或者对
topic_id、user_id建立普通索引提升查询效率 - 如果需要支持匿名投票,可以去掉
vote_record表的user_id字段,改用设备标识等方式防重复投票,但匿名场景的防刷票难度会更高