2. 数据库设计:考勤表结构设计、索引优化策略、数据字典编写、ER图绘制
好,咱们进入第二个大环节——数据库设计。说实话,考勤模块的数据库设计,看着简单,坑其实不少。我当年第一次做考勤系统时,就因为在表结构上偷了懒,结果上线后查询慢得像蜗牛爬,被运维同事追着骂了三天。嗯,从那以后,我对表设计就格外上心。
这一章,我会带着你一步步把考勤相关的表结构搭起来,顺便聊聊索引怎么优化、数据字典怎么写,最后再画个ER图让你一目了然。
2.1 考勤表结构设计:核心表与字段
考勤模块的核心,说白了就是记录「谁、在什么时间、做了什么动作」。比如打卡、请假、加班、出差,这些都是考勤事件。我个人习惯把这类事件拆成两张表:一张是考勤记录表,一张是考勤明细表。
为什么拆两张? 你想想看,每天每个人可能打多次卡(上班、下班、午休进出),如果全塞在一张表里,字段会变得很冗余。拆开之后,主表存每天的整体状态,明细表存每次打卡的时间点,查询起来也灵活。
2.1.1 考勤主表(attendance_main)
这张表记录每个员工每天的整体考勤情况。比如他今天是否迟到、早退、缺勤,以及最终的考勤状态。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | BIGINT | 主键,自增 |
| employee_id | INT | 员工ID,关联员工表 |
| attendance_date | DATE | 考勤日期 |
| status | TINYINT | 考勤状态:0-正常,1-迟到,2-早退,3-缺勤,4-请假,5-出差 |
| work_start_time | DATETIME | 实际上班打卡时间 |
| work_end_time | DATETIME | 实际下班打卡时间 |
| late_minutes | SMALLINT | 迟到分钟数 |
| early_leave_minutes | SMALLINT | 早退分钟数 |
| overtime_minutes | SMALLINT | 加班分钟数 |
| created_at | DATETIME | 创建时间 |
| updated_at | DATETIME | 更新时间 |
attendance_date 和 employee_id 设为联合唯一索引。这样能保证同一个人同一天只有一条主记录,避免重复数据。
2.1.2 考勤明细表(attendance_detail)
这张表记录每次打卡的精确时间。比如一个人一天打了4次卡(上班、午休出、午休回、下班),明细表里就有4条记录。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | BIGINT | 主键,自增 |
| main_id | BIGINT | 关联考勤主表ID |
| punch_time | DATETIME | 打卡时间 |
| punch_type | TINYINT | 打卡类型:0-上班,1-下班,2-午休出,3-午休回 |
| device_id | VARCHAR(50) | 打卡设备编号(可选) |
| created_at | DATETIME | 创建时间 |
main_id 的索引。结果查询某天某人的打卡明细时,全表扫描,慢得要命。所以记得给 main_id 加上普通索引。
2.2 索引优化策略:让查询飞起来
表设计好了,索引就是灵魂。考勤模块的查询场景很典型:按员工查、按日期查、按状态查。索引怎么建,直接决定了你的接口响应速度。
核心原则: 索引不是越多越好。写多读少的表,索引要克制;读多写少的表,索引可以适当多建。考勤表属于典型的「写一次、读多次」——每天只生成一条主记录,但HR可能反复查询统计。
2.2.1 必建索引清单
- 主表:
(employee_id, attendance_date)联合唯一索引 —— 这是查询最频繁的条件。 - 主表:
attendance_date单独索引 —— 用于按日期范围查询(比如查某个月的所有考勤)。 - 主表:
status索引 —— 用于按状态筛选(比如查所有迟到记录)。 - 明细表:
main_id索引 —— 用于关联查询。 - 明细表:
punch_time索引 —— 用于按时间范围查询打卡记录。
2.2.2 索引优化实战经验
我记得有一次,客户反馈考勤报表加载要十几秒。我一看慢查询日志,发现是 WHERE employee_id IN (...) AND attendance_date BETWEEN ... 这种查询走了全表扫描。原因很简单:employee_id 和 attendance_date 没有联合索引,MySQL 只能选其中一个索引,另一个条件就得回表过滤。
解决方案就是建了联合索引 (employee_id, attendance_date),查询时间直接降到 0.02 秒。嗯,有时候优化就是这么立竿见影。
attendance_date 放在前面,employee_id 放在后面。结果按员工查的时候索引失效了。记住:最左前缀原则——查询条件必须从索引最左边开始匹配。所以高频查询字段要放前面。
2.3 数据字典编写:给表写个说明书
数据字典这东西,很多人觉得麻烦就不写。但我在项目里吃过亏——半年后自己都忘了某个字段是干嘛的。所以我现在每设计一张表,都会同步写数据字典。说白了,就是给每个字段写个「身份证」。
数据字典一般包含:表名、字段名、类型、长度、是否为空、默认值、注释。我习惯用 Markdown 表格来写,然后导出成文档。
2.3.1 数据字典示例(考勤主表)
| 表名 | 字段名 | 类型 | 长度 | 是否为空 | 默认值 | 注释 |
|---|---|---|---|---|---|---|
| attendance_main | id | BIGINT | 20 | NO | 无 | 主键,自增 |
| attendance_main | employee_id | INT | 11 | NO | 无 | 员工ID,关联employee表 |
| attendance_main | attendance_date | DATE | 无 | NO | 无 | 考勤日期 |
| attendance_main | status | TINYINT | 4 | NO | 0 | 0-正常 1-迟到 2-早退 3-缺勤 4-请假 5-出差 |
| attendance_main | late_minutes | SMALLINT | 6 | YES | 0 | 迟到分钟数 |
COMMENT 把注释写进去。这样用 SHOW CREATE TABLE 就能看到完整的数据字典,省得再维护一份文档。
2.4 ER图绘制:让关系一目了然
ER图(实体关系图)是数据库设计的蓝图。画ER图不是为了好看,而是为了理清表之间的关系。考勤模块涉及的实体不多,但关系要搞清楚。
核心实体:
- 员工表(employee) —— 考勤的主体
- 考勤主表(attendance_main) —— 每天每人的考勤汇总
- 考勤明细表(attendance_detail) —— 每次打卡的原始记录
- 请假表(leave) —— 请假申请记录
- 加班表(overtime) —— 加班申请记录
关系说明:
- 员工 1 : N 考勤主表(一个员工有多天考勤)
- 考勤主表 1 : N 考勤明细表(一天考勤对应多次打卡)
- 员工 1 : N 请假表(一个员工可多次请假)
- 员工 1 : N 加班表(一个员工可多次加班)
画ER图时,我习惯用 plantuml 或者 draw.io。不过这里我用文字描述一下核心关系:
员工 (employee)
│
├── 1 : N ── 考勤主表 (attendance_main)
│ │
│ └── 1 : N ── 考勤明细表 (attendance_detail)
│
├── 1 : N ── 请假表 (leave)
│
└── 1 : N ── 加班表 (overtime)
好了,数据库设计这块就聊到这儿。下一章咱们开始写代码——用 Python 实现考勤打卡的接口。到时候你会看到,今天设计的表结构到底好不好用。