【发布时间】:2014-01-23 15:23:21
【问题描述】:
我有一个可以离线使用的应用程序,它可以从本地和远程源对数据库进行更改。我有一个指标查询,每次写入数据库时都需要运行(实时更新仪表板),由于它可以在本地或远程完成,它可能应该由数据库触发。
基本上,每次事务完成时,我都会运行以下 SQL:
- (NSString *)selectDashboardActivities {
return [NSString stringWithFormat:@"SELECT a.*, f.*, p.*, r.*, phys.*, account.*, t.*, s.*, t_i.*, a_i.*, c.*, consent.*, therapy.*, indication.*,"
" (SELECT CASE"
" WHEN ar.resource_cust_num = user.custno THEN %d"
" WHEN aardt.territory IS NOT NULL THEN %d"
" WHEN aardt.territory IS NULL THEN %d"
" ElSE %d"
" END) AS file_scope"
" FROM PT_ACTIVITY a"
" CROSS JOIN PT_ACTIVITY_TYPE_STATUS s ON a.activity_type_status_id = s.activity_type_status_id"
" AND s.active_flag = 'Y'"
" AND s.closed_flag IN %@"
" CROSS JOIN PT_ACTIVITY_TYPE t ON a.activity_type_id = t.activity_type_id"
" AND t.active_flag = 'Y'"
" CROSS JOIN PT_ACTIVITY_RESOURCE ar ON a.activity_id = ar.activity_id"
" AND ar.active_flag = 'Y'"
" CROSS JOIN PT_FILE f ON a.file_id = f.file_id"
" AND f.active_flag = 'Y'"
" CROSS JOIN PT_PATIENT p ON f.patient_id = p.patient_id"
" AND p.active_flag = 'Y'"
" CROSS JOIN PT_RESOURCE r ON ar.resource_cust_num = r.sold_to_cust_num"
" AND r.active_flag = 'Y'"
" CROSS JOIN PT_AARDT aardt ON a.rdt_key = aardt.rdt_key"
" AND aardt.active_flag = 'Y'"
" CROSS JOIN PT_THERAPY therapy ON f.therapy_id = therapy.therapy_id"
" AND therapy.active_flag = 'Y'"
" CROSS JOIN PT_ICON a_i ON t.icon_id = a_i.icon_id"
" AND a_i.active_flag = 'Y'"
" CROSS JOIN PT_ICON t_i ON therapy.icon_id = t_i.icon_id"
" AND t_i.active_flag = 'Y'"
" CROSS JOIN PT_USER user ON user.user_id = %@"
" AND user.active_flag = 'Y'"
" LEFT JOIN PT_ACTIVITY_CONTACT_TYPE act ON a.activity_type_id = act.activity_type_id"
" AND act.primary_flag = 'Y'"
" AND act.active_flag = 'Y'"
" LEFT JOIN PT_ACTIVITY_CONTACT ac ON a.activity_id = ac.activity_id"
" AND ac.activity_contact_type_id = act.activity_contact_type_id"
" AND ac.active_flag = 'Y'"
" LEFT JOIN PT_PHYSICIAN phys ON ac.contact_no = phys.contact_no"
" AND phys.active_flag = 'Y'"
" LEFT JOIN PT_ACCOUNT account ON a.sold_to_cust_num = account.sold_to_cust_num"
" AND account.sold_to_cust_num = account.ship_to_cust_num"
" AND account.active_flag = 'Y'"
" LEFT JOIN PT_FILE_CONSIDERATION fc ON f.file_id = fc.file_id"
" AND fc.active_flag = 'Y'"
" LEFT JOIN PT_CONSIDERATION c ON fc.consideration_id = c.consideration_id"
" AND c.active_flag = 'Y'"
" LEFT JOIN PT_CONSENT consent ON p.patient_id = consent.patient_id"
" AND consent.active_flag = 'Y'"
" LEFT JOIN PT_FILE_INDICATION fi ON f.file_id = fi.file_id"
" AND fi.active_flag = 'Y'"
" LEFT JOIN PT_INDICATION indication ON indication.indication_id = fi.indication_id"
" AND indication.active_flag = 'Y'"
" WHERE date(max(a.activity_date, ifnull(a.client_updated_date, a.activity_date)), 'unixepoch') > date('now', 'unixepoch', '-120 day')"
" AND a.active_flag = 'Y'"
" AND ar.resource_cust_num IN %@",
FileScopeMyFile,
FileScopeTerritory,
FileScopeDistrict,
FileScopeUnknown,
self.filter.closedFlagString,
[MDTAuthenticationManager authToken].userIdNumber,
self.filter.resourcesString
];
}
FileScope 参数是枚举,其余的是字符串。 self.filter.resourcesString 可以是类似(1,2,3,4,5,6) 的字符串,目前可以包含 1-1000 个资源。使用 1 个资源运行会更快,但并不显着。
在 SQLite 上运行 explain query plan 给了我这个:
0| 0| 0|SEARCH TABLE PT_ACTIVITY AS a USING INDEX PT_ACTIVITY_IDX7 (ACTIVE_FLAG=?) (~1049 rows)
0| 1| 1|SEARCH TABLE PT_ACTIVITY_TYPE_STATUS AS s USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
0| 0| 0|EXECUTE LIST SUBQUERY 1
0| 2| 2|SEARCH TABLE PT_ACTIVITY_TYPE AS t USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
0| 3| 3|SEARCH TABLE PT_ACTIVITY_RESOURCE AS ar USING INDEX PT_ACTIVITY_RESOURCE_IDX1 (ACTIVITY_ID=?) (~2 rows)
0| 0| 0|EXECUTE LIST SUBQUERY 1
0| 4| 4|SEARCH TABLE PT_FILE AS f USING INDEX sqlite_autoindex_PT_FILE_1 (FILE_ID=?) (~1 rows)
0| 5| 5|SEARCH TABLE PT_PATIENT AS p USING INDEX sqlite_autoindex_PT_PATIENT_1 (PATIENT_ID=?) (~1 rows)
0| 6| 6|SEARCH TABLE PT_RESOURCE AS r USING INDEX sqlite_autoindex_PT_RESOURCE_1 (SOLD_TO_CUST_NUM=?) (~1 rows)
0| 7| 7|SEARCH TABLE PT_AARDT AS aardt USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
0| 8| 8|SEARCH TABLE PT_THERAPY AS therapy USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
0| 9| 9|SEARCH TABLE PT_ICON AS a_i USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
0|10|10|SEARCH TABLE PT_ICON AS t_i USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
0|11|11|SEARCH TABLE PT_USER AS user USING AUTOMATIC COVERING INDEX (USER_ID=? AND ACTIVE_FLAG=?) (~7 rows)
0|12|12|SEARCH TABLE PT_ACTIVITY_CONTACT_TYPE AS act USING INDEX PT_ACTIVITY_CONTACT_TYPE_ACTIVITY_TYPE_ID (ACTIVITY_TYPE_ID=?) (~2 rows)
0|13|13|SEARCH TABLE PT_ACTIVITY_CONTACT AS ac USING INDEX PT_ACTIVITY_CONTACT_PK (ACTIVITY_ID=? AND ACTIVITY_CONTACT_TYPE_ID=?) (~1 rows)
0|14|14|SEARCH TABLE PT_PHYSICIAN AS phys USING INDEX PT_PHYSICIAN_IDX5 (CONTACT_NO=?) (~2 rows)
0|15|15|SEARCH TABLE PT_ACCOUNT AS account USING INDEX PT_ACCOUNT_IDX2 (SOLD_TO_CUST_NUM=?) (~2 rows)
0|16|16|SEARCH TABLE PT_FILE_CONSIDERATION AS fc USING INDEX PT_FILE_CONSIDERATION_FILE_ID (FILE_ID=?) (~2 rows)
0|17|17|SEARCH TABLE PT_CONSIDERATION AS c USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
0|18|18|SEARCH TABLE PT_CONSENT AS consent USING INDEX PT_CONSENT_IDX1 (PATIENT_ID=?) (~2 rows)
0|19|19|SEARCH TABLE PT_FILE_INDICATION AS fi USING INDEX PT_FILE_INDICATION_FILE_ID (FILE_ID=?) (~2 rows)
0|20|20|SEARCH TABLE PT_INDICATION AS indication USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
0| 0| 0|EXECUTE CORRELATED SCALAR SUBQUERY 1
如果我了解查询计划,则应该有适当的索引。
在 iOS 设备上,此查询需要 8-12 秒。我需要它花费少于 1 秒,最好少于 0.5 秒。我确实需要所有这些表来生成正确的仪表板项目。第一个表 PT_ACTIVITY 有 4500 个活动。这些表格是按照我实验发现的最快顺序排列的。
对于如何加快查询速度,有人有什么建议吗?我的想法和理智都快用完了...在此先感谢!
【问题讨论】:
-
您在一个查询中加入 20 个表,但您想知道为什么速度很慢?
-
@rmaddy 这很粗鲁。为了满足业务需求,我别无选择。
-
这不是粗鲁的。这只是一个简单的事实,在移动设备上运行的 20 表连接只能做到如此之快。这完全取决于每个表中有多少行。再加上你的
where子句正在做日期计算,这也总是很慢。 -
@rmaddy 我可以尝试以 MongoDB 风格的方法进行非规范化,但这需要我在远程同步格式和我们的本地非规范化数据库之间进行转换(和返回)的整个过程。我希望尽可能避免这种情况。