【问题标题】:Bad performance compared to MySql with Neo4j与使用 Neo4j 的 MySql 相比性能差
【发布时间】:2023-03-25 02:20:01
【问题描述】:

我将 MySQL 数据库迁移到 Neo4j 并测试了一个简单的请求。我很惊讶地看到,neo4j 中的等效请求比 MySql 中长 10 到 100 倍。我正在开发 Neo4j 2.0.1。

在原始 MySql 架构中,我有以下三个表:

  • 国家/地区:包含一个“代码”、一个“大陆 ID”和一个“选定”布尔值,
  • 城市:包含一个“country_code”、一个“name”和一个“status”布尔值,
  • 剧院:包含一个“city_id”和一个“public”布尔值,

每个属性都有一个索引。我想在几个条件下按城市显示给定大陆的剧院数量。请求是:

SELECT count(*) as nb, c.name 
FROM `cities` c LEFT JOIN theaters t ON c.id = t.city_id 
WHERE c.country_code IN 
  (SELECT code FROM countries WHERE selected is true AND continent_id = 4)
 AND c.status=1 AND t.public = 1 
GROUP BY c.name  ORDER BY nb DESC


Neo4j 中的数据库架构如下:

(:Continent)-[:Include]->(:Country{selected:bool})-[:Include]->(:City{name:string, status:bool})-[:Include]->(:Theater{public:bool})

还为每个属性定义了一个索引。密码请求是:

MATCH (:Continent{code: 4})-[:Include]->(:Country{selected:true})-[:Include]->(city:City{status:true})-[:Include]->(:Theater{public: true})
RETURN city.name, count(*) AS nb ORDER BY nb DESC


每个数据库中大约有 70.000 个城市和 140.000 个剧院。

在 ID 为 4 的大陆上,MySql 请求大约需要 0.02 秒,而 Neo4j 需要 0.4 秒。此外,如果我在 Cypher 请求中引入 Country 和 City (...(:Country{selected:true})-[:Include*..3]->(city:City{status:true})...) 之间的可变关系长度,因为我希望能够添加像 Regions 这样的中间级别,那么请求需要超过 2 秒。

我知道在这种特殊情况下,使用 Neo4j 代替 MySql 没有任何好处,但我希望看到这两种技术之间的性能大致相当,并且我想利用 Neo4j 地理层次结构功能。

是我遗漏了什么还是 Neo4j 的限制?

感谢您的回答。

编辑:首先你会找到数据库转储文件here。 Neo4j server configuration 是开箱即用的。我在 Ruby 环境中工作,我使用的是neography gem。我也单独运行 Neo4J 服务器 因为我不在 JRuby 上,所以它通过 Rest API 发送密码请求。

该数据库包含 244 个国家、69000 个城市和 138,000 家影院。对于continent_id 4,有46,982 个城市(37,210 个将状态布尔值设置为true)和74,420 个剧院。

请求返回 2256 行。在第三次运行时,花了 338 毫秒。这是带有分析信息的请求输出:

profile MATCH (:Continent{code: 4})-[:Include]->(country:Country{selected:true})-[:Include*..1]->(city:City{status:true})-[:Include]->(theater:Theater{public: true}) RETURN city.name, count(*) AS nb ORDER BY nb DESC;

==> ColumnFilter(symKeys=["city.name", "  INTERNAL_AGGREGATE85ca19f3-9421-4c18-a449-1097e3deede2"], returnItemNames=["city.name", "nb"], _rows=2256, _db_hits=0)
==> Sort(descr=["SortItem(Cached(  INTERNAL_AGGREGATE85ca19f3-9421-4c18-a449-1097e3deede2 of type Integer),false)"], _rows=2256, _db_hits=0)
==>   EagerAggregation(keys=["Cached(city.name of type Any)"], aggregates=["(  INTERNAL_AGGREGATE85ca19f3-9421-4c18-a449-1097e3deede2,CountStar())"], _rows=2256, _db_hits=0)
==>     Extract(symKeys=["city", "  UNNAMED27", "  UNNAMED7", "country", "  UNNAMED113", "theater", "  UNNAMED72"], exprKeys=["city.name"], _rows=2257, _db_hits=2257)
==>       Filter(pred="(hasLabel(theater:Theater(3)) AND Property(theater,public(5)) == true)", _rows=2257, _db_hits=2257)
==>         SimplePatternMatcher(g="(city)-['  UNNAMED113']-(theater)", _rows=2257, _db_hits=4514)
==>           Filter(pred="(((hasLabel(city:City(2)) AND hasLabel(city:City(2))) AND Property(city,status(4)) == true) AND Property(city,status(4)) == true)", _rows=2257, _db_hits=74420)
==>             TraversalMatcher(start={"label": "Continent", "query": "Literal(4)", "identifiers": ["  UNNAMED7"], "property": "code", "producer": "SchemaIndex"}, trail="(  UNNAMED7)-[  UNNAMED27:Include WHERE (((hasLabel(NodeIdentifier():Country(1)) AND hasLabel(NodeIdentifier():Country(1))) AND Property(NodeIdentifier(),selected(3)) == true) AND Property(NodeIdentifier(),selected(3)) == true) AND true]->(country)-[:Include*1..1]->(city)", _rows=37210, _db_hits=37432)

【问题讨论】:

    标签: neo4j


    【解决方案1】:

    你说得对,我自己试了一下,查询的时间只有 100 毫秒。

     MATCH (:Continent{code: 4})-[:Include]->
           (country:Country{selected:true})-[:Include]->
           (city:City{status:true})-[:Include]->
           (theater:Theater{public: true}) 
     RETURN city.name, count(*) AS nb 
     ORDER BY nb DESC;
    
    | "Forbach"                       | 1  |
    | "Stuttgart"                     | 1  |
    | "Mirepoix"                      | 1  |
    | "Bonnieux"                      | 1  |
    | "Saint Cyprien Plage"           | 1  |
    | "Crissay sur Manse"             | 1  |
    +--------------------------------------+
    2256 rows
    **85 ms**
    

    请注意,截至 2.0.x 的 cypher 尚未优化性能,这项工作始于 Neo4j 2.1,并将持续到 2.3。内核中还计划了更多性能工作,这也将加快速度。

    我也在 Java 中实现了该解决方案,并将其缩短到 19 毫秒。它当然没有那么漂亮,但这也是我们使用 cypher 的目标:

    class City {
        Node city;
        int count = 1;
    
        public City(Node city) {
            this.city = city;
        }
    
        public void inc() { count++; }
    
        @Override
        public String toString() {
            return String.format("City{city=%s, count=%d}", city.getProperty("name"), count);
        }
    }
    
    private List<?> queryJava3() {
        long start = System.currentTimeMillis();
        Node continent = IteratorUtil.single(db.findNodesByLabelAndProperty(CONTINENT, "code", 4));
        Map<Node,City> result = new HashMap<>();
        for (Relationship rel1 : continent.getRelationships(Direction.OUTGOING,Include)) {
            Node country = rel1.getEndNode();
            if (!(country.hasLabel(COUNTRY) && (Boolean) country.getProperty("selected", false))) continue;
            for (Relationship rel2 : country.getRelationships(Direction.OUTGOING, Include)) {
                Node city = rel2.getEndNode();
                if (!(city.hasLabel(CITY) && (Boolean) city.getProperty("status", false))) continue;
                for (Relationship rel3 : city.getRelationships(Direction.OUTGOING, Include)) {
                    Node theater = rel3.getEndNode();
                    if (!(theater.hasLabel(THEATER) && (Boolean) theater.getProperty("public", false))) continue;
                    City city1 = result.get(city);
                    if (city1==null) result.put(city,new City(city));
                    else city1.inc();
                }
            }
        }
        List<City> list = new ArrayList<>(result.values());
        Collections.sort(list, new Comparator<City>() {
            @Override
            public int compare(City o1, City o2) {
                return Integer.compare(o2.count,o1.count);
            }
        });
        output("java", start, list.iterator());
        return list;
    }
    
    
    java time = 19ms
    first = City{city=Val de Meuse, count=1} total-count 22561
    

    【讨论】:

    • 非常感谢您的帮助。如果我没记错你在 Java 中的解决方案意味着我在我的应用程序中嵌入了 Neo4j 服务器,或者有没有办法在我通过 Rest API 调用的 java 中执行类似于存储过程的东西?否则我发现this post 非常有趣:也许我可以使用 Gremlin 获得与纯 Java 相同的性能提升?无论如何,目前我认为我会在每个城市节点中维护一个计数器。
    • 您可以为 Neo4j 编写一个服务器扩展,将其与服务器一起部署,然后针对服务器嵌入式数据库运行。 Gremlin 会更慢,因为 Java b/c 它是 groovy。
    • 是的,您可以在 Neo4j 服务器扩展中使用此代码,编写和安装它们非常容易,请参阅:docs.neo4j.org/chunked/milestone/…
    【解决方案2】:

    你是怎么测量的?这是第一次运行还是后续运行?

    该查询返回了多少个城市/剧院?

    您是否可以使用http://localhost:7474/webadmin/#/console/ 在查询前添加“配置文件”并发布结果查询计划来运行它?

    默认情况下它可能会选择错误的索引。

    另请注意,2.0.1 版的 Cypher 还没有达到最高性能。我们目前正在努力。因此,如果您想获得终极性能,则必须使用较低级别的 API。

    有没有机会和我分享你的数据库,看看性能如何。

    仅拥有一个“包含”关系类型可能会使它比需要的成本更高。

    能否请您也发布您的 neo4j 配置 (conf/*) 和可能的 graph.db/messages.log ?

    【讨论】:

    • 感谢您的关注。我在问题正文中添加了您提出的信息。您说我可能会使用较低级别的 API 以获得更好的性能。这是否意味着我必须使用服务器的嵌入式版本?或者如何在没有 Cypher 请求的情况下通过 REST api 进行计数聚合?再次感谢您的帮助。
    猜你喜欢
    • 2018-11-17
    • 2013-07-23
    • 2012-03-11
    • 2013-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-01
    相关资源
    最近更新 更多