多级部门查询性能问题解决方案

目录

  • 项目吐槽
  • 遇到的问题
    • 1.mysql函数group_contact()返回值限制
    • 2. Impala使用in语句存在限制
  • 解决方案
    • 优化MySQL函数递归调用方案
    • 将Impala的in查询转换为等值查询
  • 总结

项目吐槽

其实,涉及部门层级关系的问题在很多情形下都会遇到,特别是针对toB的应用开发场景。
但奇葩的是,在我们的项目里头,项目经理在前期需求调研时,预估的用户部门最大数为1k,于是相关的开发同事就按照最大数1k*4=4k的目标进行了设计实现,而真正交付到用户生产环境时同步的数据是1w。
What?也就是说,即使之前已经按照最大预估数进行了4倍数放大设计,但是现在是10倍。于是,各种问题接踵而至。
导致该问题出现的原因主要有一下几点:

  1. 需求调研不严谨,我估计所谓最大子部门数为1k也只是产品经理拍脑袋想出来的,并没有真正与用户沟通过。
  2. 问题暴露得太晚,导致上线时才发现,加剧了项目问题的严重性和影响了项目投产进度。
  3. 设计实现考虑不周,原本就是大数据分析项目,却使用了不恰当的查询方式(查询子部门数据时通过传递子部门id列表使用in查询),遇到问题了必须推翻之前的实现。

这是一次非常惨痛的项目教训,虽然大家都在埋怨需求做得太扯淡,但是本着“我们是一个团队”的大和谐思想,还得见招拆招真正去解决问题。

遇到的问题

1.mysql函数group_contact()返回值限制

部门数据是存放在mysql中的,结构为:

CREATE TABLE `organization` (
  `org_id` bigint(10) unsigned NOT NULL,
  `org_name` varchar(128) DEFAULT NULL,
  `parent_org_id` bigint(10) DEFAULT NULL,
  `status` varchar(4) DEFAULT NULL,
  `sync_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `update_time` timestamp NULL DEFAULT NULL,
  `cutecomm_dep_id` varchar(50) DEFAULT NULL,
  `company_id` bigint(20) DEFAULT NULL,
  `org_code` varchar(30) DEFAULT NULL COMMENT '机构代码',
  `parent_org_code` varchar(30) DEFAULT NULL COMMENT '上级机构代码',
  PRIMARY KEY (`org_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

使用自定义函数查询指定部门下的子部门列表:

DELIMITER $$
CREATE FUNCTION `getChildList` (parentId BIGINT, companyid BIGINT) RETURNS VARCHAR (10000) CHARSET utf8
DETERMINISTIC
BEGIN
    DECLARE
        parent VARCHAR (10000);
    DECLARE
        child VARCHAR (10000);
    SET parent = '$';
    SET child = cast(parentId as CHAR) ;

    WHILE child IS NOT NULL DO
        SET parent = concat(parent, ',', child);
        SELECT group_concat(org_id) INTO child FROM organization
         WHERE company_id = companyid and FIND_IN_SET(parent_org_id, child) > 0;
    END WHILE;
    RETURN parent;
END $$

查询所有部门结构目录树:

SELECT * FROM organization WHERE FIND_IN_SET(org_id, getChildList(0, 100000)) and company_id = 100000 ORDER BY parent_org_id, org_id

之前都是使用1k的数据进行验证的,由于group_contact函数返回值默认大小为1204字节,并没有遇到这个限制。
现在数据量是1w,是测试数据的10倍,于是首先遇到的第一个问题就是mysql函数的限制。导致某些实际在MySQL中存在的数据,通过上述SQL语句却查询不出来。
于是修改该变量值为:max_group_contact=1024000。
同时修改自定义函数中的变量值类型从VARCHAR修改为LongText(在函数中先查询子部门id列表,使用逗号分隔,当数据量很大时varchar类型存放不了),如下:

DELIMITER $$
CREATE FUNCTION `getChildList` (parentId BIGINT, companyid BIGINT) RETURNS LongText CHARSET utf8
DETERMINISTIC
BEGIN
    DECLARE
        parent LongText;
    DECLARE
        child LongText;
    SET parent = '$';
    SET child = cast(parentId as CHAR) ;

    WHILE child IS NOT NULL DO
        SET parent = concat(parent, ',', child);
        SELECT group_concat(org_id) INTO child FROM organization
         WHERE company_id = companyid and FIND_IN_SET(parent_org_id, child) > 0;
    END WHILE;
    RETURN parent;
END $$

OK,到这里就可以正常查出数据了。但是!效率低的吓人:25s!
实际上,对于查询部门目录树这个需求而言,由于全表数据才1w,直接全表查询效率都比使用上述函数递归方式查询高太多了。
改用全表查询,响应时间为:1s !!! 不知道之前写这部分代码得同事连这个最基本的事实都没注意到。

2. Impala使用in语句存在限制

于此同时,因为有另外一部分数据是存放在hive表中,通过impala进行查询。奇怪的是竟然使用是in查询,查询的in条件就是部门id,当查询根部门下的所有子部门数据时,需要传递1w+个部门id到in中。

select max(totalnum) from (select count(distinct did) as totalnum, dt from mocdb_day_activity_device_all t where t.companyid = ? AND t.dt between ? AND ?  AND t.groupid in (?,?,?,...,?) group by dt ) a

中间的省略号表示有很多参数,至于多少个就看想象力了!
观察一下,这么一个SQL语句出现眼前,光长度就够吓人的了,就别奢望着它的查询性能了,用这样一个语句进行查询就是灾难的开始。
而且impala最大只能支持到9999个in参数,于是到这里。基本上大家就炸开锅了,彻底要崩溃的节奏。怎么办?项目存在这么大的缺陷,同时还要及时交付,这不是在开国际玩笑嘛。
由于impala对于当前的查询方式存在限制,所以要绕开限制(其实即便impala不存在限制,想想在一个in中传递1w+部门id进行匹配查询,性能也不可能好到那里去)。

解决方案

优化MySQL函数递归调用方案

既然是数据分析项目,不需要处理事务,为了达到一定的查询性能,应该进行适当的冗余设计。
针对使用MySQL函数进行递归查询不合理的问题,直接修改为使用全表查询的方式解决,再结合缓存解决性能问题。

将Impala的in查询转换为等值查询

针对在Impala中使用in查询不合理的问题和限制,于是重新做如下宽表方案设计:
为了不在Impala中使用in查询,需要做冗余字段设计,针对多级部门这个场景,在每条记录中都记录员工或设备的直接部门与其所有祖先部门的信息,这样可以直接在Impala中将in查询改为“=”等值查询。

首先,自定义函数查找所有祖先部门id列表(之前是查找所有子部门id列表,数据量级相差很大),用逗号分隔:

drop function `getOrgParentStr`;
delimiter $$
create function getOrgParentStr(orgId bigint) returns varchar(1024)
begin
    declare tmpId bigint default 0;
    declare parentStr varchar(1024) default '';
    declare tmpParent bigint default -1;
    set tmpId = orgId;
    WHILE tmpId > 0 do
        set tmpParent = (select parent from orgnization where id = tmpId);
        set tmpId = tmpParent;
        IF ISNULL(parentStr) || LENGTH(trim(parentStr)) < 1 THEN
            set parentStr = CAST(tmpParent as CHAR(50));
        ELSE
            set parentStr = CONCAT_WS(',',parentStr,CAST(tmpParent as CHAR(50)));
        END IF;
    end WHILE;
    return parentStr;
end $$

其次,修改部门表设计,添加冗余字段:

CREATE TABLE `organization` (
  `org_id` bigint(10) unsigned NOT NULL,
  `org_name` varchar(128) DEFAULT NULL,
  `parent_org_id` bigint(10) DEFAULT NULL,
  `status` varchar(4) DEFAULT NULL,
  `sync_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `update_time` timestamp NULL DEFAULT NULL,
  `cutecomm_dep_id` varchar(50) DEFAULT NULL,
  `company_id` bigint(20) DEFAULT NULL,
  `org_code` varchar(30) DEFAULT NULL COMMENT '机构代码',
  `parent_org_code` varchar(30) DEFAULT NULL COMMENT '上级机构代码',
  `depth` int default 0 COMMENT '部门所在层级深度值',
  `level0` bigint(20) DEFAULT -1,
  `level1` bigint(20) DEFAULT -1,
  `level2` bigint(20) DEFAULT -1,
  `level3` bigint(20) DEFAULT -1,
  `level4` bigint(20) DEFAULT -1,
  `level5` bigint(20) DEFAULT -1,
  `level6` bigint(20) DEFAULT -1,
  `level7` bigint(20) DEFAULT -1,
  `level8` bigint(20) DEFAULT -1,
  `level9` bigint(20) DEFAULT -1,
  PRIMARY KEY (`org_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

在对部门表做冗余字段设计时需要遵守如下约定:

(1) 最多支持10级部门,新增冗余字段存储父部门信息,如: level0,level1,...,level9;

(2) 冗余字段存储的父部门含义规约,level0存储最顶级根部门id,level1存储次级部门id,以此类推,level9存储当前部门的直接父部门id

(3) 新增部门层级深度字段depth,存储当前部门所在层级深度值(从0开始),例如:

    -x
     -xx
      -xxx
       -xxxx

在上述部门树结构中,“xxxx”部门的层级深度值为:3。

(4) 导入数据时对于父部门信息不正确的记录丢弃。

经过验证,数据量在2kw时,使用原来impala的in查询方式响应时间在15s,使用宽表方式响应时间为4s;当数据量在5kw时,impala的in查询响应时间为50s,宽表方式响应时间为5s。

总结

  1. 在数据预估的时候要根据数据产生的速度进行适当的规划,且一定要经过严格的测试验证。
  2. 在大数据分析项目中,为了满足一定的查询性能,适当进行宽表设计是非常有必要的。

【参考】
http://www.cnblogs.com/kissdodog/p/3297894.html 逻辑数据库设计 - 单纯的树(递归关系数据)

原文地址:https://www.cnblogs.com/nuccch/p/10776830.html

时间: 2024-11-10 14:53:19

多级部门查询性能问题解决方案的相关文章

MySql数据库查询多级部门及其下的所有用户信息

关于多级别菜单栏或者权限系统中部门上下级的树形遍历,oracle中有connect by来实现,  mysql没有这样的便捷途径,所以MySQL遍历数据表是我们经常会遇到的头痛问题,下面通过数据库函数来实现 1.建表 ①  机构表 CREATE TABLE `t_sys_org` ( `ID` varchar(64) NOT NULL COMMENT '主键ID', `CODE` varchar(60) DEFAULT NULL COMMENT '编码', `NAME` varchar(200)

聚焦-移除Bookmark Lookup、RID Lookup、Key Lookup提高SQL查询性能(六)

前言 前面几节都是讲的基础内容,本节我们讲讲索引性能优化,当对大数据进行处理时首先想到的就是索引,一旦遇到这样的问题则手忙脚乱,各种查资料,为何平常不扎实基本功呢,我们由浅入深,简短的内容,深入的理解,而非一上来就把问题给框死,立马给出解决方案,抛出问题,再到解决问题,你GET了没有. Bookmark Lookup.RID Lookup.Key Lookup定义 一说到这三者,如果对索引研究不深的童鞋估计是懵逼的,什么玩意,我们姑且将上面三者翻译为:标签查找.行ID查找.键查找.标签查找和键查

SQL Server-聚焦过滤索引提高查询性能(十)

前言 这一节我们还是继续讲讲索引知识,前面我们聚集索引.非聚集索引以及覆盖索引等,在这其中还有一个过滤索引,通过索引过滤我们也能提高查询性能,简短的内容,深入的理解. 过滤索引,在查询条件上创建非聚集索引(1) 过滤索引是SQL 2008的新特性,被应用在表中的部分行,所以利用过滤索引能够提高查询,相对于全表扫描它能减少索引维护和索引存储的代价.当我们在索引上应用WHERE条件时就是过滤索引.也就是满足如下格式: CREATE NONCLUSTERED INDEX <index name> O

SQL Server-聚焦计算列或计算列持久化查询性能(二十二)

前言 上一节我们详细讲解了计算列以及计算列持久化的问题,本节我们依然如前面讲解来看看二者查询性能问题,简短的内容,深入的理解,Always to review the basics. 持久化计算列比非持久化计算列性能要好 我们开始创建两个一样的表并都插入100条数据来进行比较,对于计算列我们重新进行创建计算列和非计算列持久化. CREATE TABLE [dbo].[ComputeColumnCompare] (ID INT, FirstName VARCHAR(100), LastName C

《高性能MySQL》读书笔记--查询性能优化

对于高性能数据库操作,只靠设计最优的库表结构.建立最好的索引是不够的,还需要合理的设计查询.如果查询写得很糟糕,即使库表结构再合理.索引再合适,也无法实现高性能.查询优化.索引优化.库表结构优化需要齐头并进,一个不落. 6.1 为什么查询速度会慢 通常来说,查询的生命周期大致可以按照顺序来看:从客户端>>服务器>>在服务器上进行解析>>生成执行计划>>执行>>返回结果给客户端.其中执行可以认为是整个生命周期中最重要的阶段,这其中包括了大量为了检索

MySQL查询缓存设置 提高MySQL查询性能

首先看看MSYQL逻辑框架:图片来自高性能mysql 如果使用了QueryCache,当查询接收到一个和之前同样的查询,服务器将会从查询缓存中检索结果,而不是再次分析和执行相同的查询.这样就能大大提高查询性能. 打开查询缓存,要通过几个步骤来设置: 虽然你设置mysql允许查询缓存,但是如果你设置的查询缓存大小为了0,这和没有允许没什么区别. 所以必须是几个步骤的设置才能真正打开查询缓存这个功能. 下面演示最常用的设置查询缓存 一. query_cache_type 使用查询缓存的方式 一般,我

SQLServer性能优化之 nolock,大幅提升数据库查询性能

公司数据库随着时间的增长,数据越来越多,查询速度也越来越慢.进数据库看了一下,几十万调的数据,查询起来确实很费时间. 要提升SQL的查询效能,一般来说大家会以建立索引(index)为第一考虑.其实除了index的建立之外,当我们在下SQL Command时,在语法中加一段WITH (NOLOCK)可以改善在线大量查询的环境中数据集被LOCK的现象藉此改善查询的效能. 不过有一点千万要注意的就是,WITH (NOLOCK)的SQL SELECT有可能会造成Dirty Read,就是读到无效的数据.

一种更高查询性能的列存储方式MaxMinT 第一部分

简介本文描述了一种列存储方式和对应的查询方法,这种存储方式具有更好的查询性能和更小的存储空间. And查询 本文先用直观的图形方式展示and查询时的方式,这也是算法要解决的问题核心.通常在OLAP数据查询时,需要进行and处理,例如你需要获取 year = 2017 and customer = 13 的数据,这在列存储中实际是对值 year的2017这个列和 customer的13列进行and操作,而这些列一般都使用位图的方式存储.市面上有很多位图的存储方式,比如WAH, EWAH, Conc

新建一个索引能够同时提升三条SQL的查询性能

如题 CREATE TABLE `score` (   `id` int(11) NOT NULL,   `studentid` int(11) NOT NULL,   `subjectid` int(11) NOT NULL,   `score` int(11) NOT NULL,   PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 新建一个索引能够同时提升三条SQL的查询性能 ALTER TABLE `score` AD