MySQL Execution Plan--合理利用隐式的业务逻辑
问题描述
优化过程中遇到一个SQL:
SELECT SUM(user_value) FROM user_log WHERE del_flag = 0 AND product_id = 2324 AND user_type = 1;
其执行计划为:
*************************** 1. row *************************** id: 1 select_type: SIMPLE table: user_log partitions: NULL type: ref possible_keys: index_sId_ty_vl_ct,IDX_product_id_oth1 key: IDX_product_id_oth1 key_len: 12 ref: const,const,const rows: 14884 filtered: 100.00 Extra: Using index 1 row in set, 1 warning (0.00 sec
从执行计划来看,使用Using index(覆盖索引)已经是最优的执行计划,但每次查询扫描数据较多,影响整体查询性能。
优化方案
查询需要使用SUM计算user_value的总和,借用1+1+0+0+0+0+0=1+1=2的例子,进行如下测试:
SELECT SUM(CASE WHEN user_value>0 THEN 1 ELSE 0 END) AS count1, COUNT(1) AS count2 FROM user_log WHERE del_flag = 0 AND product_id = 2324 AND user_type = 1; +--------+--------+ | count1 | count2 | +--------+--------+ | 680 | 8067 | +--------+--------+
在假设user_value没有负值的情况下,下面两条SQL的结果相同:
##测试SQL1 SELECT SUM(user_value), COUNT(1) AS count2 FROM user_log WHERE del_flag = 0 AND product_id = 2324 AND user_type = 1; ##测试SQL2 SELECT SUM(user_value) FROM user_log WHERE del_flag = 0 AND product_id = 2324 AND user_type = 1 AND user_value>0;
测试SQL1的执行时间为0.00327250,其资源消耗为:
+----------------------+----------+----------+------------+--------------+---------------+-------+ | Status | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out | Swaps | +----------------------+----------+----------+------------+--------------+---------------+-------+ | starting | 0.000066 | 0.000000 | 0.000000 | 0 | 0 | 0 | | checking permissions | 0.000012 | 0.000000 | 0.000000 | 0 | 0 | 0 | | Opening tables | 0.000016 | 0.000000 | 0.000000 | 0 | 0 | 0 | | init | 0.000021 | 0.000000 | 0.000000 | 0 | 0 | 0 | | System lock | 0.000015 | 0.000000 | 0.000000 | 0 | 0 | 0 | | optimizing | 0.000016 | 0.000000 | 0.000000 | 0 | 0 | 0 | | statistics | 0.000127 | 0.000000 | 0.000000 | 0 | 0 | 0 | | preparing | 0.000019 | 0.000000 | 0.000000 | 0 | 0 | 0 | | executing | 0.000011 | 0.000000 | 0.000000 | 0 | 0 | 0 | | Sending data | 0.002867 | 0.002999 | 0.000000 | 0 | 0 | 0 | | end | 0.000012 | 0.000000 | 0.000000 | 0 | 0 | 0 | | query end | 0.000013 | 0.000000 | 0.000000 | 0 | 0 | 0 | | closing tables | 0.000012 | 0.000000 | 0.000000 | 0 | 0 | 0 | | freeing items | 0.000054 | 0.000000 | 0.000000 | 0 | 8 | 0 | | cleaning up | 0.000012 | 0.000000 | 0.000000 | 0 | 0 | 0 | +----------------------+----------+----------+------------+--------------+---------------+-------+
而测试SQL2的执行时间为0.00072325,其资源消耗为:
+----------------------+----------+----------+------------+--------------+---------------+-------+ | Status | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out | Swaps | +----------------------+----------+----------+------------+--------------+---------------+-------+ | starting | 0.000072 | 0.000000 | 0.000000 | 0 | 0 | 0 | | checking permissions | 0.000013 | 0.000000 | 0.000000 | 0 | 0 | 0 | | Opening tables | 0.000016 | 0.000000 | 0.000000 | 0 | 0 | 0 | | init | 0.000021 | 0.000000 | 0.000000 | 0 | 0 | 0 | | System lock | 0.000014 | 0.000000 | 0.000000 | 0 | 0 | 0 | | optimizing | 0.000020 | 0.000000 | 0.000000 | 0 | 0 | 0 | | statistics | 0.000089 | 0.000000 | 0.000000 | 0 | 0 | 0 | | preparing | 0.000020 | 0.000000 | 0.000000 | 0 | 0 | 0 | | executing | 0.000011 | 0.000000 | 0.000000 | 0 | 0 | 0 | | Sending data | 0.000365 | 0.000000 | 0.000000 | 0 | 0 | 0 | | end | 0.000012 | 0.000000 | 0.000000 | 0 | 0 | 0 | | query end | 0.000014 | 0.000000 | 0.000000 | 0 | 0 | 0 | | closing tables | 0.000012 | 0.000000 | 0.000000 | 0 | 0 | 0 | | freeing items | 0.000035 | 0.000000 | 0.000000 | 0 | 0 | 0 | | cleaning up | 0.000012 | 0.000000 | 0.000000 | 0 | 0 | 0 | +----------------------+----------+----------+------------+--------------+---------------+-------+
在Sending data部分,两者在Durion部分差距约10倍,而测试SQL2在CPU_user部分差距更明显。
总结:
DBA在优化SQL时,除了从数据分布/索引结构等方面入手外,还需要从业务逻辑方面入手。
PS:上面的优化是假设user_value没有负值,而实际业务逻辑中user_value可能存在负值,因此以上优化纯属于瞎编。