是否有更有效的方法来提高嵌套循环查询的性能

时间:2017-01-14 02:22:16

标签: java mysql

我的问题

我有一个应用程序生成一个包含multiple layers of nested lists的列表。我当前的实现会生成所需的结果,但它也会对数据库执行大量查询exponential to the number of layers depth。所以整体表现相当差(花费太多时间)。我试图找到一种方法来改善它,但是,说实话,我不知道哪种方式更好。

所以我想知道的是:

  1. 我有所改善。我的改进可以接受吗? (不会创造其他问题)

  2. 还有其他方法可以改进,例如:

    一个。非规范化数据库表

    湾使用其他ORM框架和更好的支持

    ℃。数据库设计或java代码的其他更好的实现(可以真正使用你们的一些帮助)

  3. 原始需要

    我所拥有的是一些具有一对多关系的表格:

    Family
    id       name
    1        Smiths
    2        Johnson
    3        Williams
    
    Member
    id       family_id  name
    1        1          David Smiths
    2        1          Mary Smiths
    3        1          William Smiths
    3        2          David Johnson
    4        3          David Williams
    
    Asset
    id       member_id  type     value
    1        1          cash     100.00
    2        1          share    200.00
    3        1          security 100.00
    4        2          cash     50.00
    

    我需要的是生成a list of Families and their Asset。这应该是Family的列表,每个Family包含Member的列表,每个Member包含Asset的列表:

    class Family{
        Integer id;
        String name;
        List<Member> members;
        // Getter and setters
    }
    
    class Member{
        Integer id;
        Integer family_id;
        String name;
        List<Asset> assets;
        // Getter and setters
    }
    
    class Asset{
        Integer id;
        Integer member_id;
        String type;
        BigDecimal value;
    }
    

    结果应为List<Family> families。输出到JSON:

    {    
    "families": [{
        "id": 1,
        "name": "Smiths",
        "members": [{
            "id": 1,
            "family_id": 1,
            "name": "David Smiths",
            "assets": [{
                "id": 1,
                "member_id": 1,
                "type": "cash",
                "value": "100.00"
            }, {
                "id": 2,
                "member_id": 1,
                "type": "share",
                "value": "200.00"
            }, {
                "id": 3,
                "member_id": 1,
                "type": "security",
                "value": "100.00"
            }]
        }]
    }]
    }
    

    到目前为止,我有两种方法可以做到这一点:

    解决方案1嵌套循环查询

    Java代码

    List<Family> generateFamilyList(){
        List<Family> families = resultMapper.selectFamily();
    
        // select Member for each Family
        for(Family family: families){
    
            List<Member> membersInFamily = resultMapper.selectMemberByFamily(family.getId());
    
            //select Asset for each Member
            for(Member member: membersInFamily){
    
                List<Asset> assetsOfMember = resultMapper.selectAssetByMember(member.getId());
                member.setAssets(assetsOfMember);
            }
            family.setMembers(membersInFamily );
        }
    
        return families;
    }
    

    和声明,使用Mybatis

    <select id="selectFamily" resultType="Family">
        select id, name from family;
    </select>
    
    <select id="selectMemberByFamily" resultType="Member">
        select id, family_id, name from member
        where family_id = #{familyId};
    </select>
    
    <select id="selectAssetByMember" resultType="Asset">
        select id, member_id, type, value from asset
        where member_id = #{memberId};
    </select>
    

    此方法完成其工作并生成正确的JSON。但它也会在最里面选择N^3 times of query to the database

    时创建selectAssetByMember(member.getId())

    我切换到mapper中使用Mybatis collection select,但发现只有make Mybatis Mapper执行for循环查询而不是在java代码中执行。数据库仍然接收N ^ 3查询。

    所以我提出了另一个解决方案:

    解决方案2在查询中选择all,然后在Java

    中过滤

    Java代码

    List<Family> generateFamilyList(){
        List<Family> families = resultMapper.selectFamily();
    
        List<Integer> allFamilyIds = familes.stream()
            .map(Family::getId)
            .collect(Collectors.toList());
    
        if(familyIds.isEmpty())
            //throw exception or return 
    
        // select all members in one query.
        // !Watch out! for Max Number of Allowable Parameters
        List<Member> allMembers = resultMapper.selectMemberByFamilyIds(allFamilyIds);
    
        List<Integer> allMemberIds = allMembers.stream()
            .map(Member::getId)
            .collect(Collectors.toList());
    
        if(allMemberIds.isEmpty())
            //throw exception or 
            //return a new empty ArrayList for this member's asset
    
        // select all ssets in one query.
        // !Watch out! for Max Number of Allowable Parameters
        List<Asset> allAssets = resultMapper.selectAssetByMemberIds(allMemberIds );
    
        // filter and set Members for each Family       
        for(Family family: families){
    
            List<Member> membersInFamily = allMembers.stream()
                .filter(member -> member.getFamilyId().equals(family.getId()))
                .collect(Collectors.toList());
    
            //select Asset for each Member
            for(Member member: membersInFamily){
    
                List<Asset> assetsOfMember = allAssets.stream()
                    .filter(asset -> asset.getMemberId().equals(member.getId()))
                    .collect(Collectors.toList());
    
                member.setAssets(assetsOfMember);
            }
            family.setMembers(membersInFamily );
        }
    
        return families;
    }
    

    和陈述。 mybatis支持一系列参数:

    <select id="selectFamily" resultType="Family">
        select id, name from family;
    </select>
    
    <select id="selectMemberByFamilyIds" resultType="Member">
        select id, family_id, name from member
        where family_id IN 
        <foreach> #{familyId} </foreach>;  
        <!-- ( 1, 2, 3, ....); the input list familyIds-->
        <!-- could cause SQLException if the ids more than the Max Number of Allowable Parameters of database -->
    </select>
    
    <select id="selectAssetByMemberIds" resultType="Asset">
        select id, member_id, type, value from asset
        where member_id IN
        <foreach> #{memberId} </foreach>
        <!-- ( 1, 2, 3, ....); the input list memberIds-->
        <!-- could cause SQLException if the ids more than the Max Number of Allowable Parameters of database -->
    </select>
    

    结果和问题

    1. 查询号码减少

    2. Java应用程序中的内存使用量增加

    3. 整体应用程序响应时间显着缩短(在我的情况下降至20% - 10%)

    4. 新问题:Max Number of Allowable Parameters。如果参数列表memberIds的大小太大,则需要在查询之前进行分页。最大数量有限,与数据库类型不同。

    5. 这就是我现在所得到的一切。而且我有点卡住,不知道我应该在哪里改进我的代码。

      感谢各位观看我的长期问题。

2 个答案:

答案 0 :(得分:2)

这实际上取决于我们谈论的数据量,保存的数据等等。

对于真正的大数据解决方案,我们很难说像cassandra中的图形数据库,通过akka连接。

对于中等负载,数据库将位于自己的服务器上,因此您需要减少连接数量和网络使用量,因此最好在本地进行一次调用和排序,

对于非常小的数据,其中app和db位于同一服务器上,对db的多次调用是可以的,并且db在排序时会更好更快。

答案 1 :(得分:1)

如果您真的想要最佳性能,那么您的过滤应该在数据所在的位置附近。这意味着使用您的数据库功能最大化。例如,MySQL支持JSON_OBJECT来创建 JSON 结果。 例如:

SELECT
    member.id,
    member.name,
    member.family_id,
    JSON_OBJECT('id', asset.id,'member_id', asset.member_id, 'type', asset.type, 'value',
    asset.value) AS assets
FROM
    member
LEFT OUTER JOIN
    asset
ON
    member.id = asset.member_id

会给你以下结果。 你可以进一步微调。

1   David Smiths    1   {"id": 1, "type": "cash", "value": 100, "member_id": 1}
1   David Smiths    1   {"id": 2, "type": "share", "value": 200, "member_id": 1}
1   David Smiths    1   {"id": 3, "type": "security", "value": 100, "member_id": 1}
2   Mary Smiths 1   {"id": 4, "type": "cash", "value": 50, "member_id": 2}
3   William Smiths  1   {"id": null, "type": null, "value": null, "member_id": null}
4   David Johnson   2   {"id": null, "type": null, "value": null, "member_id": null}
5   David Williams  3   {"id": null, "type": null, "value": null, "member_id": null}

希望这有帮助。