Подтвердить что ты не робот

Чистая производительность OO-структуры и производительности SQL

При программировании на PHP я всегда стараюсь создавать осмысленные "модели" (классы), соответствующие таблицам в базе данных. Я часто сталкиваюсь со следующей проблемой:

Предполагая, что я создал базу данных с двумя таблицами: authors и blogs, которые имеют соответствующую модель в моем приложении.

Скажем, я хочу напечатать все блоги вместе с информацией об авторе, мне нужно было бы сделать что-то вроде этого:

<?php
foreach ($app->getBlogs() as $blog) {
  echo "<h1>" . $blog->title . "</h1>";
  echo "Written by" . $blog->getAuthor()->name . "</p>";
  // ... et cetera
}
?>

Проблема в том, что приложение теперь будет запускать 1 SQL-запрос, чтобы получить все элементы блога, а также [количество запросов блога], чтобы получить информацию для каждого автора. Используя простой SQL, я мог бы получить эту информацию с помощью простого запроса:

SELECT * FROM blogs
JOIN authors ON authors.id = blogs.author

Каков наилучший способ решения таких проблем: разработка объектно-ориентированного приложения без выполнения слишком многих бесполезных SQL-запросов.

4b9b3361

Ответ 1

IMO, я думаю, вы должны просто написать еще один класс, который будет инкапсулировать то, что у вас есть. Всегда ли имеет смысл для блога иметь автора? У каждого автора есть блог? Может ли автор иметь несколько блогов? Подумайте об этих проблемах, затем создайте класс, который инкапсулирует это. Помните, что типичные схемы базы данных не OO... они реляционные. Да, они близки, но есть тонкие различия.

Итак, если у автора может быть несколько блогов, у вас может быть ключ к многозначному классу какого-либо типа (с ключом на основе идентификатора автора), и вы можете инициализировать или загрузить этот класс с помощью одного вызова SQL. Просто о чем подумать.

Ответ 2

Если вы не знаете, что неэффективные SQL-операции не будут иметь реального влияния, например количество избыточных итераций или затронутых строк, всегда будут небольшими (например, повторение операции над количеством детей в семье, за исключением очень редких случаев, таких как Duggars, можно полагаться меньше 10), я всегда выступал за эффективность реляционного запроса по красоте кода OO.

Хотя уродливый код OO может облегчить обслуживание, неэффективный доступ к данным может привести к тому, что система встанет на колени, как правило, когда вы в отпуске или пытаетесь спать. И большую часть времени вы можете найти хороший компромисс, который делает наиболее эффективные операции SQL имеющими разумно "объективный" интерфейс. Это может стоить вам немного больше времени, когда речь заходит о рефакторинге или добавлении функций, если ваша объектная модель некрасива, но она затрачивает время вашего клиента каждый раз, когда они нажимают эту кнопку (или деньги с точки зрения более крупного оборудования для запуска приложение - никогда не будет хорошим методом оптимизации), а человеко-часы, потраченные с помощью приложения, должны намного опередить человеческие часы, потраченные на его разработку (можно было бы надеяться).

Что касается вашего беспокойства о том, нужен ли интерфейс (заставляя вас разобраться во всех возможных шаблонах потребления), я справился с этим, выполнив все мои модификации данных с помощью хранимых процедур, но позволяя доступ к данным прямо против таблицы и представления, предоставляя всем пользователям только привилегии. Это полубесконечная позиция, так как многие люди хотели бы заблокировать все операции доступа к данным от нисходящих потребителей во имя обеспечения того, чтобы все выполняемые sql соответствовали их стандартам. Но новые способы поиска данных всегда появляются, и если вам нужно добавить новый сохраненный процесс, обновите свои основные библиотеки классов и обновите свой клиентский код каждый раз, когда кто-то хочет реализовать новую функцию, развертывание и квалификация могут вырасти до быть реальным бременем - гораздо больше, чем иметь дело с объектной моделью, которая не соответствует религиозному идеалу. И намного проще реализовать процесс проверки кода, который проверяет, что новые команды выбора, написанные потребителями ниже по потоку, являются кошерными.

Ответ 3

Я большой сторонник ORM, и вот мое взвешивание:

Хорошо торговать неописуемым количеством производительности приложения за тонну производительности разработчика. В наши дни серверы чрезвычайно мощные, и лишнее железо дает нам новую гибкость.

Тем не менее, если вы делаете что-то глупое, что стирает пользовательский интерфейс, доведя сервер до колен, это уже не так. Если бы у вас было миллион авторов в вашем примере, их все вниз и все их поля и повторение через них было бы неразумно. Если у вас было только 20 авторов, то это не имеет большого значения.

В случае огромных наборов данных и дорогостоящих пакетных операций, даже как парень ORM, я должен оптимизировать и писать специальные sprocs или SQL-операторы только для этого случая. И я должен быть осторожным, чтобы не писать свой код таким образом, чтобы я забивал базу данных, было бы лучше использовать шаблон кэширования, где я вытаскиваю большой набор данных и затем отключаю его.

Это большая дискуссия, но для меня это всего лишь вопрос понимания того, что вы не можете решить каждую проблему с помощью одного инструмента.

Ответ 4

Такие вещи, как это именно то, что должен создать ваш собственный уровень данных. В вашей модели для ваших блогов должна быть функция типа getBlogList(), которая вернет названия блога и имя автора в одном запросе.

Ответ 5

Независимо от того, какое решение вы используете, ORM или нет, должно быть способно, если в этом случае выбрать один выбор, а также он должен быть способен выбирать только нужные столбцы. Затем из этого соединения он должен быть способен заполнять объекты авторов соответствующими списками блогов на автора. Необходимость выпуска нескольких SQL является расточительным.

Ответ 6

Propel - это пример PHP ORM, который может справиться с этим. Я уверен, что Доктрина должна быть в состоянии сделать это, хотя я никогда не смотрел на нее.

Зачем изобретать колесо?

Ответ 7

Я использовал некоторые другие привязки. Пример:

<?php
$blogs = $app->getBlogs();
$blogs->getAuthor();
foreach ($blogs as $blog) {
  echo "<h1>" . $blog->title . "</h1>";
  echo "Written by" . $blog->getAuthor()->name . "</p>";
  // ... et cetera
}
?>

- > getAuthor() вызов $blog запрашивает БД только один раз и использует специальный объект для массива, вызов getAuthor() вызывается для каждого (но, как-то оптимизирован для запуска только как один запрос).

Ответ 8

Вы уже ответили на вопрос:

Используя простой SQL, я мог бы получить эту информацию, используя простой запрос

У вас есть выбор между SQL, который извлекает только записи в блогах и SQL, которые извлекают сообщения и авторы блога. Аналогично, у вас есть выбор между некоторым PHP-кодом, который извлекает только сообщения в блогах или PHP-код, который извлекает сообщения в блогах и авторов. Вы должны сделать выбор в отношении своего PHP-кода, так же, как вам нужно сделать выбор в отношении вашего SQL.

Есть много примеров, которые демонстрируют, как это будет работать на практике. Рекомендация использовать Doctrine или Propel также является хорошей.

Ответ 9

Рассмотрим разделение команд/запросов, как описано Грегом Яном и Мартином Фаулером. В вашей модели запроса может быть отключен блог и автор в одну таблицу, оптимизированную для извлечения DTO для вашего уровня представления.

У Грега Янга есть отличная презентация на CQS в InfoQ.

Ответ 10

Это классическая проблема ORM. Многочисленных школ мысли. Не уверен в специфике PHP, но существует несколько стратегий для устранения этого "несоответствия импеданса". Google orm.

Ответ 11

Один из способов сделать это - создать представление с вашим присоединением к нему и отобразить результаты поиска в другой класс, который содержит данные для блога и автора.

Ответ 12

Честно говоря, просто создайте метод в своем классе Blog с именем getBlogsWithAuthors(), а затем запустите

SELECT  *
FROM    blogs
JOIN    authors 
        ON authors.id = blogs.author

Я знаю, что может показаться больным писать такие вещи для каждого модельного класса, но другого способа нет. Однако вы можете сделать его более динамичным:

//this is a method of a model class. 
//Assume $this->table is the table name of the model (ie, Blog)
public function getWith($joinTable, $pivot1, $pivot2)
{
    $sql="SELECT    *
            FROM    {$this->table}
            JOIN    $joinTable 
                    ON $pivot1 = $pivot2";

    return executeQuery($sql);    
}

$blog=new Blog();
$result=$blog->getWith('authors', 'authors.id', 'blogs.author');
[play with results here]

Ответ 13

Вы всегда можете использовать memcached как промежуточный слой. Каждый запрос будет чисто основан на ОЗУ, что означает, что вы можете запускать столько, сколько хотите.