简介:一份基于Spring Boot、Layui与Shiro构建的Java医院管理系统(HIS)完整源码,适合医院信息化学习者、Java开发人员及毕业设计参考者,用来理解门诊、住院、系统管理等核心业务流程与权限控制实现。压缩包约199.26MB,共38171个文件,以js、ts、json、md等为主,包含前端页面脚本、Java后端代码、配置文档及小程序相关文件,覆盖挂号、处方划价、入院登记、缴费、药品记账、出院结算等模块。已有1043人学习,资料附带百度云盘中的视频教程与业务参考文档,便于快速上手。项目数据库共36张表,业务复杂度较高;门诊侧涵盖用户挂号、处方/项目划价、项目检查、药品缴费与门诊患者库,住院侧支持入院登记、住院缴费、药品/项目记账和出院结算,并包含菜单、角色、用户、图标及数据源监控等系统管理功能。通过重复就诊校验、转住院前费用结清判断等细节,可帮助读者掌握医院业务流程建模、Spring Boot整合Shiro权限管理及小程序开发要点,适合项目复现与二次开发。
1. HIS系统源码能不能直接跑:先分清“能演示”和“能上线”
做Java开发的,十有八九都被问过“有没有医院管理系统源码”,培训机构出来的第一反应是上网找一套HIS改巴改巴当项目经验。真到医院现场你会发现,市面流传的所谓HIS源码,绝大多数是“能演示”和“能上线”之间差着一整条河的半成品。这套Java医院管理系统HIS源码属于能落到本机跑、能拿来做二次开发底子、也能支撑小规模诊所信息化的那一类:后端是Spring Boot那一整套,带一个微信小程序端,外加一份安装教程,数据库脚本、启动步骤都齐。适合三类人:想把Java基础、Spring Boot、小程序联调串成完整项目的求职者;要给社区诊所做挂号收费系统的his实施工程师;以及做毕业设计不想从头造轮子的学生。它能解决的是“从0到1把HIS跑起来”这个最现实的诉求,至于能不能直接扛住三甲医院的并发,那是另外一个问题。
2. 读懂这套HIS源码的结构:Spring Boot + MyBatis-Plus + MySQL的模块与表设计
拿到任何一套源码,第一件事不是急着启动,而是先看包结构和数据库脚本。这套HIS的目录划分比较规矩,后端是一个标准的Spring Boot工程,前端小程序是独立的原生微信小程序工程,两者通过HTTP接口通信。如果连模块边界都没分清楚就启动,后面排错会非常被动。
2.1 源码目录结构与模块划分:看包名就能猜到业务流程
后端源码打开后,第一层是src/main/java下的包目录,按controller、service、mapper、entity四层划分,这是MyBatis-Plus项目的经典结构。实体类在entity包,对应数据库表;mapper包是数据访问层,配合MyBatis-Plus的BaseMapper,大部分单表CRUD不用手写SQL;service包放业务逻辑,挂号、收费、退药这些操作都在这里;controller包暴露给前端调用的REST接口。另外有一个config包,放拦截器、跨域配置、MyBatis-Plus分页插件,这个包决定了一堆接口能不能被小程序正常调用。
com.clinic.his ├── controller # 对外接口层,/api/patient、/api/register等 ├── service # 业务逻辑层,挂号事务、收费事务 ├── mapper # 数据访问层,继承BaseMapper ├── entity # 实体类,对应数据库表 ├── config # 拦截器、跨域、MyBatis-Plus配置 └── common # 统一返回结果、异常处理逻辑说明:controller只做参数接收和结果封装,业务计算全部下沉到service层,事务注解打在service方法上。这样的分层好处是,将来要加一个“号源锁”功能,不用动controller接口签名,只需要在service里加逻辑。
参数说明:common包里通常有个Result类,所有接口统一返回{code, msg, data}结构,小程序端判断code==200才取数据。如果你看到的返回结构不是这个,先改这里,而不是去改几十个接口。
看完包结构,再看application.yml。这里有几个关键配置项:数据源url、username、password,MyBatis-Plus的map-underscore-to-camel-case驼峰映射开关,还有server.port端口号,默认8080。真正动手前把这三样确认清楚,后面能省一半排错时间。
2.2 核心数据表:患者、挂号、处方、收费怎么关联
这套源码的数据库脚本通常叫his_db.sql或者类似的名字,导入后大概会看到十几张表。HIS系统看着复杂,核心逻辑其实就围绕几张主表转:患者表、医生表、挂号表、处方表、收费表、药品库存表。
| 表名 | 核心字段 | 作用 |
|---|---|---|
| patient | id, name, id_card, phone, age | 患者基本信息 |
| doctor | id, name, dept_id, title | 医生信息,关联科室 |
| register | id, patient_id, doctor_id, reg_date, fee, status | 挂号记录,状态0待诊1已诊2已退 |
| prescription | id, register_id, drug_id, quantity, amount | 处方明细,一条挂号多条药品 |
| charge | id, register_id, total_amount, pay_type, status | 收费记录,记录实收金额 |
| drug | id, name, stock, price, spec | 药品库存与价格 |
关联关系走的是业务主键串联:挂号表register.patient_id指向患者,register.doctor_id指向医生;医生看诊后在prescription里开药,处方表的register_id指向挂号记录;收费时生成一条charge,charge.register_id再指回挂号。也就是说,一条挂号记录,串起了“谁来就诊、看哪个大夫、开了什么药、收了多少钱”这条完整链路。
建表语句里需要特别留意两个字段的设计:register.status和charge.status。很多翻车现场就是前端把挂号记录显示出来了,但状态一直没更新,因为状态是0、1、2这种魔法数字,前端枚举没配全。我一般会在阅读脚本时先把这些状态码整理成一份枚举对照表,贴在小程序端的常量文件里。
2.3 权限模型:医生、护士、药师、管理员怎么控权
HIS跟普通CRUD系统最大的区别在于权限敏感:不是登录进去就能所有按钮都点的。这套源码的权限实现走的是经典的“用户-角色-菜单”三表模型,后端用一个拦截器统一校验请求头里的token,再根据当前用户的角色ID决定能不能访问某个接口。
用户表(sys_user) → 角色表(sys_role) → 菜单表(sys_menu) 用户ID绑定角色,角色ID绑定菜单列表逻辑说明:登录接口拿到用户名密码后,校验账户会返回一个token,后续所有请求在Header里带Authorization: token。后端拦截器每个请求都会先解析token,从Redis或者数据库里把用户角色拉出来,再对照这个接口要求的角色编码做放行或拒绝。角色一般分管理员、医生、收费员、药师四档,管理员拥有全部权限,医生只能写处方,收费员只能操作收费模块,药师只能看出入库。
参数说明:路由权限判断通常在config包的Interceptor里,如果你要临时放通某个接口做调试,不用改数据库,直接在WebMvcConfig里把该路径加到excludePathPatterns就行。但上线前记得删掉,这是血泪教训。
这里多说一句,给his实施工程师的一个实用技巧:拿到源码第一天,先登录管理员账号,把系统里每个角色点一遍,记下权限盲区,后面处理“为什么某科室看不见某某菜单”的需求时会快得多。
3. 把Java后端拉起来:从建库到Swagger接口验证的全过程
这套源码自带安装教程,但很多人按教程跑还是起不来,问题不在源码,在环境版本和配置细节。本机部署我建议按这个顺序走:装JDK和MySQL → 导入SQL → 改application.yml → 启动 → 验证接口。
3.1 环境准备:JDK 8、MySQL 5.7/8.0、Maven 3.6+的版本匹配
先说版本,这套HIS在JDK 8和Maven 3.6以上的环境下最稳。JDK 11理论上也能跑,但部分老版本依赖在JDK 11下会有模块化报错,比如java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException,不折腾,直接用JDK 8最省心。
MySQL建议用5.7或8.0,安装时字符集选utf8mb4,避免后面存入患者名字里的生僻字变成问号。MySQL 8有一点要注意:默认认证插件是caching_sha2_password,Spring Boot连接时会报认证协议错误,后面避坑章节详细说。
Maven装完后,在命令行确认一下环境变量生效:
java -version mvn -v mysql --version逻辑说明:这三个命令分别验证Java编译器、Maven构建工具、MySQL客户端是否都能被命令行直接找到。任何一个报“不是内部或外部命令”,都是环境变量没配好。
参数说明:Windows下配环境变量时,JAVA_HOME指到JDK安装目录,MAVEN_HOME指到Maven解压目录,PATH里追加%JAVA_HOME%\bin和%MAVEN_HOME%\bin。不要指到bin目录本身,这是新手最常见的配错点。
3.2 导入数据库脚本:初始化数据到底包含什么
在MySQL里建一个空库,然后把SQL脚本导进去。用命令行操作最直观:
mysql -u root -p CREATE DATABASE his_db CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; exit; mysql -u root -p his_db < /path/to/his_db.sql逻辑说明:第一步建库,指定字符集和排序规则,避免中文乱码;第二步把SQL文件重定向导入到刚建好的空库。导入过程如果大量报错,先看第一行错误信息,通常是某个表已经存在,或者SQL文件里自带USE语句指向了别的库名。
参数说明:COLLATE utf8mb4_general_ci是中文场景常用的排序规则,兼容性比utf8mb4_unicode_ci更宽泛。如果你是本机演示,用哪个排序规则影响不大;如果是部署到生产环境且有跨库数据交互需求,全库规则统一能少很多夜班电话。
导入完成后,登录MySQL确认一下表数量:
USE his_db; SHOW TABLES; SELECT COUNT(*) FROM sys_user;这里能看到初始化数据里带了一个管理员账号,通常是admin/admin123这类默认密码,具体以安装教程里写的为准。第一次登录后应立刻改掉,内部系统最容易出问题的不是SQL注入,而是默认密码流传太广。
3.3 修改application.yml:数据库连接与端口配置
打开src/main/resources/application.yml,重点改三个地方:数据源地址、用户名、密码。
spring: datasource: url: jdbc:mysql://localhost:3306/his_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: 你自己的密码 driver-class-name: com.mysql.cj.jdbc.Driver jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 server: port: 8080 mybatis-plus: configuration: map-underscore-to-camel-case: true逻辑说明:url里的每个参数都有它存在的理由。serverTimezone=Asia/Shanghai解决MySQL 8下时间字段差8小时的问题;useSSL=false关闭SSL握手,本机调试保平安;allowPublicKeyRetrieval=true配合MySQL 8的caching_sha2_password认证插件,否则连接会直接报Public Key Retrieval is not allowed。
参数说明:map-underscore-to-camel-case: true让数据库的patient_id自动映射成实体类的patientId,这套源码的实体类命名如果漏了这个配置,MyBatis-Plus 查出来全是null。如果改完仍然查不到数据,就去entity包里看看字段上有没有@TableField注解把映射名写死。
3.4 启动后端并用接口验证:第一次跑通看哪几个信号
配置改完,进入项目根目录,在命令行执行:
mvn spring-boot:run逻辑说明:Maven先拉依赖再启动Spring Boot。首次启动会因为下载依赖包耗时几分钟,进度走到Started Application in X.XX seconds才是启动成功,之前所有的日志都是噪音。
启动成功的标志性日志有三行:第一行是Tomcat初始化的端口号,确认是8080;第二行是Completed initialization;第三行是Started Application。看到第三行说明上下文没炸。然后打开浏览器访问接口文档页面,验证接口是否能通:
http://localhost:8080/doc.html如果源码集成了knife4j或Swagger,这个地址会列出全部接口清单;如果没有集成,就用下面这个命令验证最小接口:
curl http://localhost:8080/api/patient/list?page=1&size=10逻辑说明:curl发一个GET请求,如果接口通,返回的是JSON格式的分页数据,里面带着code:200和患者列表;如果返回404,大概率是项目上下文路径改过,比如server.servlet.context-path配了/his,访问地址就得加前缀。
参数说明:page和size是MyBatis-Plus分页插件的标准参数,page从1开始。有的接口参数名是current和pageSize,两者含义相同,只是命名不同,报参数错误时先去controller里看@RequestParam注解写的是什么名字。
到这里,后端已经能跑了。很多人在这一步就开始兴奋地打开小程序,然后遇到一连串联调问题,下一章说清楚小程序端怎么接上这套后端。
4. 小程序端联调:登录、挂号、下单这三条链路的对接细节
小程序端是原生微信小程序工程,拿到手后直接导入微信开发者工具,导入时选“不使用云服务”和“不使用模板”,因为这套小程序走的是自建后端接口,跟微信云开发没关系。整个小程序端最核心的三个链路是:微信登录换token、挂号页提交、个人信息查询,这三条通了,剩下的页面都是套模板。
4.1 静态代码提示:先在模拟器里看基建通不通
微信开发者工具打开工程后,第一件事不是点编译,而是先看两个地方:app.js里的全局变量,和utils/request.js里的请求封装。大多数HIS小程序的接口请求都会统一封装在request.js里,所有页面调用request()函数,而不是直接写wx.request,这样改接口地址只改一个文件就够了。
// utils/request.js const BASE_URL = 'http://localhost:8080' function request(path, method, data, header = {}) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + path, method: method, data: data, header: { 'Content-Type': 'application/json', 'Authorization': wx.getStorageSync('token') || '', ...header }, success(res) { if (res.data.code === 200) { resolve(res.data.data) } else { wx.showToast({ title: res.data.msg, icon: 'none' }) reject(res.data) } }, fail(err) { wx.showToast({ title: '网络异常', icon: 'none' }) reject(err) } }) }) } module.exports = { request, BASE_URL }逻辑说明:所有请求自动带上本地缓存的token,这是登录态保持的关键。后端统一返回code字段,每层页面不用重复判错,在封装里统一处理错误提示,代码能砍掉三分之一。
参数说明:BASE_URL是本机调试最重要的配置,默认是http://localhost:8080,真机调试时localhost会指向手机自己,所以手机预览必须把它改成电脑的局域网IP,比如http://192.168.1.100:8080。这一步忘了改,手机上一律是“网络异常”。
4.2 微信登录换取手机号:wx.login到后端code2Session
登录流程是微信小程序连接HIS后端绕不开的第一关。用户点“微信登录”后,小程序调wx.login拿到一个临时code,传给后端,后端拿这个code去微信服务器换openid和session_key,再生成自己的token返回给小程序。
小程序 wx.login() → 拿到code → 请求后端 /api/auth/login 后端 拿code + appid + secret 调微信 code2Session 接口 换取 openid → 查sys_user表有没有这个openid → 生成token返回前端// 小程序端登录页 wx.login({ success: (res) => { if (res.code) { request('/api/auth/login', 'POST', { code: res.code }) .then(data => { wx.setStorageSync('token', data.token) wx.setStorageSync('userInfo', JSON.stringify(data.userInfo)) wx.reLaunch({ url: '/pages/index/index' }) }) } else { console.log('登录失败', res.errMsg) } } })逻辑说明:wx.login返回的code有效期只有五分钟且只能用一次,所以登录接口必须实时获取,不能缓存。后端拿到code后去微信官方接口换openid是标准做法,这个步骤后端日志会打印openid,排错时盯住这行日志,如果连openid都没打印,说明code传丢了或者appid密钥不对。
参数说明:后端appid和secret配置在application.yml里,对应微信公众平台里“小程序”的AppID和AppSecret。注意这是小程序AppID,不是公众号AppID,两者长得一样但对着不同的后台。确认方法:微信公众平台后台“开发管理-开发设置”里复制的那一串才是对的。
很多这套源码的定制需求里,用户点登录后要求自动带出手机号。实现方式有两种:老接口getPhoneNumber返回动态令牌传给后端换手机号,新接口走手机号快捷验证组件。后端处理方法一致:拿到手机号后,去sys_user表按手机号找用户,找不到就自动创建一个“手机号用户”绑定openid,第一次登录的账号密码置空,这就是所谓的“静默注册”。
4.3 挂号下单:前端传参和后端校验的两个关键点
挂号页面是这套系统业务价值最高的页面,因为医院里最忙碌的场景就是号源分配。前端提交的挂号单,核心字段是患者ID、医生ID、挂号日期、号源时间段。
// POST /api/register/create 请求体 { "patientId": 36, "doctorId": 12, "regDate": "2024-05-20", "timeSlot": "AM", "regType": 1, "fee": 15.00 }逻辑说明:timeSlot字段常用两个值,AM和PM代表上午和下午的号段;regType表示普通号还是专家号,不同号型对应不同挂号费。后端在service层要做三层校验:号源是否存在、这个医生当天这个号段是否还有余号、这个患者今天有没有挂过同一个医生的号。
参数说明:fee这个字段前端传了后端也会再次从数据库里查出医生对应的挂号费覆盖掉,防止用户篡改价格。这是医疗系统的硬性要求——金额一律以服务端计算为准。如果你在做二次开发,记住这条规则,否则上线第一天就会被人用改包价的方式薅羊毛。
第二层校验是防止重复挂号的,后端service层通常这样处理:先在register表里查今天这个患者对这个医生有没有status=0(待诊)的记录,有就直接拒绝。并发场景下单靠查询不够,需要给这张表加一个唯一索引(patient_id, doctor_id, reg_date),生产环境这套源码几乎全会在这一步加唯一索引,不然连点两次“提交”就产生两条重复挂号。
4.4 动态标题与页面栈:小程序端几个高频改动的落点
页面调通之后,实施中最常改的是页面标题和登录态跳转。挂号成功页如果要在不同状态下显示不同标题(比如“挂号成功”和“已退号”),用wx.setNavigationBarTitle在onLoad里动态设置。退出登录则要记得清空本地缓存的token再跳回登录页,这套源码里退出接口返回值跟登录接口结构一致,前端照抄登录逻辑反着写就行。
小程序端到这里基本能跑通“查看首页科室列表 → 选医生 → 提交挂号 → 查看挂号记录”的完整闭环。接下来是重头戏:部署到服务器时,一堆环境问题会接踵而至,下一章把最常见的坑一次性讲透。
5. 安装部署避坑指南:从本机跑通到服务器上线的12个一致性坑
本机能跑,不代表服务器能跑;服务器能跑,不代表手机能访问。我把这套HIS从本机到Linux服务器部署过程中最常踩的坑汇总成五条,每一条都是真实发生过的事故,按“现象 → 原因 → 解决”的顺序说。
5.1 MySQL 8密码加密方式导致后端启动直接失败
现象:后端启动日志报错java.sql.SQLException: Public Key Retrieval is not allowed,或者Client does not support authentication protocol requested by server; consider upgrading MySQL client。原因:MySQL 8默认认证插件是caching_sha2_password,而JDBC驱动和它握手时默认不允许传输公钥。解决:在application.yml的JDBC URL上加两个参数,allowPublicKeyRetrieval=true&useSSL=false。如果是老项目用的mysql-connector-java 5.x驱动,直接换成8.x版本的驱动包,一劳永逸。
url: jdbc:mysql://localhost:3306/his_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true5.2 服务器端口通了,但手机小程序连不上
现象:本机浏览器访问http://服务器IP:8080能打开Swagger,手机小程序却提示“网络异常”或request:fail。原因:微信小程序要求请求地址必须是HTTPS,且域名要在小程序后台配置为request合法域名;直接用IP加HTTP,线上版本会直接被微信拦截。解决:本地开发时,在微信开发者工具右上角“详情-本地设置”勾选“不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书”,真机预览时手机和电脑连同一WiFi就能访问。上线时则必须准备一个备案过的域名,配好HTTPS证书,在小程序后台“开发管理-服务器域名”里把request合法域名填进去。
5.3 数据库时间比实际慢8小时,挂号日期全错了
现象:小程序里挂号记录显示的时间比真实时间晚了8小时。原因:JDBC连接字符串里没设置时区,MySQL的serverTimezone默认用UTC,比北京时间晚8小时。解决:URL上强制加serverTimezone=Asia/Shanghai,同时注意jackson.time-zone=GMT+8也要配置,否则接口返回的JSON时间字符串还是会转成UTC。改完这两处,重启后端再查一次数据,确认时间对上了再继续往下做。
5.4 前端传参老报空指针,JSON字段对不上
现象:接口文档里写的是patientId,前端传了也能查到数据,但换了另一页面的接口就报patientId is null。原因:Java实体类用的是驼峰命名patientId,而建表语句的字段是patient_id,某个接口的@RequestBody直接接收JSON并绑定实体时,如果JSON字段名是patient_id而不是patientId,MyBatis-Plus虽然能映射数据库字段,但Jackson在做JSON反序列化时不去做下划线转驼峰,绑定就失败了。解决:统一前后端字段风格,小程序端全部用驼峰;或者在application.yml里给Jackson加配置spring.jackson.property-naming-strategy: SNAKE_CASE,完全不用,因为一旦开启,所有接口返回的字段全变下划线,影响面太大。我一般的选择是:保持前端驼峰,后端不要动全局配置。
5.5 Redis没启动,后端整体起不来
现象:启动日志到一半报Unable to connect to Redis,后端直接退出。原因:这套源码的token和验证码缓存依赖Redis,如果不启动Redis,Spring容器初始化时创建Redis连接池的Bean就会失败,整个应用启动不了。解决:本地装一个Windows版Redis或者用Docker跑一个Redis实例,默认端口6379,启动后回到第3章的启动流程即可。如果本机是在做纯功能演示不想额外装Redis,也可以把这个Bean改为内存缓存替代,但这就属于二次开发范围了,建议还是老老实实把Redis装起来,生产环境早晚要用。
现象里有一个共同点:99%的启动失败都是配置不一致造成的,不是源码本身的问题。所以在执行mvn spring-boot:run之前,我强烈建议你先把下面这张检查表过一遍:端口号有没有冲突、MySQL密码和yml里是不是一致、Redis起没起来、JDK版本是不是8。全部确认后启动,再失败就逐行看日志,而不是去网上复制一堆玄学依赖进来。
6. 给HIS加一张统计报表并接入“我的”页面:一套能复制到任意模块的路线
后端和小程序都能跑了,接下来最常用的需求是“加模块”:比如领导要一张“每个科室本周就诊人次”的统计报表,从数据库设计到小程序展示,整条链路是固定的,我把这套操作顺序讲清楚,照着它复制到任意模块都能少走弯路。
第一步在MySQL里写统计SQL,先在客户端里验证结果再进代码:
SELECT d.dept_name, COUNT(r.id) AS visit_count FROM register r LEFT JOIN doctor d ON r.doctor_id = d.id WHERE r.reg_date >= DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY d.dept_name ORDER BY visit_count DESC;第二步在Java后端的mapper层加方法,MyBatis-Plus项目里用注解SQL最省事:
@Select("SELECT d.dept_name AS deptName, COUNT(r.id) AS visitCount " + "FROM register r LEFT JOIN doctor d ON r.doctor_id = d.id " + "WHERE r.reg_date >= DATE_SUB(CURDATE(), INTERVAL 7 DAY) " + "GROUP BY d.dept_name ORDER BY visitCount DESC") List<Map<String, Object>> countVisitByDept();逻辑说明:统计报表的返回值用List<Map<String, Object>>而不新建实体类,是因为报表结构经常调整,硬编码实体类会导致每次改字段都改Java文件,不值得。前端拿到JSON直接渲染,字段名和SQL别名一一对应。
参数说明:DATE_SUB(CURDATE(), INTERVAL 7 DAY)是MySQL计算近7天数据的标准写法;如果要改成当月统计,换成DATE_FORMAT(r.reg_date, '%Y-%m') = DATE_FORMAT(CURDATE(), '%Y-%m')。这两种写法本系统里我都实测过,第一种在数据量到十万级时记得在reg_date上加索引,否则会全表扫。
第三步在controller里暴露一个接口,返回统一格式的JSON;第四步在小程序utils/request.js的request方法上直接复用,调用接口后渲染在“我的”页面的统计卡片上。整个流程是从SQL到接口到前端渲染的完整链路:改SQL、加mapper、加service、加controller、小程序页面调接口,五步走完。这套路线我复制到过药品库存预警、收费员日结、科室排班等多个模块,每轮最多半天。但那以后我强制自己每次加统计SQL前先跑一遍EXPLAIN看索引,原因是一次上线前一天,“我的”页面统计接口到数据量上来时响应从300ms飙到3秒,把“科室就诊人次”的锅甩给了“网络波动”,排查了一个下午最后发现是reg_date没走索引。从那以后凡是涉及日期范围的统计,我都要先看执行计划再往代码里放。希望这篇笔记和这套源码,能让你在跑通HIS的路上少一点查日志到凌晨的经历,能帮你把这个过程压缩到半天以内,那就值了。
本文还有配套的精品资源,点击获取