【问题标题】:How can I improve performance of a many-to-many SQL query?如何提高多对多 SQL 查询的性能?
【发布时间】:2014-07-03 19:56:59
【问题描述】:

我在书籍和流派之间建立了多对多的关系。例如,“霍比特人”书可能有“儿童”、“小说”和“奇幻”等类型。

这是架构:

CREATE TABLE "genre" (
    "id" integer NOT NULL PRIMARY KEY,
    "name" varchar(50) NOT NULL
)
;
CREATE TABLE "book_genres" (
    "book_id" integer NOT NULL REFERENCES "book" ("id"),
    "genre_id" integer NOT NULL REFERENCES "genre" ("id"),
    CONSTRAINT book_genres_pkey PRIMARY KEY (book_id, genre_id)
)
;
CREATE TABLE "book" (
    "id" integer NOT NULL PRIMARY KEY,
    "name" varchar(255) NOT NULL,
    "price" real NOT NULL
)
;

还有索引:

CREATE INDEX "book_genres_36c249d7" ON "book_genres" ("book_id");
CREATE INDEX "book_genres_33e6008b" ON "book_genres" ("genre_id");
CREATE INDEX "book_5a5255da" ON "book" ("price");

行数:

  • 类型:30
  • book_genres:800,000
  • 图书:200,000

我正在尝试在 SQL 中编写一个查询,它可以返回按价格排序的特定类型的所有书籍,而不会重复。

这是我的查询:

SELECT name, price 
FROM book 
WHERE book.id 
IN 
    (SELECT book_id 
    FROM book_genres
    WHERE genre_id = 1
    OR genre_id = 2)
ORDER BY price LIMIT 10

我的问题是性能。此查询最多可能需要 2000 毫秒才能执行。我怎样才能提高性能?

我可以完全控制数据库(Postgres 9.3),因此可以添加视图、索引或非规范化。我也在使用 Django,因此可以使用 Python/Django 在内存中执行多个查询执行操作。

【问题讨论】:

    标签: sql database postgresql join schema


    【解决方案1】:

    在大多数情况下,您可以使用JOIN 而不是子查询来提高性能(尽管这取决于许多因素):

    SELECT * 
    FROM 
    (
       SELECT b.name, b.price 
       FROM book b JOIN book_genres g ON b.book.id = g.book_id 
                                  AND g.genre_id = 1
       UNION
    
       SELECT b.name, b.price 
       FROM book b JOIN book_genres g ON b.book.id = g.book_id 
                                  AND g.genre_id = 2
    )
    ORDER BY price LIMIT 10
    

    【讨论】:

    • 谢谢,这也是我的第一次尝试。不幸的是,如果这本书同时属于流派 1 和流派 2,它会带来重复。此外,添加 DISTINCT 会大大降低性能。
    • @donturner : 我刚刚开始)。再试一个 ^ 。关于第一个:你试过group by吗?
    • 我试过GROUP BY,性能和DISTINCT一样。不幸的是,您的新查询需要大约 3000 毫秒才能执行。查询计划器输出会有帮助吗?
    【解决方案2】:
    SELECT b.name, b.price
    FROM book b
    WHERE EXISTS (
        SELECT *
        FROM book_genres bg
        WHERE bg.book_id = b.id 
        AND bg.genre_id IN( 1 , 2)
        )
    ORDER BY b.price 
    LIMIT 10
            ;
    

    按价格+LIMIT 的顺序可能会成为性能杀手:检查查询计划。

    PLUS:将单列索引替换为“反向”索引: 使 book_id 成为 books.id 的 FK 并且(也许)省略代理键 id


    CREATE TABLE book_genres
            ( book_id integer NOT NULL REFERENCES book (id)
            , genre_id integer NOT NULL REFERENCES genre (id)
            , PRIMARY KEY (book_id, genre_id)
            ) ;
    CREATE INDEX ON book_genres  (genre_id,book_id);
    

    【讨论】:

    • 谢谢,我已经按照您的建议进行了更改。性能与我当前的查询相同。需要注意的一件事是使用OFFSET(例如OFFSET 500)时性能会越来越差。规划器输出会有帮助吗?
    • (修改表后你做了真空分析?)删除order by price LIMIT xxx,性能可能会更好(如果没有太多行满足你的条件)OFFSET会使事情变得更糟。
    • “你做了真空分析?” - 那是问题!我没有运行那个。现在,我的原始查询在不使用OFFSET 时每次运行不到 20 毫秒,而在使用它时最多运行 200 毫秒(这是可以容忍的)。很棒的工作,感谢您为我指出解决方案。
    • 您应该养成这样的习惯:在修改表格内容(分布)或改变其结构(添加索引等)后,在其上运行VACUUM ANALYZE the_table; 以刷新统计信息。
    猜你喜欢
    • 2016-01-17
    • 1970-01-01
    • 1970-01-01
    • 2023-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多