2017-02-21 9 views
0

프로그램하는 방법을 스스로 가르치려고, 나는 주문한 모든 책을 표시하기 위해 작은 웹 응용 프로그램 (Flask, SQLAlchemy, Jijna)을 만들고 있습니다. 아마존에서.다 - 대 - 다 관계의 쿼리 속도 향상

"barest bones"가능한 방법으로, 나는 http://pinboard.in을 복제하는 법을 배우려고합니다. 그것은 내 모범입니다. Maciej Cegłowski는 똑바로 G입니다 ... 그의 사이트가 어떻게 그렇게 빨리 움직이는 지 전혀 알지 못합니다. 160 개의 북마크 항목을로드 할 수 있습니다. 모두 관련 태그 -in, dunno, 500 ms? ... 나는 왜 내가 끔찍하고 끔찍한 잘못을 저지르고 있는지를 아는 이유입니다. (내가 할 수 있다면, 그냥 교사에게 가르쳐 줄께.)

어쨌든 내 books 클래스와 내 tag 클래스 사이에 다 대다 관계를 만들었으므로 (1) book을 클릭하고 tags과 (2) tag을 클릭하고 관련 도서를 모두 확인하십시오.

assoc = db.Table('assoc', 
    db.Column('book_id', db.Integer, db.ForeignKey('books.book_id')), 
    db.Column('tag_id', db.Integer, db.ForeignKey('tags.tag_id')) 
) 

class Book(db.Model): 
    __tablename__ = 'books' 
    book_id = db.Column(db.Integer, primary_key=True) 
    title = db.Column(db.String(120), unique=True) 
    auth = db.Column(db.String(120), unique=True) 
    comment = db.Column(db.String(120), unique=True) 
    date_read = db.Column(db.DateTime) 
    era = db.Column(db.String(36)) 
    url = db.Column(db.String(120)) 
    notable = db.Column(db.String(1)) 

    tagged = db.relationship('Tag', secondary=assoc, backref=db.backref('thebooks',lazy='dynamic')) 

    def __init__(self, title, auth, comment, date_read, url, notable): 
     self.title = title 
     self.auth = auth 
     self.comment = comment 
     self.date_read = date_read 
     self.era = era 
     self.url = url 
     self.notable = notable 

class Tag(db.Model): 
    __tablename__ = 'tags' 
    tag_id = db.Column(db.Integer, primary_key=True) 
    tag_name = db.Column(db.String(120)) 

문제

나는 books 테이블을 통해 반복하는 경우에만 (~ :

여기 Entity relationship diagram

두 클래스 사이의 관계에 대한 코드입니다 : 여기 내 테이블 아키텍처 400 행) 쿼리가 실행되고 번개 속도로 브라우저로 렌더링됩니다. 문제 없습니다. 그러나, 나는 어떤을 보여주고 싶은 책과 관련된 모든 태그, 나는 for loop를 중첩하여 코드를 변경하는 경우

{% for i in book_query %} 
    <li> 
     {{i.notable}}{{i.notable}} 
     <a href="{{i.url}}">{{i.title}}</a>, {{i.auth}} 
     <a href="/era/{{i.era}}">{{i.era}}</a> {{i.date_read}} 
     {% if i.comment %} 
      <p>{{i.comment}}</p> 
     {% else %} 
      <!-- print nothing --> 
     {% endif %} 
    </li> 
{% endfor %} 

는 다음과 같이

{% for i in book_query %} 
    <li> 
     {{i.notable}}{{i.notable}} 
     <a href="{{i.url}}">{{i.title}}</a>, {{i.auth}} 
     <a href="/era/{{i.era}}">{{i.era}}</a> 
     {% for ii in i.tagged %} 
      <a href="/tag/{{ii.tag_name}}">{{ii.tag_name}}</a> 
     {% endfor %} 
     {{i.date_read}} 
     {% if i.comment %} 
      <p>{{i.comment}}</p> 
     {% else %} 
      <!-- print nothing --> 
     {% endif %} 
    </li> 
    {% endfor %} 

쿼리 크게 아래로 속도가 느려 (약 20 초 소요). 내 코드는 book 테이블의 모든 행에 대해 내 코드가 전체assoc 테이블 (즉, "전체 테이블 스캔")을 반복하기 때문에 이러한 현상이 발생합니다. 분명히

("제가 생각하는 것은 일어나고있다"또는,)

토론, 나는 완전한 멍청한 놈 들려요은 2 ~ 3 개월 동안 프로그래밍 된입니다. 그것은 단지 일을 시작하게 동기를 부여하지만, 내가 따라갈 때 채우기 위해 노력하고있는 지식 기반에는 큰 차이가 있다는 것을 알게됩니다. 오른쪽이 박쥐

, 나는 그것이 각각의 새로운 책, 코드가 전체 연관 테이블을 통해 반복되는 것을 매우 비효율적 있음을 알 수 있습니다 (즉 나는 그것이 믿는, 무슨 일이 일어나고 있는지 참 인 경우). 나는 내가 book with book_id == 1에 대한 모든 태그를 검색 한 후에 다시 book_id == 1assoc 테이블의 행을 "확인"하는 방식으로 assoc 테이블을 클러스터링 (?)하거나 정렬 (?)해야한다고 생각합니다.즉

가, 내가 무슨 일이 일어나고 생각하면이 (computerspeak에)입니다 :

  • 오, 그는 books 테이블에 book_id == 1와 책이
  • 좋아 태그 된 방법을 알고 싶어 나를 보자 assoc 테이블로 이동
  • 행 번호 1 ... assoc 테이블의 book_id1과 동일합니까?
  • 좋습니다. Row # 1에 대해 tag_id은 무엇입니까? ... [컴퓨터가 tag 테이블로 이동하여 tag_name 테이블을 반환하고 브라우저로 반환]
  • 행 번호 2 ... book_idassoc 테이블은 1과 동일합니까?
  • 오, 안돼, 그렇지 않다 ... 알았어, 행 # 3으로 가자.
  • 흠. 내 프로그래머가 어리 석고이 테이블을 어떤 식 으로든 정렬하거나 색인을 만들지 않았기 때문에 나는 아마도 거기 더 이상있을 때 우리는 books table 컴퓨터에서 book_id == 2에 도착하면, 다음 ... book_id == 1을 찾고 전체 assoc 테이블을 통해

을 가야하는 것은 정말 미친 가져옵니다

  • 좋아, 그는 함께가는 모든 태그를 알고 싶어한다. book_id == 2
  • 좋아, 나는 이미 하나를 확인하지 않았다 ... 나 ...을 assoc 테이블
  • 행 # 1로 이동 잠깐만 보자? 이런 젠장 # t, 나는 이것을 다시해야만한다 ??
  • 젠장 ... 좋아요 ... 행 번호 1 ...은 book_id == 2입니까? (나는 그것이 아니라는 것을 알고!하지만 내가 프로그래머가 둠 - 둠 때문에 어쨌든 확인해야 ...)

질문

그래서 질문 (수는 I (1) 종류입니까?) 또는 클러스터 (?) assoc 테이블을 통해 "지능형"순회를 보장하는 어떤 방식 으로든 assoc 테이블을 사용하거나, 제 친구가 제안한대로 I (2)가 "좋은 SQL 쿼리 작성 방법 배우기"를 수행합니까? 모든 입력에 대한

감사를

마지막 단어 (나는 ... 빌어 먹을 연금술사가 .... 비밀 이것 저것 자신의 마법을 enshrouding SQLAlchemy의로 모든 것을 처리했습니다 때문에 참고, 나는 SQL을 배운 적이 없어요). stackoverflow에 대한 질문을하는 방법을 개선하는 데 도움이되는 의견이 있으면 (이것이 내 첫 번째 게시물입니다!) 알려주십시오.

답변

1

대부분의 대답은 질문입니다.

첫 번째 예제에서는 books 테이블을 반복 할 때 SQL 쿼리가 실행됩니다. 두 번째 예에서는 Book마다 별도의 assoc 쿼리가 실행됩니다. 그래서 이것은 약 400 개의 SQL 질의로 상당히 시간이 많이 소요됩니다.당신이 SQLALCHEMY_ECHO 구성 매개 변수를 설정 한 경우 앱 디버그 로그에서 볼 수 있습니다

app.config['SQLALCHEMY_ECHO'] = True 

을 또는 당신은 Flask-DebugToolbar를 설치하고 웹 인터페이스에서 이러한 쿼리를 볼 수 있습니다.

이 문제를 해결하는 가장 좋은 방법은 SQL 기본을 배우는 것입니다. 응용 프로그램이 커지면 어쨌든 필요합니다. pure SQL에서보다 최적화 된 쿼리를 작성하십시오. 귀하의 케이스의 경우는 다음과 같이 보일 수 있습니다 :

# Single query to get all books and their tags 
query = db.session.query(Book, Tag.tag_name).join('tagged') 
# Dictionary of data to be passed to renderer 
books = {} 
for book, tag_name in query: 
    book_data = books.setdefault(book.book_id, {'book': book, 'tags': []}) 
    book_data['tags'].append(tag_name) 
# Rendering HTML 
return render_template('yourtemplate.html', books=books) 

템플릿 코드는 다음과 같이 표시됩니다 :

다음
SELECT books.*, tags.tag_name FROM books 
JOIN assoc ON assoc.book_id = books.book_id 
JOIN tags ON assoc.tag_id = tags.tag_id 

하는 HTML 렌더러에 전달하기 전에 책에서 다음 그룹을 SQLAlchemy의 코드를 다시 작성하려고

{% for book in books %} 
<li> 
    {{ book.book.notable }}{{ book.book.notable }} 
    <a href="{{ book.book.url }}">{{ book.book.title }}</a>, {{ book.book.auth }} 
    <a href="/era/{{ book.book.era }}">{{ book.book.era }}</a> 
    {% for tag in book.tags %} 
    &nbsp;<a href="/tag/{{ tag }}" class="tag-link">{{ tag }}</a>&nbsp; 
    {% endfor %} 
    {{ book.book.date_read }} 
    {% if book.book.comment %} 
     <p>{{ book.book.comment }}</p> 
    {% else %} 
     <!-- print nothing --> 
    {% endif %} 
</li> 
{% endfor %} 

또 다른 방법

데이터베이스는 PostgreSQL의 경우 당신은 쿼리 쓸 수 있습니다 : 당신이 배열로 이미 집계 태그 책 데이터를 얻을 것이다이 경우

SELECT books.title, books.auth (...), array_agg(tags.tag_name) as book_tags FROM books 
JOIN assoc ON assoc.book_id = books.book_id 
JOIN tags ON assoc.tag_id = tags.tag_id 
GROUP BY books.title, books.auth (...) 

합니다. SQLAlchemy의 당신은 쿼리를 만들 수 있습니다 :

from sqlalchemy import func 

books = db.session.query(Book, func.array_agg(Tag.tag_name)).\ 
    join('tagged').group_by(Book).all() 
return render_template('yourtemplate.html', books=books) 

그리고 템플릿은 다음과 같은 구조를 가지고 : 조회는 별도의 하나에 의해 각 책 하나에 대한 태그를 가져 오는 책을 많이있는 경우

{% for book, tags in books %} 
<li> 
    {{ book.notable }}{{ book.notable }} 
    <a href="{{ book.url }}">{{ book.title }}</a>, {{ book.auth }} 
    <a href="/era/{{ book.era }}">{{ book.era }}</a> 
    {% for tag in tags %} 
    &nbsp;<a href="/tag/{{ tag }}" class="tag-link">{{ tag }}</a>&nbsp; 
    {% endfor %} 
    {{ book.date_read }} 
    {% if book.comment %} 
     <p>{{ book.comment }}</p> 
    {% else %} 
     <!-- print nothing --> 
    {% endif %} 
</li> 
{% endfor %} 
0

SQL 문을 사용하면 네트워크 I/O에서 응답 시간이 줄어 듭니다.

이 최적화를위한 한 가지 방법은이 쿼리에 항상 태그가 필요하다는 것을 알고 있다면 SQLAlchemy가 조인 또는 하위 쿼리를 통해 하나의 쿼리에서 모든 종속 태그를 가져 오도록하는 것입니다.

내가 당신의 쿼리를 볼 수 없지만, 내 생각 엔이 하위 쿼리 부하가 사용 사례에 가장 적합한 것입니다 :

session.query(Book).options(subqueryload('tagged')).filter(...).all() 
1

다음 구현, @ 세르게이-Shubin에서 적응에 실행 가능한 해결책이었다 이 질문 :

클래스 & 테이블 협회 선언

assoc = db.Table('assoc', 
    db.Column('book_id', db.Integer, db.ForeignKey('books.book_id')), 
    db.Column('tag_id', db.Integer, db.ForeignKey('tags.tag_id')) 
    ) 

class Book(db.Model): 
    __tablename__ = 'books' 
    book_id = db.Column(db.Integer, primary_key=True) 
    title = db.Column(db.String(120), unique=True) 
    auth = db.Column(db.String(120), unique=True) 
    comment = db.Column(db.String(120), unique=True) 
    date_read = db.Column(db.DateTime) 
    era = db.Column(db.String(36)) 
    url = db.Column(db.String(120)) 
    notable = db.Column(db.String(1))  

    tagged = db.relationship('Tag', secondary=assoc, backref=db.backref('thebooks',lazy='dynamic')) 

class Tag(db.Model): 
    __tablename__ = 'tags' 
    tag_id = db.Column(db.Integer, primary_key=True) 
    tag_name = db.Column(db.String(120)) 

def construct_dict(query): 
     books_dict = {} 
     for each in query: # query is {<Book object>, <Tag object>} in the style of assoc table - therefore, must make a dictionary bc of the multiple tags per Book object 
      book_data = books_dict.setdefault(each[0].book_id, {'bookkey':each[0], 'tagkey':[]}) # query is a list of like this {index-book_id, {<Book object>}, {<Tag object #1>, <Tag object #2>, ... }} 
      book_data['tagkey'].append(each[1]) 
     return books_dict 

경로, SQL-연금술 쿼리

@app.route('/query') 
def query(): 
    query = db.session.query(Book, Tag).outerjoin('tagged') # query to get all books and their tags 
    books_dict = construct_dict(query) 

    return render_template("query.html", query=query, books_dict=books_dict)