Pregunta

Por el momento, mi código (PHP) tiene demasiadas consultas SQL.p.ej...

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

Estoy considerando el uso de procedimientos almacenados para reducir esto y hacer las cosas un poco más sólidas, pero tengo algunas dudas.

Tengo cientos de consultas diferentes en uso en el sitio web y muchas de ellas son bastante similares.¿Cómo debo gestionar todas estas consultas cuando se eliminan de su contexto (el código que utiliza los resultados) y se colocan en un procedimiento almacenado en la base de datos?

¿Fue útil?

Solución

El mejor curso de acción para usted dependerá de cómo aborde su acceso a los datos.Hay tres enfoques que puede adoptar:

  • Usar procedimientos almacenados
  • Mantenga las consultas en el código (pero coloque todas sus consultas en funciones y arregle todo para usar PDO para los parámetros, como se mencionó anteriormente)
  • Utilice una herramienta ORM

Si desea pasar su propio SQL sin formato al motor de base de datos, entonces los procedimientos almacenados serían el camino a seguir si todo lo que desea hacer es obtener el SQL sin formato de su código PHP pero mantenerlo relativamente sin cambios.El debate entre procedimientos almacenados y SQL sin formato es una especie de guerra santa, pero K.Scott Allen hace un comentario excelente -aunque descartable- en un artículo sobre versiones de bases de datos:

En segundo lugar, los procedimientos almacenados han caído en desgracia ante mis ojos.Vengo de la escuela de adoctrinamiento WinDNA que decía que los procedimientos almacenados deberían usarse todo el tiempo.Hoy veo los procedimientos almacenados como una capa API para la base de datos.Esto es bueno si necesita una capa API a nivel de base de datos, pero veo que muchas aplicaciones incurren en la sobrecarga de crear y mantener una capa API adicional que no necesitan.En esas aplicaciones, los procedimientos almacenados son más una carga que un beneficio.

Tiendo a inclinarme por no utilizar procedimientos almacenados.He trabajado en proyectos donde la base de datos tiene una API expuesta a través de procedimientos almacenados, pero los procedimientos almacenados pueden imponer algunas limitaciones propias, y esos proyectos tienen todo, en diversos grados, utilizó SQL sin formato generado dinámicamente en el código para acceder a la base de datos.

Tener una capa API en la base de datos brinda una mejor delimitación de responsabilidades entre el equipo de base de datos y el equipo de desarrollo a expensas de parte de la flexibilidad que tendría si la consulta se mantuviera en el código; sin embargo, es menos probable que los proyectos PHP tengan dimensiones considerables. suficientes equipos para beneficiarse de esta delimitación.

Conceptualmente, probablemente debería tener versionada su base de datos.Sin embargo, en términos prácticos, es mucho más probable que solo tenga versionado su código que su base de datos.Es probable que cambie sus consultas cuando realice cambios en su código, pero si cambia las consultas en los procedimientos almacenados almacenados en la base de datos, entonces probablemente no las registrará cuando registre el código y pierda muchos de los beneficios del control de versiones para un área importante de su aplicación.

Independientemente de si elige o no utilizar procedimientos almacenados, al menos debe asegurarse de que cada operación de la base de datos se almacene en una función independiente en lugar de estar integrada en cada uno de los scripts de su página, esencialmente una capa API para su base de datos que se mantiene y versiona con su código.Si está utilizando procedimientos almacenados, esto significará efectivamente que tiene dos capas de API para su base de datos, una con el código y otra con la base de datos, lo que puede considerar que complica innecesariamente las cosas si su proyecto no tiene equipos separados.Ciertamente lo hago.

Si el problema es de claridad del código, hay maneras de hacer que el código con SQL bloqueado sea más presentable, y la clase UserManager que se muestra a continuación es una buena manera de comenzar: la clase solo contiene consultas relacionadas con la tabla 'usuario'. cada consulta tiene su propio método en la clase y las consultas tienen sangría en las declaraciones de preparación y se formatean como lo haría en un procedimiento almacenado.

// 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'];

Sin embargo, si sus consultas son bastante similares pero tiene una gran cantidad de permutaciones en las condiciones de su consulta, como paginación, clasificación, filtrado, etc. complicados, una herramienta de mapeo de objetos/relacional es probablemente el camino a seguir, aunque el proceso de revisión de su código existente Hacer uso de la herramienta podría resultar bastante complicado.

Si decide investigar las herramientas ORM, debería considerar Impulsar, el componente ActiveRecord de yii, o el ORM PHP rey-papá, Doctrina.Cada uno de estos le brinda la capacidad de crear consultas a su base de datos mediante programación con todo tipo de lógica complicada.Doctrine es el que tiene más funciones y te permite crear una plantilla para tu base de datos con elementos como el Patrón de árbol de conjunto anidado fuera de la caja.

En términos de rendimiento, los procedimientos almacenados son los más rápidos, pero generalmente no mucho más que SQL sin formato.Las herramientas ORM pueden tener un impacto significativo en el rendimiento de varias maneras: consultas ineficientes o redundantes, E/S de archivos enormes al cargar las bibliotecas ORM en cada solicitud, generación dinámica de SQL en cada consulta...Todas estas cosas pueden tener un impacto, pero el uso de una herramienta ORM puede aumentar drásticamente el poder disponible con una cantidad de código mucho menor que crear su propia capa de base de datos con consultas manuales.

Gary Richardson Sin embargo, tiene toda la razón: si va a continuar usando SQL en su código, siempre debe usar las declaraciones preparadas de PDO para manejar los parámetros, independientemente de si está usando una consulta o un procedimiento almacenado.La higienización de los insumos la realiza PDO para usted.

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

Advertencia:suponiendo que el ID es 1, el script anterior generará string(1) "1". PDO->lastInsertId() devuelve el ID como una cadena independientemente de si la columna real es un número entero o no.Probablemente esto nunca será un problema para usted ya que PHP realiza la conversión de cadenas a números enteros automáticamente.

Lo siguiente generará bool(true):

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

pero si tiene un código que espera que el valor sea un número entero, como es_int o PHP "es realmente, verdaderamente, 100% igual a" operador:

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

podrías encontrarte con algunos problemas.

Editar: Una buena discusión sobre procedimientos almacenados. aquí

Otros consejos

En primer lugar, debes utilizar marcadores de posición en tu consulta en lugar de interpolar las variables directamente.PDO/MySQLi le permite escribir sus consultas como:

SELECT * FROM sometable WHERE iUser = ?

La API sustituirá de forma segura los valores en la consulta.

También prefiero tener mis consultas en el código en lugar de en la base de datos.Es mucho más fácil trabajar con un RCS cuando las consultas son con su código.

Tengo una regla general cuando trabajo con ORM:si estoy trabajando con una entidad a la vez, usaré la interfaz.Si estoy informando/trabajando con registros en conjunto, normalmente escribo consultas SQL para hacerlo.Esto significa que hay muy pocas consultas en mi código.

Tuve que limpiar un proyecto con muchas consultas (duplicadas/similares) plagadas de vulnerabilidades de inyección.Los primeros pasos que tomé fueron usar marcadores de posición y etiquetar cada consulta con el objeto/método y la línea fuente en la que se creó la consulta.(Inserte las constantes PHP MÉTODO y LÍNEA en una línea de comentario SQL)

Se parecía a esto:

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

SELECT * FROM USERS;

El registro de todas las consultas durante un breve periodo de tiempo me proporcionó algunos puntos de partida sobre qué consultas fusionar.(¡Y donde!)

Movería todo el SQL a un módulo Perl separado (.pm). Muchas consultas podrían reutilizar las mismas funciones, con parámetros ligeramente diferentes.

Un error común de los desarrolladores es sumergirse en bibliotecas ORM, consultas parametrizadas y procedimientos almacenados.Luego trabajamos durante meses seguidos para hacer que el código sea "mejor", pero sólo es "mejor" en una especie de desarrollo.¡No vas a crear ninguna característica nueva!

Utilice la complejidad en su código solo para abordar las necesidades del cliente.

Utilice un paquete ORM, cualquier paquete medio decente le permitirá

  1. Obtenga conjuntos de resultados simples
  2. Mantenga su SQL complejo cerca del modelo de datos

Si tiene SQL muy complejo, las vistas también son buenas para hacerlo más presentable para las diferentes capas de su aplicación.

Estuvimos en una situación similar en algún momento.Consultamos una tabla específica de diversas formas, mayores de 50 años.

Lo que terminamos haciendo fue crear un único procedimiento almacenado Fetch que incluye un valor de parámetro para WhereClause.WhereClause se construyó en un objeto Proveedor, empleamos el patrón de diseño Fachada, donde pudimos fregar Úselo para cualquier ataque de inyección SQL.

En lo que respecta al mantenimiento, es fácil de modificar.SQL Server también es bastante amigo y almacena en caché los planes de ejecución de consultas dinámicas para que el rendimiento general sea bastante bueno.

Tendrás que determinar el actuación desventajas basadas en su propio sistema y necesidades, pero en general, esto funciona muy bien para nosotros.

Hay algunas bibliotecas, como MDB2 en PEAR, que hacen que las consultas sean un poco más fáciles y seguras.

Desafortunadamente, su configuración puede resultar un poco complicada y, a veces, es necesario pasarles la misma información dos veces.He usado MDB2 en un par de proyectos y tendía a escribir una fina capa alrededor de él, especialmente para especificar los tipos de campos.Generalmente creo un objeto que conoce una tabla en particular y sus columnas, y luego una función auxiliar que completa los tipos de campos cuando llamo a una función de consulta MDB2.

Por ejemplo:

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

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

    return $Types;
}

Obviamente, este objeto tiene un mapa de nombres de tablas -> esquemas que conoce, y simplemente extrae los tipos de campos que usted especifica y devuelve una matriz de tipos coincidentes adecuada para usar con una consulta MDB2.

MDB2 (y bibliotecas similares) luego manejan la sustitución de parámetros por usted, por lo que para consultas de actualización/inserción, simplemente crea un hash/mapa desde el nombre de la columna hasta el valor y usa las funciones 'autoExecute' para crear y ejecutar la consulta relevante.

Por ejemplo:

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

y MDB2 creará la consulta, escapando de todo correctamente, etc.

Sin embargo, recomendaría medir el rendimiento con MDB2, ya que incorpora una buena cantidad de código que podría causarle problemas si no está ejecutando un acelerador PHP.

Como digo, la sobrecarga de configuración parece desalentadora al principio, pero una vez hecha, las consultas pueden ser más simples/más simbólicas de escribir y (especialmente) modificar.Creo que MDB2 debería saber un poco más sobre su esquema, lo que simplificaría algunas de las llamadas API comúnmente utilizadas, pero puede reducir la molestia de esto encapsulando el esquema usted mismo, como mencioné anteriormente, y proporcionando funciones de acceso simples que generen el arrays MDB2 necesita para realizar estas consultas.

Por supuesto, si lo desea, puede realizar consultas SQL planas como una cadena usando la función query(), de modo que no esté obligado a cambiar a la 'forma MDB2' completa; puede probarlo poco a poco y ver si Odiarlo o no.

esta otra pregunta También tiene algunos enlaces útiles...

Utilice un marco ORM como QCodo: puede mapear fácilmente su base de datos existente

Intento utilizar funciones bastante genéricas y simplemente pasar las diferencias entre ellas.De esta manera, solo tendrá una función para manejar la mayoría de los SELECT de su base de datos.Obviamente puedes crear otra función para manejar todos tus INSERTOS.

p.ej.

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;
}

Esto se me viene a la cabeza, pero ya entiendes la idea.Para usarlo simplemente pase a la función los parámetros necesarios:

p.ej.

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

En este caso $publicaciones de blog será una matriz de matrices que representan cada fila de la tabla.Luego puedes usar un foreach o consultar la matriz directamente:

echo $blogposts[0]['title'];
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top