简介:本资源是一份面向高校数据库课程设计实践的机房管理系统完整开发资料包,适用于计算机专业本科生及初学数据库设计的学习者,聚焦解决信息化机房中设备、用户、预约与使用记录等多维度数据建模与管理问题。压缩包共3个文件(2个Word文档+1个SQL脚本),总大小155KB:其中《数据库设计说明书.doc》和《详细设计.doc》系统阐述ER模型构建、关系模式转换、范式优化(1NF/2NF/3NF)、索引设计及核心表结构说明;《学院机房管理系统.sql》提供可直接执行的建库建表与基础数据脚本,覆盖机房、设备、用户、预约、使用记录五大实体及其关联逻辑。目前已有753人学习下载,资料结构紧凑、理论结合实操,既可用于课程设计快速上手,也适合作为数据库原理教学的典型分析案例,帮助学习者扎实掌握从需求分析到SQL落地的全流程设计能力。
1. 机房管理系统:不是监控大屏,而是让设备状态、巡检任务、告警响应全在线闭环的运维中枢
你见过那种“系统上线即闲置”的机房管理平台吗?大屏上跳着温度曲线,但空调故障了没人收到通知;巡检表还在用Excel手填,漏检三次才被发现;UPS电池老化预警明明写了“剩余寿命<30天”,却没人跟进更换——这不是系统没做,是系统没真正嵌进运维动作里。机房管理系统的核心价值,从来不是把数据“搬上网”,而是把“人盯设备”变成“系统推任务、自动校验、闭环归档”。它面向的是某高校数据中心、某实验室超算集群、某公司本地IDC等真实场景中,每天要处理20+台服务器启停、5类环境传感器轮询、8类工单流转的基层运维人员。它不追求炫酷三维建模,但必须能扛住每分钟300+条温湿度上报、支持离线巡检APP拍照回传、允许非IT背景的值班员三步完成告警确认。本文就从零开始,用开源可落地的技术栈,把这套系统从“概念”拉到“能跑、能用、能追责”的生产级水位。
2. 用Python + Flask + SQLite快速搭出可运行的最小核心服务
2.1 为什么选Flask而不是Django或Spring Boot?
这不是技术情怀选择,而是基于真实约束的务实决策:某高校机房改造项目预算有限、交付周期压到6周、运维团队仅2人且无Java开发经验。Django虽自带Admin和ORM,但其默认用户体系与机房角色(值班员/管理员/巡检员)权限粒度不匹配,强行定制反而拖慢进度;Spring Boot则需额外部署JVM、配置Tomcat,对只有4核8G物理服务器的旧机房环境过于沉重。而Flask的轻量性体现在三个关键点:
- 启动快:
flask run命令1秒内响应,调试时改一行代码热重载,避免每次修改都要等30秒构建; - 依赖少:核心仅需
flask、flask-sqlalchemy、flask-login三个包,总安装体积<5MB,避免在老旧CentOS7上因glibc版本冲突导致pip install失败; - 路由自由:机房API天然分层(设备层
/api/device/123、巡检层/api/inspection/20240520、告警层/api/alert/unhandled),Flask的@app.route可直接按业务域拆分蓝图(Blueprint),比Django的URLconf更贴近运维人员理解习惯。
提示:别被“Flask太简陋”带偏——机房系统90%的请求是CRUD,剩下10%才是WebSocket实时推送。先用Flask把CRUD稳住,再用
flask-socketio补实时能力,比一上来就堆Spring Cloud更可控。
2.2 初始化项目结构与数据库模型
创建标准目录结构,重点在于隔离业务逻辑与数据访问:
rack-system/ ├── app.py # 主应用入口,只负责注册蓝图和启动 ├── models/ # 数据库模型定义 │ ├── __init__.py │ ├── device.py # 服务器/空调/UPS等设备实体 │ └── inspection.py # 巡检记录、照片路径、执行人 ├── routes/ # API路由实现 │ ├── __init__.py │ ├── device_routes.py # GET /api/devices, POST /api/devices │ └── alert_routes.py # GET /api/alerts?status=unhandled └── instance/ # 配置文件与SQLite数据库存放位置(避开git) └── config.py在models/device.py中定义设备核心字段(注意:不存实时状态,只存静态属性):
from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() class Device(db.Model): __tablename__ = 'devices' id = db.Column(db.Integer, primary_key=True) rack_id = db.Column(db.String(20), nullable=False) # 所属机柜编号,如"A01" position = db.Column(db.String(10), nullable=False) # U位,如"U12-U14" type = db.Column(db.String(20), nullable=False) # "server"/"ups"/"ac" brand = db.Column(db.String(50)) # 品牌,用于备件采购 model = db.Column(db.String(100)) # 型号,如"Dell R750" sn = db.Column(db.String(100), unique=True, nullable=False) # 序列号,唯一索引 ip = db.Column(db.String(15)) # 管理IP,可能为空(如普通空调) status = db.Column(db.String(10), default="normal") # 当前状态:"normal"/"warning"/"offline" last_updated = db.Column(db.DateTime, default=db.func.now(), onupdate=db.func.now()) def to_dict(self): return { "id": self.id, "rack_id": self.rack_id, "position": self.position, "type": self.type, "brand": self.brand, "model": self.model, "sn": self.sn, "ip": self.ip, "status": self.status, "last_updated": self.last_updated.isoformat() if self.last_updated else None }参数说明:
rack_id和position组合构成物理定位坐标,比单纯存“机房A区”更精准,后续生成机柜拓扑图时可直接映射;status字段不用于实时监控(那是Zabbix的事),而是人工巡检后标记的“当前可信状态”,比如巡检员发现UPS蜂鸣器响,手动将status改为warning并填写备注;last_updated同时设default和onupdate,确保首次插入和后续更新都能自动打时间戳,避免运维人员忘记填时间。
2.3 实现设备列表API与前端对接验证
在routes/device_routes.py中编写GET接口,支持分页与类型过滤:
from flask import Blueprint, jsonify, request from models.device import Device from app import db device_bp = Blueprint('device', __name__) @device_bp.route('/api/devices', methods=['GET']) def get_devices(): # 获取查询参数,带默认值防空 page = request.args.get('page', 1, type=int) per_page = request.args.get('per_page', 20, type=int) device_type = request.args.get('type') # 如?type=ups query = Device.query if device_type: query = query.filter(Device.type == device_type) pagination = query.paginate( page=page, per_page=per_page, error_out=False # 页码超限时返回空列表而非404 ) return jsonify({ "data": [d.to_dict() for d in pagination.items], "pagination": { "page": page, "per_page": per_page, "total": pagination.total, "pages": pagination.pages } })逻辑说明:
- 使用SQLAlchemy的
paginate()方法而非手写LIMIT OFFSET,因为后者在大数据量下性能陡降(10万条记录时第1000页查询耗时超2秒); error_out=False是血泪经验:运维人员手误输?page=9999时,系统不应崩溃,而应静默返回空data数组,前端展示“暂无设备”即可;- 返回结构明确区分
data(业务数据)和pagination(分页元信息),避免前端反复解析响应体。
验证方式:用curl直接调用,确认返回JSON格式正确:
curl "http://localhost:5000/api/devices?page=1&per_page=5&type=server"预期响应含data数组(5个服务器对象)和pagination对象(total为服务器总数)。若报错sqlite3.OperationalError: no such table: devices,说明未执行数据库迁移——此时需在app.py中添加初始化代码:
# app.py末尾 if __name__ == '__main__': with app.app_context(): db.create_all() # 首次运行创建所有表 app.run(debug=True)注意:db.create_all()仅用于开发环境,生产环境必须用flask-migrate做版本化迁移,否则上线后加字段会丢数据。
3. 接入真实设备数据:用Modbus TCP读取智能PDU电压电流,不依赖厂商SDK
3.1 为什么绕过厂商Web界面,直连Modbus?
某实验室机房的智能PDU(型号:APC AP8941)自带Web管理页,但存在三个硬伤:
- Web页每30秒刷新一次,无法满足“电压突变100ms内告警”的需求;
- 厂商API需申请Token且有效期仅24小时,运维人员得每天手动续期;
- 历史数据导出为CSV,无法实时关联到设备台账(如某台PDU的SN是AP8941-00123,但CSV里只写“Outlet1”)。
而Modbus TCP是工业设备通用协议,APC PDU开放端口502,寄存器地址公开(见APC官方文档《AP8941 Modbus Map v2.1》),用Python直接读取,数据流变成:PDU硬件 → Modbus TCP → Python脚本 → 写入SQLite device.status
全程不经过厂商云、不依赖浏览器、不产生额外授权成本。
3.2 用pymodbus读取PDU输入电压并写入数据库
安装依赖:
pip install pymodbus==3.5.2 # 注意:v3.6+移除了同步客户端,必须锁定v3.5.2编写scripts/pdu_monitor.py,每10秒轮询一次:
from pymodbus.client import ModbusTcpClient from pymodbus.exceptions import ConnectionException, ModbusIOException from models.device import Device from app import db import time from datetime import datetime def read_pdu_voltage(ip, port=502): """读取APC PDU输入电压(寄存器40001,保持型,单位0.1V)""" try: client = ModbusTcpClient(ip, port=port, timeout=3) if not client.connect(): raise ConnectionException(f"无法连接PDU {ip}") # 读取保持寄存器40001(地址0x0000),数量1个 result = client.read_holding_registers(address=0x0000, count=1, slave=1) if result.isError(): raise ModbusIOException(f"Modbus读取错误: {result}") # 转换为整数,再除以10得到实际电压(如3823 → 382.3V) raw_value = result.registers[0] voltage = raw_value / 10.0 return round(voltage, 1) except Exception as e: print(f"[{datetime.now().strftime('%H:%M:%S')}] PDU {ip} 读取失败: {e}") return None finally: client.close() def update_device_status(sn, voltage): """根据电压值更新设备状态""" device = Device.query.filter_by(sn=sn).first() if not device: print(f"警告:SN {sn} 在设备台账中未找到") return # 电压正常范围200-250V,超出则标为warning if voltage is None: device.status = "offline" elif 200 <= voltage <= 250: device.status = "normal" else: device.status = "warning" device.last_updated = datetime.now() db.session.commit() if __name__ == "__main__": # 配置:PDU IP与对应SN(从台账中查得) pdu_configs = [ {"ip": "192.168.10.101", "sn": "AP8941-00123"}, {"ip": "192.168.10.102", "sn": "AP8941-00456"} ] while True: for cfg in pdu_configs: voltage = read_pdu_voltage(cfg["ip"]) if voltage is not None: print(f"[{datetime.now().strftime('%H:%M:%S')}] {cfg['ip']} 电压: {voltage}V") update_device_status(cfg["sn"], voltage) time.sleep(10) # 每10秒轮询一次关键参数说明:
address=0x0000:APC文档明确输入电压存于保持寄存器40001,Modbus地址从0开始计数,故为0x0000;slave=1:APC PDU默认从站ID为1,若现场改过需同步调整;timeout=3:网络不稳定时避免脚本卡死,3秒无响应即报错重试;round(voltage, 1):原始值为整数(3823),除以10后保留1位小数,避免浮点精度污染数据库。
部署方式:
将脚本加入Linux系统服务,开机自启:
# 创建systemd服务文件 /etc/systemd/system/pdu-monitor.service [Unit] Description=PDU Voltage Monitor After=network.target [Service] Type=simple User=www-data WorkingDirectory=/opt/rack-system ExecStart=/usr/bin/python3 /opt/rack-system/scripts/pdu_monitor.py Restart=always RestartSec=10 [Install] WantedBy=multi-user.target启用服务:
sudo systemctl daemon-reload sudo systemctl enable pdu-monitor.service sudo systemctl start pdu-monitor.service注意:
pymodbus==3.5.2是当前唯一稳定支持同步TCP客户端的版本。曾试过v3.6.0,read_holding_registers返回None而不报错,导致电压永远为0,排查3小时才发现是库的breaking change。
4. 巡检任务闭环:从派单、执行到电子签名归档的全流程设计
4.1 为什么不用微信小程序扫码?而坚持原生APP?
某公司试点过微信扫码巡检:运维员扫机柜二维码→跳转H5页面→填写表单→上传照片。结果上线两周后弃用,原因很现实:
- 离线失效:机房核心区无WiFi,4G信号被金属机柜屏蔽,扫码后页面白屏;
- 照片模糊:H5调用手机摄像头分辨率受限,UPS接线端子细节拍不清;
- 无强制流程:员工可跳过“拍摄配电柜总开关”步骤直接提交,失去过程审计价值。
因此我们采用原生Android APP(用Kotlin开发),核心能力聚焦三点:
- 离线优先:扫码后所有表单、检查项、历史记录均缓存在本地SQLite,无网时照常填写;
- 硬件直控:调用相机API强制12MP模式,拍完自动压缩至1024×768(兼顾清晰度与传输速度);
- 防跳过逻辑:每个检查项设
is_required=true,未拍照或未勾选“确认无异常”则禁用提交按钮。
4.2 巡检任务数据结构与API设计
在models/inspection.py中定义巡检主表与明细表:
class InspectionTask(db.Model): __tablename__ = 'inspection_tasks' id = db.Column(db.Integer, primary_key=True) task_id = db.Column(db.String(32), unique=True, nullable=False) # 业务ID,如"INS-20240520-001" device_id = db.Column(db.Integer, db.ForeignKey('devices.id'), nullable=False) inspector = db.Column(db.String(20), nullable=False) # 执行人姓名 status = db.Column(db.String(10), default="pending") # pending/doing/done/failed created_at = db.Column(db.DateTime, default=db.func.now()) completed_at = db.Column(db.DateTime) class InspectionItem(db.Model): __tablename__ = 'inspection_items' id = db.Column(db.Integer, primary_key=True) task_id = db.Column(db.Integer, db.ForeignKey('inspection_tasks.id'), nullable=False) item_name = db.Column(db.String(100), nullable=False) # "UPS状态灯" expected = db.Column(db.String(200)) # 期望状态:"绿色常亮" actual = db.Column(db.String(200)) # 实际状态:"红色闪烁" photo_path = db.Column(db.String(200)) # 相对路径,如"photos/20240520/ins-001-1.jpg" remark = db.Column(db.Text) # 补充说明 is_passed = db.Column(db.Boolean, default=True) # 是否通过设计理由:
task_id用业务ID而非自增ID,方便运维人员口头沟通(“看下INS-20240520-001这个任务”);InspectionItem单独建表而非JSON字段,确保可按“所有UPS状态灯异常”的条件快速查询(SELECT * FROM inspection_items WHERE item_name='UPS状态灯' AND actual LIKE '%红色%');photo_path存相对路径,实际文件存于/opt/rack-system/static/photos/,Nginx直接配置location /photos/ { alias /opt/rack-system/static/photos/; },避免Python处理文件IO拖慢API。
4.3 APP提交巡检数据的API实现
在routes/inspection_routes.py中接收APP的POST请求:
import os from flask import request, jsonify, current_app from werkzeug.utils import secure_filename from models.inspection import InspectionTask, InspectionItem from app import db @inspection_bp.route('/api/inspections', methods=['POST']) def submit_inspection(): try: # 1. 解析JSON主体 data = request.get_json() if not data or 'task_id' not in data: return jsonify({"error": "缺少task_id"}), 400 # 2. 创建主任务记录 task = InspectionTask( task_id=data['task_id'], device_id=data['device_id'], inspector=data['inspector'], status="done", completed_at=datetime.now() ) db.session.add(task) db.session.flush() # 获取task.id用于外键 # 3. 处理明细项与照片 for item_data in data.get('items', []): # 保存照片(APP已base64编码,此处简化为接收文件流) if 'photo' in item_data: filename = secure_filename(f"{data['task_id']}-{item_data['item_name']}.jpg") filepath = os.path.join(current_app.config['PHOTO_FOLDER'], filename) # 实际项目中APP发multipart/form-data,此处用伪代码示意 # item_data['photo'].save(filepath) item_data['photo_path'] = f"photos/{filename}" # 4. 创建明细记录 item = InspectionItem( task_id=task.id, item_name=item_data['item_name'], expected=item_data.get('expected'), actual=item_data['actual'], photo_path=item_data.get('photo_path'), remark=item_data.get('remark', ''), is_passed=item_data.get('is_passed', True) ) db.session.add(item) db.session.commit() return jsonify({"success": True, "task_id": task.task_id}) except Exception as e: db.session.rollback() return jsonify({"error": str(e)}), 500关键逻辑:
db.session.flush()在提交前获取刚插入InspectionTask的ID,确保InspectionItem.task_id能正确关联;secure_filename()防止APP传入../../../etc/passwd.jpg这类路径遍历攻击;db.session.rollback()兜底:任一环节出错(如照片写入失败),整个事务回滚,避免出现“任务已创建但明细丢失”的脏数据。
5. 告警分级与响应闭环:从“弹窗提醒”到“自动派单+超时升级”的实战避坑
5.1 告警不等于日志:必须定义三级响应机制
很多团队把Zabbix告警直接推到企业微信,结果是:
- 一级告警(立即响应):UPS输出电压<190V(可能断电),要求5分钟内电话通知负责人;
- 二级告警(当日处理):机柜温度>35℃,要求当天18:00前提交散热整改方案;
- 三级告警(周期跟踪):硬盘SMART预测剩余寿命<7天,要求每周五同步更换进度。
若不做分级,所有告警挤在同一个群,运维员会养成“忽略红点”的肌肉记忆。因此我们在models/alert.py中增加level字段:
class Alert(db.Model): __tablename__ = 'alerts' id = db.Column(db.Integer, primary_key=True) device_id = db.Column(db.Integer, db.ForeignKey('devices.id')) level = db.Column(db.Enum('critical', 'warning', 'info'), default='warning') message = db.Column(db.Text, nullable=False) status = db.Column(db.String(10), default="unhandled") # unhandled/handling/done assigned_to = db.Column(db.String(20)) # 指派人姓名 created_at = db.Column(db.DateTime, default=db.func.now()) handled_at = db.Column(db.DateTime)5.2 自动派单逻辑:用Celery实现异步任务分发
告警触发后不能阻塞主线程,需用消息队列解耦。选用Celery(Redis作Broker)因其轻量且与Flask集成成熟:
# celery_config.py from celery import Celery def make_celery(app): celery = Celery( app.import_name, backend=app.config['CELERY_RESULT_BACKEND'], broker=app.config['CELERY_BROKER_URL'] ) celery.conf.update(app.config) return celery # app.py中初始化 celery = make_celery(app)定义派单任务:
# tasks/alert_tasks.py from celery import shared_task from models.alert import Alert from app import db import smtplib from email.mime.text import MIMEText @shared_task def assign_alert(alert_id, level): """根据告警级别自动指派并通知""" alert = Alert.query.get(alert_id) if not alert: return # 一级告警:电话+短信(调用第三方短信API) if level == 'critical': # 此处调用短信网关,伪代码 # send_sms(to="13800138000", content=f"CRITICAL: {alert.message}") alert.assigned_to = "值班主管" # 二级告警:邮件通知指定组 elif level == 'warning': msg = MIMEText(f"请处理告警:{alert.message}") msg['Subject'] = f"[机房告警] {alert.message[:20]}..." msg['From'] = "rack-system@company.com" msg['To'] = "it-ops@company.com" s = smtplib.SMTP('smtp.company.com') s.send_message(msg) s.quit() alert.assigned_to = "IT运维组" # 三级告警:仅记录,不通知 else: alert.assigned_to = "系统自动" alert.status = "handling" db.session.commit()在告警生成处触发任务:
# routes/alert_routes.py @alert_bp.route('/api/alerts', methods=['POST']) def create_alert(): data = request.get_json() alert = Alert( device_id=data['device_id'], level=data['level'], message=data['message'] ) db.session.add(alert) db.session.flush() # 异步派单,不阻塞API响应 assign_alert.delay(alert.id, data['level']) return jsonify({"alert_id": alert.id})5.3 告警响应避坑:5个让系统真正可用的关键细节
现象 → 原因 → 解决:
现象:告警重复发送,同一温度超限每分钟发10条邮件。
原因:Zabbix监控脚本每分钟调用一次/api/alerts,但未校验“相同设备+相同级别+10分钟内是否已有未处理告警”。
解决:在create_alert()开头加去重逻辑:
```python recent_alert = Alert.query.filter( Alert.device_id == data['device_id'], Alert.level == data['level'], Alert.status == "unhandled", Alert.created_at > datetime.now() - timedelta(minutes=10) ).first() if recent_alert: return jsonify({"warning": "10分钟内已存在相同告警"}), 200现象:APP上显示“告警已处理”,但数据库
handled_at字段为空。
原因:前端点击“确认处理”按钮后,只发了PATCH /api/alerts/123,但后端路由未实现handled_at更新。
解决:补全alert_routes.py中的PATCH方法,且必须校验status从unhandled变为done时才写handled_at:
```python @alert_bp.route('/api/alerts/ int:alert_id ', methods=['PATCH']) def update_alert(alert_id): alert = Alert.query.get(alert_id) data = request.get_json() if data.get('status') == 'done' and alert.status == 'unhandled': alert.handled_at = datetime.now() alert.status = data.get('status', alert.status) db.session.commit() return jsonify(alert.to_dict())现象:夜间无人值守时,一级告警未触发电话通知。
原因:assign_alert任务中硬编码了电话号码,但未区分工作日/节假日/夜班排班。
解决:新建staff_schedule表,存每日值班人及联系方式,任务中动态查表获取号码。现象:告警详情页打开慢,加载10秒以上。
原因:前端请求/api/alerts/123时,后端to_dict()方法试图关联查询设备名称,而devices表有10万条记录且sn字段无索引。
解决:给devices.sn加数据库索引,并在Alert.to_dict()中只返回device_id,前端需要设备名时再单独查。现象:超时升级失效,告警挂了24小时没人管。
原因:Celery定时任务check_overdue_alerts未设置max_retries=3,某次Redis宕机导致任务丢失。
解决:在任务装饰器中加重试:@shared_task(bind=True, max_retries=3, default_retry_delay=60),且每次重试记录日志。
6. 生产环境加固与持续演进:从“能用”到“敢用”的最后10%
6.1 数据库备份策略:用WAL模式+每日增量,避免锁表
SQLite在高并发写入时易出现database is locked错误,尤其当PDU监控脚本、巡检API、告警任务同时写devices表。解决方案是启用Write-Ahead Logging(WAL)模式,它允许多个读者与一个写者并发:
# 在app.py初始化DB后添加 @app.before_first_request def init_db(): db.create_all() # 启用WAL模式 db.session.execute("PRAGMA journal_mode=WAL;") db.session.commit()但WAL模式不解决灾难恢复——若服务器硬盘损坏,SQLite文件全丢。因此必须搭配外部备份:
- 每日全量备份:凌晨2点用
sqlite3 rack.db ".backup backup_$(date +%Y%m%d).db"生成压缩包,保留7天; - 每小时WAL归档:启用
PRAGMA wal_checkpoint(TRUNCATE),将WAL日志合并到主库,并将旧WAL文件(rack.db-wal)复制到备份目录; - 验证备份有效性:每周六执行
sqlite3 backup_20240520.db "PRAGMA integrity_check;",输出ok才视为成功。
血泪经验:某次备份脚本权限错误,连续5天生成的
backup_*.db全是0字节,直到磁盘报警才发现。现在所有备份命令后加&& echo "$(date): backup success" >> /var/log/rack-backup.log,用日志行数监控成功率。
6.2 权限最小化实践:让值班员只能看到自己机柜
机房有多个区域(A区/B区/C区),不同值班员只负责其中一块。若用RBAC模型过度设计,不如用数据行级权限(Row-Level Security):
- 在
Device模型中增加region字段("A"/"B"/"C"); - 在所有
GET /api/devices等接口中,强制注入filter(Device.region == current_user.region); - 关键操作(如修改设备状态)加二次确认:前端提交时附带
region参数,后端校验device.region == submitted_region,不一致则拒绝。
这样,A区值班员登录后,/api/devices永远只返回region="A"的设备,无需理解“角色”“权限集”等抽象概念,符合一线人员认知习惯。
6.3 未来演进方向:用TimescaleDB替代SQLite存时序数据
当前PDU电压、温湿度等时序数据全塞在SQLite,当单表超100万行时,SELECT * FROM sensor_data WHERE time > '2024-05-01'查询耗时从200ms升至8秒。解决方案是引入TimescaleDB(PostgreSQL的时序扩展):
- 将
sensor_data表迁移到TimescaleDB,启用自动分区(按天分块); - 用
continuous_aggregate物化视图预计算每小时平均温度,查询提速10倍; - 保留SQLite存设备台账、巡检记录等关系型数据,形成“SQLite + TimescaleDB”混合架构。
这不是为了炫技,而是当某高校机房扩容到200个传感器时,系统仍能支撑“查看过去30天温度趋势”的运营需求——而不用让运维员手动导出20个CSV再用Excel画图。
我坚持一个习惯:每次上线新功能,必在测试环境用ab -n 1000 -c 50 http://localhost:5000/api/devices压测,观察SQLite的busy_timeout是否被触发。如果出现database is locked,立刻回退方案,宁可功能晚一周,也不让生产环境卡顿。因为对值班员来说,“系统卡了”和“设备宕机”没有区别——都是风险。希望帮到你。
本文还有配套的精品资源,点击获取