252 lines
6.8 KiB
Markdown
252 lines
6.8 KiB
Markdown
# 预约列表查询性能优化 - 索引方案
|
||
|
||
## 问题分析
|
||
|
||
预约列表查询涉及多表关联和复杂条件筛选,主要性能瓶颈:
|
||
|
||
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;
|
||
```
|