必须一步到位创建message_db数据库和messages表,严格按业务初始化结构,确保字段类型正确、status加索引、utf8mb4编码,并验证连接权限与配置。

创建 message_db 数据库和 messages 表必须一步到位
很多人在搭建时只建库不建表,或字段类型写错(比如用 TEXT 存短消息、漏掉 status 枚举约束),导致后续推送逻辑查不到未读消息。正确做法是严格按业务需要初始化结构:
CREATE database message_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;USE message_db;CREATE table messages (id int AUTO_INCREMENT PRIMARY KEY, content VARCHAR(255) NOT NULL, status enum('unread','read') default 'unread', created_at timestamp DEFAULT CURRENT_TIMESTAMP);
注意:别用 TIMESTAMP 自动更新 created_at,否则状态变更时时间会被覆盖;utf8mb4 是必须的,否则 emoji 或某些中文会存成问号。
插入测试数据前先确认连接权限和用户密码
常见错误是本地开发用 root 连 mysql,但生产环境禁止 root 远程登录,结果 go run migrate/migrate.go 报 access denied for user 却卡在“数据库已存在”假象里。实操建议:
- 用
mysql -u your_username -p -h localhost手动连一次,验证账号密码和 host 权限 - 检查
conf/app.ini中的host是否写成127.0.0.1(部分 MySQL 安装默认禁用 localhost 的 socket 连接) - 如果用 docker 启 MySQL,确保容器端口映射正确:
-p 3306:3306,且应用配置里的host填容器名(如mysql)而非localhost
查询 unread 消息必须加索引,否则高并发下直接拖垮服务
刚上线时几条测试数据看不出问题,一旦日均消息量过万,select * FROM messages WHERE status = 'unread' 就会变慢查询。MySQL 默认不会给 status 字段自动建索引,必须手动补:
ALTER TABLE messages ADD INDEX idx_status (status);- 如果推送接口还按时间排序(如
ORDER BY created_at DESC),建议建联合索引:ADD INDEX idx_status_created (status, created_at) - 别在
content上建全文索引——消息推送几乎不搜内容,纯属浪费 I/O 和内存
用存储过程模拟队列出队要小心事务和锁
有人照搬 redis 的 pop 模式,用 MySQL 写 SELECT ... FOR UPDATE + delete 实现“取一条删一条”,但在高并发下容易死锁或漏消息。更稳妥的做法是:
- 用单条原子语句完成“标记为已读并返回”:
UPDATE messages SET status = 'read' WHERE id = (SELECT id FROM messages WHERE status = 'unread' ORDER BY id ASC LIMIT 1) AND status = 'unread'; - 再用
SELECT查刚才更新的那条(需配合LAST_INSERT_ID()或应用层记录 ID) - 避免用
TRIGGER自动推 Redis——触发器内不能开新连接,也难调试;改用应用层轮询 +INSERT ... SELECT转存到专用队列表更可控
真正麻烦的不是建库建表,而是当推送量涨到每秒上百条时,status 字段的行锁竞争会让整个表卡住——这时候才意识到,一开始没加索引、没规划好读写分离,比重装 MySQL 还费时间。