2014-05-15 34 views
0

我有书籍和流派之间的多对多关系。例如,“霍比特人”的书可能有流派“孩子”,“小说”和“幻想”。如何提高多对多SQL查询的性能?

这里的模式:

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 80万
  • 书:200 ,0 00

我正在尝试在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),所以可以添加视图,索引或denormalise。我也使用Django,因此可以使用Python/Django执行多个查询在内存中执行操作。按价格+ LIMIT

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 

回答

3
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 
     ; 

的顺序可以是性能杀手:

+0

谢谢,我已经做出了您所建议的更改。性能与我当前的查询相同。需要注意的一点是,使用'OFFSET'(例如'OFFSET 500'),性能会进一步恶化。计划者输出是否有帮助? – donturner

+0

(你在表格修改之后做了真空分析?)删除'按价格LIMIT xxx'的顺序,性能可能会变好(如果没有太多的行满足您的条件)OFFSET可能会使事情变得更糟。 – joop

+0

“你做过真空分析吗?” - 这是问题!我没有运行过。现在,我的原始查询每次不使用“OFFSET”时会运行小于20毫秒,并且在使用时最多可以运行200毫秒(这是可以接受的)。非常棒的工作,感谢您指点我的解决方案。 – donturner

2

在大多数情况下,你可以提高你的表现用JOIN代替子查询(尽管这取决于很多因素左右)检查查询计划。

PLUS: 化妆book_id一个FK到books.id 和(也许)省略代理键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); 
+0

感谢,认为:通过 “反向” 指标代替一列索引是我的第一次尝试。不幸的是,如果本书既是体裁1又是体裁2,它会带来重复。另外,增加“DISTINCT”会大大降低性能。 – donturner

+0

@donturner:我刚刚开始)。试试另一个^。关于第一个问题:你是否尝试过“分组”? – potashin

+0

我试过GROUP BY,性能和DISTINCT一样。不幸的是,您的新查询需要大约3000毫秒才能执行。查询计划员输出帮助? – donturner