Как вы управляете SQL-запросами


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

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

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

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

Author: Rik Heywood, 2008-09-01

10 answers

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

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

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

Во-вторых, хранимые процедуры вышли из моды в моих глазах. Я пришел из школы идеологической обработки Виндны, в которой говорилось, что хранимые процедуры должны использоваться постоянно. Сегодня я вижу хранимые процедуры как уровень 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'];

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

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

С точки зрения производительности хранимые процедуры являются самыми быстрыми, но, как правило, не намного превосходят необработанный 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);

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

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

 29
Author: Shabbyrobe, 2017-05-23 12:02:33

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

SELECT * FROM sometable WHERE iUser = ?

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

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

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

 4
Author: Gary Richardson, 2008-09-01 15:19:37

Мне пришлось очистить проект, в котором было много (повторяющихся/похожих) запросов, пронизанных уязвимостями для инъекций. Первыми шагами, которые я предпринял, были использование заполнителей и пометка каждого запроса объектом/методом и исходной строкой, в которой был создан запрос. (Вставьте PHP-константы МЕТОД и СТРОКУ в строку комментария SQL)

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

--@Строка:151 Класс пользователя::getuser():

SELECT * FROM USERS;

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

 3
Author: Willem, 2008-09-17 21:14:54

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

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

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

 3
Author: Andomar, 2010-07-02 14:29:25

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

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

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

 2
Author: Matthew Watson, 2008-09-01 11:41:16

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

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

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

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

 2
Author: mattruma, 2008-09-01 11:48:01

Существуют некоторые библиотеки, такие как 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" - вы можете попробовать его по частям, и посмотри, ненавидишь ты это или нет.

 1
Author: Slacker, 2008-09-01 17:36:32

В этом другом вопросе также есть несколько полезных ссылок...

 0
Author: Rik Heywood, 2017-05-23 12:02:33

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

 0
Author: user36670, 2008-11-14 22:31:47

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

Например.

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');

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

echo $blogposts[0]['title'];
 0
Author: lewis, 2017-04-11 07:15:26