Как вы управляете запросами SQL - PullRequest
14 голосов
/ 01 сентября 2008

В данный момент в моем коде (PHP) слишком много SQL-запросов. например ...

// not a real example, but you get the idea...
$results = $db->GetResults("SELECT * FROM sometable WHERE iUser=$userid");
if ($results) {
    // Do something
}

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

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

Ответы [ 10 ]

29 голосов
/ 18 сентября 2008

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

  • Использовать хранимые процедуры
  • Сохраняйте запросы в коде (но поместите все свои запросы в функции и исправьте все, чтобы использовать параметры PDO, как упоминалось ранее)
  • Используйте инструмент ORM

Если вы хотите передать свой собственный необработанный SQL в ядро ​​базы данных, то хранимые процедуры - это то, что вам нужно, если все, что вам нужно, это извлечь необработанный SQL из вашего кода PHP, но сохранить его относительно неизменным. Хранимые процедуры против дебатов по сырому SQL - это что-то вроде священной войны, но К. Скотт Аллен делает отличное замечание - хотя и однозначно - в статье о версиях баз данных :

Во-вторых, хранимые процедуры перестали быть в моих глазах. Я приехал из школы идеологической обработки WinDNA, которая сказала, что хранимые процедуры должны использоваться все время. Сегодня я рассматриваю хранимые процедуры как уровень API для базы данных. Это хорошо, если вам нужен уровень API на уровне базы данных, но я вижу множество приложений, которые несут накладные расходы на создание и поддержку дополнительного уровня API, который им не нужен. В этих приложениях хранимые процедуры являются большим бременем, чем выгодой.

Я склонен не использовать хранимые процедуры. Я работал над проектами, в которых БД имеет API, предоставляемый через хранимые процедуры, но хранимые процедуры могут накладывать некоторые собственные ограничения, и в этих проектах все , в различной степени, используется динамически генерируемый необработанный SQL в код для доступа к БД.

Наличие уровня API в БД позволяет лучше разграничить обязанности между командой БД и командой разработчиков за счет некоторой гибкости, которую вы имели бы, если бы запрос хранился в коде, однако проекты PHP менее вероятны иметь достаточно значительные команды, чтобы извлечь выгоду из этого разграничения.

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

Независимо от того, решите вы или нет использовать хранимые процедуры, вы должны как минимум гарантировать, что каждая операция базы данных хранится в независимой функции, а не встраивается в каждый из сценариев вашей страницы - по сути, уровень API для ваша БД, которая поддерживается и поддерживается с вашим кодом. Если вы используете хранимые процедуры, это фактически означает, что у вас есть два уровня API для вашей БД, один с кодом и один с БД, что может показаться вам излишне усложняющим, если в вашем проекте нет отдельных команд. Я конечно делаю.

Если проблема связана с аккуратностью кода, есть способы сделать код с застрявшим в нем SQL более презентабельным, и класс UserManager, показанный ниже, является хорошим способом для начала - класс содержит только запросы, относящиеся к пользователю 'таблица, каждый запрос имеет свой собственный метод в классе, и запросы вставляются в операторы prepare и форматируются так, как если бы вы форматировали их в хранимой процедуре.

// UserManager.php:

class UserManager
{
    function getUsers()
    {
        $pdo = new PDO(...);
        $stmt = $pdo->prepare('
            SELECT       u.userId as id,
                         u.userName,
                         g.groupId,
                         g.groupName
            FROM         user u
            INNER JOIN   group g
            ON           u.groupId = g.groupId
            ORDER BY     u.userName, g.groupName
        ');
        // iterate over result and prepare return value
    }

    function getUser($id) {
        // db code here
    }
}

// index.php:
require_once("UserManager.php");
$um = new UserManager;
$users = $um->getUsers();
foreach ($users as $user) echo $user['name'];

Однако, если ваши запросы очень похожи, но у вас есть огромное количество перестановок в условиях запроса, таких как сложное разбиение на страницы, сортировка, фильтрация и т. Д., Инструмент Object / Relational mapper, вероятно, подходит, хотя процесс капитального ремонта Ваш существующий код для использования инструмента может быть довольно сложным.

Если вы решите исследовать инструменты ORM, вам следует обратиться к Propel , компоненту ActiveRecord Yii или PHP ORM королевского папы, Doctrine, Каждый из них дает вам возможность программно создавать запросы к вашей базе данных со всеми видами сложной логики. Doctrine - это наиболее полнофункциональный инструмент, позволяющий вам создавать шаблоны базы данных с такими вещами, как шаблон дерева вложенных наборов из коробки.

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

Гари Ричардсон абсолютно прав, хотя, если вы собираетесь продолжать использовать SQL в своем коде, вы всегда должны использовать подготовленные операторы PDO для обработки параметров независимо от того, используете ли вы запрос или хранимая процедура. Санация ввода выполняется для вас с помощью PDO.

// optional
$attrs = array(PDO::ATTR_PERSISTENT => true);

// create the PDO object
$pdo = new PDO("mysql:host=localhost;dbname=test", "user", "pass", $attrs);

// also optional, but it makes PDO raise exceptions instead of 
// PHP errors which are far more useful for debugging
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$stmt = $pdo->prepare('INSERT INTO venue(venueName, regionId) VALUES(:venueName, :regionId)');
$stmt->bindValue(":venueName", "test");
$stmt->bindValue(":regionId", 1);

$stmt->execute();

$lastInsertId = $pdo->lastInsertId();
var_dump($lastInsertId);

Предупреждение: при условии, что идентификатор равен 1, вышеприведенный скрипт выдаст string(1) "1". PDO->lastInsertId() возвращает идентификатор в виде строки независимо от того, является ли фактический столбец целым числом или нет. Это, вероятно, никогда не будет проблемой для вас, так как PHP выполняет приведение строк к целым числам автоматически.

Будет выведено bool(true):

// regular equality test
var_dump($lastInsertId == 1); 

но если у вас есть код, который ожидает, что значение будет целым числом, например, is_int или PHP "действительно, действительно, на 100% равно оператору" :

var_dump(is_int($lastInsertId));
var_dump($lastInsertId === 1);

Вы можете столкнуться с некоторыми проблемами.

Редактировать: Хорошее обсуждение хранимых процедур здесь

4 голосов
/ 01 сентября 2008

Прежде всего, вы должны использовать заполнители в вашем запросе вместо прямой интерполяции переменных. PDO / MySQLi позволяет вам писать ваши запросы как:

SELECT * FROM sometable WHERE iUser = ?

API безопасно подставит значения в запрос.

Я также предпочитаю, чтобы мои запросы были в коде, а не в базе данных. Работать с RCS намного проще, когда запросы выполняются с вашим кодом.

У меня есть практическое правило при работе с ORM: если я работаю с одним объектом одновременно, я буду использовать интерфейс Если я пишу / работаю с записями в совокупности, я обычно пишу SQL-запросы для этого. Это означает, что в моем коде очень мало запросов.

3 голосов
/ 10 января 2009

Я бы переместил весь SQL в отдельный модуль Perl (.pm). Многие запросы могут использовать одни и те же функции с немного отличающимися параметрами.

Распространенной ошибкой для разработчиков является погружение в библиотеки ORM, параметризованные запросы и хранимые процедуры. Затем мы работаем несколько месяцев подряд, чтобы сделать код «лучше», но он только «лучше» с точки зрения разработки. Вы не делаете никаких новых функций!

Используйте сложность в своем коде только для удовлетворения потребностей клиентов.

3 голосов
/ 18 сентября 2008

Мне пришлось очистить проект, который во многих (дублирующих / похожих) запросах пронизан уязвимостями к внедрению. Первыми шагами, которые я предпринял, было использование заполнителей и маркировка каждого запроса объектом / методом и исходной строкой, в которой был создан запрос. (Вставьте PHP-константы METHOD и LINE в строку комментария SQL)

Это выглядело примерно так:

- @Line: 151 UserClass :: getuser ():

SELECT * FROM USERS;

Регистрация всех запросов в течение короткого времени дала мне некоторые отправные точки, по которым запросы должны объединяться. (А где!)

2 голосов
/ 01 сентября 2008

Мы были в подобном затруднении в одно время. Мы запрашивали определенную таблицу различными способами, более 50+.

В итоге мы создали единственную хранимую процедуру Fetch, которая включает значение параметра для WhereClause. WhereClause был создан в объекте Provider, мы использовали шаблон проектирования Facade, где мы могли scrub его для любых атак SQL-инъекций.

Итак, что касается обслуживания, его легко изменить. SQL Server также вполне подходит для chum и кэширует планы выполнения динамических запросов, поэтому общая производительность довольно хорошая.

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

2 голосов
/ 01 сентября 2008

Используйте пакет ORM, любой полуприличный пакет позволит вам

  1. Получите простые наборы результатов
  2. Держите ваш сложный SQL близко к модели данных

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

1 голос
/ 01 сентября 2008

Существуют некоторые библиотеки, такие как MDB2 в PEAR, которые делают запросы немного проще и безопаснее.

К сожалению, они могут быть немного многословными, и вам иногда приходится передавать им одну и ту же информацию дважды. Я использовал MDB2 в нескольких проектах, и я старался написать тонкую фанеру вокруг него, особенно для определения типов полей. Обычно я создаю объект, который знает о конкретной таблице и ее столбцах, а затем вспомогательную функцию, которая заполняет типы полей для меня, когда я вызываю функцию запроса MDB2.

Например:

function MakeTableTypes($TableName, $FieldNames)
{
    $Types = array();

    foreach ($FieldNames as $FieldName => $FieldValue)
    {
        $Types[] = $this->Tables[$TableName]['schema'][$FieldName]['type'];
    }

    return $Types;
}

Очевидно, что у этого объекта есть карта имен таблиц -> схем, о которых он знает, он просто извлекает типы полей, которые вы укажете, и возвращает соответствующий массив типов, подходящий для использования с запросом MDB2.

MDB2 (и аналогичные библиотеки) затем обрабатывают подстановку параметров для вас, поэтому для запросов на обновление / вставку вы просто строите хеш / карту от имени столбца к значению и используете функции 'autoExecute' для построения и выполнения соответствующих запрос.

Например:

function UpdateArticle($Article)
{
    $Types = $this->MakeTableTypes($table_name, $Article);

    $res = $this->MDB2->extended->autoExecute($table_name,
        $Article,
        MDB2_AUTOQUERY_UPDATE,
        'id = '.$this->MDB2->quote($Article['id'], 'integer'),
        $Types);
}

и MDB2 создаст запрос, должным образом избегая всего и т. Д.

Я бы порекомендовал измерить производительность с помощью MDB2, поскольку он потребляет немало кода, который может вызвать проблемы, если вы не используете ускоритель PHP.

Как я уже сказал, накладные расходы на первый взгляд кажутся пугающими, но как только они выполнены, запросы могут быть проще / более символичными для написания и (особенно) изменения. Я думаю, что MDB2 должен знать немного больше о вашей схеме, что упростило бы некоторые из наиболее часто используемых вызовов API, но вы можете уменьшить раздражение этого, самостоятельно инкапсулировав схему, как я упоминал выше, и предоставив простые функции доступа, которые генерируют Массивы MDB2 должны выполнять эти запросы.

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

0 голосов
/ 15 ноября 2008

Используйте структуру ORM, такую ​​как QCodo - вы можете легко отобразить вашу существующую базу данных

0 голосов
/ 18 сентября 2008

Я стараюсь использовать довольно общие функции и просто передаю различия в них. Таким образом, у вас есть только одна функция для обработки большей части SELECT вашей базы данных. Очевидно, вы можете создать еще одну функцию для обработки всех ваших вкладышей.

например.

function getFromDB($table, $wherefield=null, $whereval=null, $orderby=null) {
    if($wherefield != null) { 
        $q = "SELECT * FROM $table WHERE $wherefield = '$whereval'"; 
    } else { 
        $q = "SELECT * FROM $table";
    }
    if($orderby != null) { 
        $q .= " ORDER BY ".$orderby; 
    }

    $result = mysql_query($q)) or die("ERROR: ".mysql_error());
    while($row = mysql_fetch_assoc($result)) {
        $records[] = $row;
    }
    return $records;
}

Это просто не в моей голове, но вы поняли идею. Для его использования достаточно передать в функцию необходимые параметры:

например.

$blogposts = getFromDB('myblog', 'author', 'Lewis', 'date DESC');

В этом случае $ blogposts будет массивом массивов, которые представляют каждую строку таблицы. Тогда вы можете просто использовать foreach или обращаться к массиву напрямую:

echo $blogposts[0]['title'];
0 голосов
/ 01 сентября 2008

Этот другой вопрос также содержит несколько полезных ссылок ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...