Files
zyt/APPOINTMENT_INDEX_OPTIMIZATION.md
T
2026-03-23 18:02:16 +08:00

252 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 预约列表查询性能优化 - 索引方案
## 问题分析
预约列表查询涉及多表关联和复杂条件筛选,主要性能瓶颈:
1. 多表 LEFT JOIN(预约表、诊断表、管理员表)
2. 多条件筛选(状态、日期范围、患者姓名、医生姓名)
3. 权限过滤(医生角色、医助角色)
4. 复杂排序(状态 + 日期 + 时间)
5. 子查询(确认诊单、开方状态)
## 索引优化方案
### 1. 预约表 (zyt_appointment)
#### 单列索引
```sql
-- 医生ID索引(医生角色查询)
ALTER TABLE `zyt_appointment` ADD INDEX `idx_doctor_id` (`doctor_id`);
-- 患者ID索引(关联查询)
ALTER TABLE `zyt_appointment` ADD INDEX `idx_patient_id` (`patient_id`);
-- 状态索引(状态筛选)
ALTER TABLE `zyt_appointment` ADD INDEX `idx_status` (`status`);
-- 预约日期索引(日期范围查询)
ALTER TABLE `zyt_appointment` ADD INDEX `idx_appointment_date` (`appointment_date`);
```
#### 组合索引(重点优化)
```sql
-- 状态+日期+时间(用于排序,覆盖 ORDER BY)
ALTER TABLE `zyt_appointment` ADD INDEX `idx_status_date_time` (`status`, `appointment_date`, `appointment_time`);
-- 医生+状态+日期(医生角色常用查询)
ALTER TABLE `zyt_appointment` ADD INDEX `idx_doctor_status_date` (`doctor_id`, `status`, `appointment_date`);
```
**优化效果**
- `idx_status_date_time` 可以直接用于排序,避免 filesort
- `idx_doctor_status_date` 可以快速过滤医生的预约记录
### 2. 诊断表 (zyt_tcm_diagnosis)
```sql
-- 患者姓名索引(模糊搜索,前缀索引)
ALTER TABLE `zyt_tcm_diagnosis` ADD INDEX `idx_patient_name` (`patient_name`(20));
-- 医助ID索引(医助角色查询)
ALTER TABLE `zyt_tcm_diagnosis` ADD INDEX `idx_assistant_id` (`assistant_id`);
```
**说明**
- `patient_name` 使用前缀索引(20),节省空间且支持 LIKE 'xxx%' 查询
- 不支持 LIKE '%xxx%' 的优化,但可以减少扫描行数
### 3. 管理员表 (zyt_admin)
```sql
-- 管理员姓名索引(医生姓名搜索)
ALTER TABLE `zyt_admin` ADD INDEX `idx_name` (`name`(20));
```
### 4. 诊断查看记录表 (zyt_diagnosis_view_records)
```sql
-- 诊断ID+确认状态+删除时间(用于确认诊单查询)
ALTER TABLE `zyt_diagnosis_view_records` ADD INDEX `idx_diagnosis_confirmed` (`diagnosis_id`, `is_confirmed`, `delete_time`);
```
**优化效果**
- 加速 `WHERE diagnosis_id IN (...) AND is_confirmed = 1 AND delete_time IS NULL` 查询
- 覆盖索引,无需回表
### 5. 处方表 (zyt_prescription)
```sql
-- 诊断ID+删除时间(用于开方状态查询)
ALTER TABLE `zyt_prescription` ADD INDEX `idx_diagnosis_delete` (`diagnosis_id`, `delete_time`);
```
**优化效果**
- 加速 `WHERE diagnosis_id IN (...) AND delete_time IS NULL` 查询
### 6. 管理员角色表 (zyt_admin_role)
```sql
-- 管理员ID+角色ID(用于权限判断)
ALTER TABLE `zyt_admin_role` ADD INDEX `idx_admin_role` (`admin_id`, `role_id`);
```
**优化效果**
- 加速角色查询,快速判断用户权限
## 执行步骤
### 方式一:直接执行(推荐)
```bash
# 连接数据库
mysql -h 39.97.232.35 -u cs_zyt -p cs_zyt
# 执行索引创建脚本
source optimize_appointment_indexes.sql
```
### 方式二:安全执行(检查后添加)
```bash
# 执行安全脚本(会先检查索引是否存在)
source check_and_add_indexes.sql
```
### 方式三:phpMyAdmin
1. 登录 phpMyAdmin
2. 选择数据库 `cs_zyt`
3. 点击 SQL 标签
4. 复制 `optimize_appointment_indexes.sql` 内容
5. 点击执行
## 预期效果
### 优化前
- 查询时间:500ms - 2000ms
- 扫描行数:全表扫描
- 使用索引:PRIMARY 或无
### 优化后
- 查询时间:50ms - 200ms(提升 5-10 倍)
- 扫描行数:索引范围扫描
- 使用索引:组合索引
## 验证方法
### 1. 查看执行计划
```sql
EXPLAIN SELECT
a.*,
u.patient_name,
u.phone as patient_phone,
ad.name as doctor_name
FROM zyt_appointment a
LEFT JOIN zyt_tcm_diagnosis u ON a.patient_id = u.id
LEFT JOIN zyt_admin ad ON a.doctor_id = ad.id
WHERE a.doctor_id = 1
AND a.status = 1
AND a.appointment_date >= '2024-03-01'
ORDER BY a.status ASC, a.appointment_date ASC, a.appointment_time ASC
LIMIT 20;
```
**期望结果**
- `type`: ref 或 range(不是 ALL
- `key`: idx_doctor_status_date 或 idx_status_date_time
- `rows`: 较小的数字(不是全表行数)
### 2. 查看索引使用情况
```sql
-- 查看索引统计
SHOW INDEX FROM zyt_appointment;
-- 查看索引使用情况(需要开启慢查询日志)
SELECT * FROM sys.schema_unused_indexes WHERE object_schema = 'cs_zyt';
```
## 注意事项
1. **索引维护成本**:索引会增加 INSERT/UPDATE/DELETE 的开销,但对于查询为主的场景,收益远大于成本
2. **索引选择性**
- 高选择性字段(如 ID、日期)适合建索引
- 低选择性字段(如性别、状态)单独建索引效果不明显,但可以作为组合索引的一部分
3. **前缀索引**
- 对于长字符串字段(如姓名),使用前缀索引可以节省空间
- 前缀长度选择:能区分大部分数据即可(通常 10-20 个字符)
4. **组合索引顺序**
- 遵循"最左前缀"原则
- 将选择性高的字段放在前面
- 考虑查询条件的组合频率
5. **定期维护**
```sql
-- 优化表(重建索引)
OPTIMIZE TABLE zyt_appointment;
OPTIMIZE TABLE zyt_tcm_diagnosis;
```
## 进一步优化建议
### 1. 查询优化
```php
// 避免 SELECT *,只查询需要的字段
$query->field('a.id, a.patient_id, a.doctor_id, a.status, ...');
// 使用 whereIn 代替多个 OR
$query->whereIn('a.status', [1, 3]);
```
### 2. 分页优化
```php
// 使用延迟关联优化深分页
$subQuery = Appointment::where($conditions)
->field('id')
->limit($offset, $limit)
->buildSql();
$list = Appointment::alias('a')
->join([$subQuery => 'sub'], 'a.id = sub.id')
->with(['patient', 'doctor'])
->select();
```
### 3. 缓存优化
```php
// 缓存医生列表、角色信息等不常变化的数据
$roleIds = Cache::remember('admin_role_' . $adminId, function() {
return AdminRole::where('admin_id', $adminId)->column('role_id');
}, 3600);
```
## 监控指标
定期检查以下指标:
1. 慢查询日志(> 1s 的查询)
2. 索引使用率
3. 表大小和索引大小
4. 查询响应时间
```sql
-- 查看表和索引大小
SELECT
table_name,
ROUND(data_length / 1024 / 1024, 2) AS data_mb,
ROUND(index_length / 1024 / 1024, 2) AS index_mb,
ROUND((data_length + index_length) / 1024 / 1024, 2) AS total_mb
FROM information_schema.tables
WHERE table_schema = 'cs_zyt'
AND table_name IN ('zyt_appointment', 'zyt_tcm_diagnosis', 'zyt_admin')
ORDER BY (data_length + index_length) DESC;
```