我有一个使用三个表的查询。此查询是较大查询的一部分,该查询由于这个看似简单的查询导致Patient表上的全表扫描而导致性能下降。
该查询的目的是能够查看患者名单,其中包含医生姓名,治疗费用以及指定日期的费用。
我已经在Transactions中创建了一个关于PatientID的索引,并在患者中创建了一个关于DoctorID的索引,但是MySQL坚持要为患者进行全表扫描。
患者表(13,000行)
CREATE TABLE `Patients` (
`ID` int(10) NOT NULL,
`DoctorID` int(10) DEFAULT NULL,
PRIMARY KEY (`ID`),
KEY `DoctorID_Index` (`DoctorID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
医生表(42行)
CREATE TABLE `Doctors` (
`ID` int(10) NOT NULL,
`DoctorName` varchar(50) DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
交易表(~500,000行)
CREATE TABLE `Transactions` (
`Description` text,
`TransactionDate` datetime DEFAULT NULL,
`Amount` decimal(19,4) DEFAULT NULL,
`PatientID` int(10) DEFAULT NULL,
`ID` int(10) NOT NULL,
PRIMARY KEY (`ID`),
KEY `PatientID_Index` (`PatientID`),
KEY `TransactionDate_Index` (`TransactionDate`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
此查询大约需要1.5秒才能执行一天。这可能会发生什么?它没有使用患者主键索引?如何进一步优化此查询?
EXPLAIN SELECT P.ID, D.DoctorName, T.Description, T.Amount
FROM
`Doctors` AS D
INNER JOIN
`Patients` AS P
ON
D.ID = P.DoctorID
INNER JOIN
`Transactions` AS T
ON
P.ID = T.PatientID
WHERE Date(T.TransactionDate) IN ('2017-03-30')
[
{
"id" : 1,
"select_type" : "SIMPLE",
"table" : "P",
"partitions" : null,
"type" : "ALL",
"possible_keys" : "PRIMARY",
"key" : null,
"key_len" : null,
"ref" : null,
"rows" : 13748,
"filtered" : 100.00,
"Extra" : "Using where"
},
{
"id" : 1,
"select_type" : "SIMPLE",
"table" : "D",
"partitions" : null,
"type" : "eq_ref",
"possible_keys" : "PRIMARY",
"key" : "PRIMARY",
"key_len" : "4",
"ref" : "P.DoctorID",
"rows" : 1,
"filtered" : 100.00,
"Extra" : null
},
{
"id" : 1,
"select_type" : "SIMPLE",
"table" : "T",
"partitions" : null,
"type" : "ref",
"possible_keys" : "PatientID_Index",
"key" : "PatientID_Index",
"key_len" : "5",
"ref" : "P.ID",
"rows" : 34,
"filtered" : 100.00,
"Extra" : "Using where"
}
]
答案 0 :(得分:1)
我会从这开始:
<强> 1。创建外键。
您需要医生和患者之间以及患者和交易之间的FK。 MySQL(与其他数据库不同)会自动创建必要的索引并加快查询速度。
alter table `Patients` add (
constraint fk_patient_doctor foreign key (`DoctorId`)
references `Doctors` (`ID`)
);
alter table `Transactions` add (
constraint fk_tx_patient foreign key (`PatientID`)
references `Patients` (`ID`)
);
<强> 2。根据日期创建交易索引。
create index ix_tx_date on `Transactions` (`TransactionDate`);
这将加快按日期搜索的速度,希望使用全表扫描的范围扫描功能。
第3。修复您的疑问。
正如@UUeerdo所说,而不是:
Date(T.TransactionDate) IN ('2017-03-30')
尝试:
T.TransactionDate BETWEEN '2017-03-30 00:00:00' AND '2017-03-30 23:59:59'
<强> 4。更新MySQL统计信息。
analyze table `Transactions`;
analyze table `Patients`;
analyze table `Orders`;
答案 1 :(得分:1)
在条件中使用的字段值上使用任何函数几乎都会破坏性能(特别是因为它导致这些字段上的任何索引都不可用)。
而不是Date(T.TransactionDate) IN ('2017-03-30')
试
T.TransactionDate BETWEEN '2017-03-30 00:00:00' AND '2017-03-30 23:59:59'
此外,由于您要对T.TransactionDate
进行过滤并加入T.PatientID
,因此在两者上使用单个复合索引比在每个索引上使用单独的索引更有帮助。
答案 2 :(得分:1)
最小的改变是避免在函数中隐藏列(DATE
)。我更喜欢这种模式:
WHERE T.TransactionDate >= '2017-03-30'
AND T.TransactionDate < '2017-03-30' + INTERVAL 1 DAY
这将让优化器从T开始,可以更快地完成过滤
您已经拥有必要的索引
在TransactionDate
复合上制作索引无济于事
FK不会增加任何表现
InnoDB表几乎不需要ANALYZE
。
现在我希望EXPLAIN
按此顺序显示表:T,P,D。T将使用'TransactionDate'上的索引;其他人将使用他们的PRIMARY KEY
。应该没有全表扫描。