Rails и полиморфные связи

    В большинстве руководств по Rails, которые мне попадались в руки, в примерах по полиморфным связям есть интересная особенность выбора типа для этих связей, о которой и пойдет речь в этом посте.

    В Rails полиморфными считаются связи, устанавливаемые между объектами разных типов. Предполагается, что все эти объекты разделяют некоторые общие характеристики, но имеют различные качественные представления. Полиморфные связи — один из способов реализации отношения супертип-подтип.

    Рассмотрим три модели:
    1. class Post < ActiveRecord::Base
    2. has_many :comments, :as => :resource
    3. end
    4. class Image < ActiveRecord::Base
    5. has_many :comments, :as => :resource
    6. end
    7. class Comment < ActiveRecord::Base
    8. belongs_to :resource, :polymorphic => true
    9. end


    Такая схема требует наличие полей resource_type и resource_id для модели Comment, и в руководствах по Rails стандартно определяют resource_type как VARCHAR (255). Долгое время я не обращал на это никакого внимания, однако на днях подумал, а почему бы не сделать это поле типа Enum ('Post','Image') NOT NULL, ведь при известном количестве моделей, участвующих в полиморфной связи это не должно вызывать никаких проблем и работает быстрее.

    Чтобы убедиться в выгоде использования ENUM я провел маленький опыт и сделал два приложения на Rails с указанными выше моделями. Для каждого приложения специальным скриптом сформировал два одинаковых набора данных следующим образом: сгенерировал по 1000 постов и картинок, затем 1 000 000 комментариев случайным образом так чтобы каждый создаваемый комментарий был случайно сопоставлен со случайным постом или картинкой. Этим я хотел добиться расположения комментариев вразнобой, хотя и был введен составной индекс (resource_type, resource_id).

    После генерации данных провёл контрольные выборки для постов c id 100,200, …,1000 и результаты эксперимента привожу в табличке. Испытания проводились на MySQL версии 5.1.37–1ubuntu5

    Таблица с результатами

    Небольшое пояснение к таблице:
    Т1 — таблица с resource_type VARCHAR (10) NOT NULL.
    Т2 — таблица с resource_type ENUM ('Post','Image') NOT NULL.
    Колонки 3 и 5 содержат время исполнения запроса вида
    SELECT * FROM `comments` WHERE resource_type='Post' AND resource_id=N1;.
    Колонки 4 и 6 содержат время исполнения запроса вида 
    SELECT * FROM `posts` INNER JOIN comments ON `comments`.resource_type='Post' AND `comments`.resource_id=`posts`.id WHERE `posts`.id=N1.
    N — номер опыта, N1=N*100.

    После этого я считаю, что имеет смысл использовать ENUM поля для полиморфных связей. Но пока что мне не ясно, какие есть подводные камни? Какие есть у вас соображения насчет полиморфных связей и их использования в народном хозяйстве:)?
    Поделиться публикацией

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 24

      +4
      Да никаких подводных камней. Просто ActiveRecord унифицированная модель для нескольких БД. И не везде есть ENUM.

      Конечно же, надо каждую табличку после стандартных миграций допиливать — добавлять индексы, внешние ключи, менять типы. Та же ситуация не только с полиморфными связями, но и с STI
        0
        Согласен, видимо поэтому в типовых примерах указывают

        t.string :resource_type
        t.integer :resource_id
        +3
        Если бы вы добавили краткую аннотацию, что такое полиморфные связи, зачем они нужны и в чём преимущество их использования, мне кажется статья стала бы полнее и полезнее.
          +1
          В самом деле. Добавлю.
            +2
            у меня сейчас в черновиках валяется статья, уже пару дней тружусь, именно про то, что такое полиморфные ассоциации и как их использовать. Поначалу даже подумал, что это моя статья каким-то образом появилась в общей ленте:) Знатно испугался, знаете ли:)
              +1
              Ну я же не телепат :) С удовольствием почитаю вашу статью.
                0
                Я опять за нее взялся, думаю выложить в понедельник, а то RoR-статьи не особо пользуются спросом, в выходной быстро вниз уйдет :(
                  0
                  Да не, вроде нормально. Ну можно еще в гуглгруппе написать пост, основное же сообщество там.
                    0
                    было бы кого там этому учить:)
                      0
                      Ну в группе очень много человек, просто не каждый там себя проявляет. В любом случае спросить мнения стоит.
            0
            Более наглядно результаты выглядили бы на графике
              0
              К сожалению нет. Данные формировались псевдослучайно, и видно что разброс не велик, поэтому я и решил взять матожидание и не строить график. Ниже в комментариях я привел ссылку на скрипт генерации данных.
            • НЛО прилетело и опубликовало эту надпись здесь
                0
                это не так :) просто не все тут.
                0
                А кто прокомментирует момент с изменением ENUM на большом количестве данных?
                Спасибо за тесты.
                  0
                  Поясните пожалуйста детальнее. Не совсем понятно что вы подразумеваете под изменением ENUM и о каком количестве данных идет речь?
                    0
                    Изменение схемы имеется ввиду, когда добавляется новое значение в ENUM, например.
                      0
                      Попытаюсь получить точные цифры. Если на глаз, то порядка 10 минут на 1000000 записей.
                        0
                        Причем мой MySQL идет со стандартным конфигом, и компьютер у меня не то что бы очень.
                        +1
                        Обещанные данные:
                        1. ENUM -> VARCHAR — 1053.34708200 секунд
                        2. VARCHAR -> ENUM('Post','Image') — 535.87988500 секунд
                        3. ENUM('Post','Image') -> Enum('Post','Image','Something') — 826.80801800 секунд

                        на 1000000 записей.
                          0
                          а ENUM('Post','Image','Reserved1','Reserved2') -> ENUM('Post','Image','Something','Reserved2') — сколько секунд?

                          осмелюсь предположить что где-то около 0.2с :)
                    +1
                    Что-то неоднозначные соотношения в результирующей таблице наводят на мысли о неодинаковости наборов исходных данных. Надо было одним скриптом оба варианта заполнять, чтобы все посты в обоих случаях имели одни и те же комментарии.

                    P.S. В независимости от того какой вариант быстрее в реальности, нет смысла вводить ENUM пока в результате профайлинга реального приложения не доказано, что использование VARCHAR является узким местом в производительности.
                    Ибо преждевременная оптимизация — одно из самых больших зол в программировании.
                      0
                      Данные одинаковы в обоих таблицах. В подтверждение вот скрипт генерации gist.github.com/264003

                      Насчет преждевременной оптимизации, как мне кажется, речи не идет. Скорее тут вопрос проектирования БД. Разумеется можно оставлять VARCHAR, и потом по необходимости менять. Зависит от проекта и лично опыта.
                      +1
                      Если я не ошибаюсь, то, что вы описываете в качестве альтернативы — это STI(Single Table Inheritance). Вообще вот здесь написано, что стоит использовать их не стоит, т.к. «модели будут связаны намертво» и потом их фиг отдерёшь друг от друга. Но вообще STI — иногда очень удобная штука, поэтому в каждом случае нужно решать отдельно, что вы будете использовать.

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое