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_dateemployee_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_idattendance_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 实现考勤打卡的接口。到时候你会看到,今天设计的表结构到底好不好用。