comprobar la unicidad de los elementos antes de insertarlos en la base de datos

Actualmente estoy trabajando en un proyecto en el que el acceso a una API está restringido a usuarios registrados. La API en sí ya está terminada y funciona como se esperaba. Limitar el acceso a la API también ha resultado bastante sencillo. Sin embargo, mi problema (o pregunta, más bien) es cómo garantizar la eficiencia de las interacciones de la base de datos para el registro, la verificación y/o el proceso de objetos perdidos.

He aquí un ejemplo de lo que sucede actualmente:

  1. El usuario solicita una clave API ingresando su dirección de correo electrónico
  2. El usuario recibe un correo electrónico de verificación
  3. El usuario hace clic en el enlace en el correo electrónico y php verifica el hash contra la base de datos
  4. Una vez que se verifica el hash, la clave API se genera, almacena y envía por correo electrónico
  5. Si el usuario olvida/pierde la clave de la API, puede volver a enviarse por correo electrónico
  6. Si no se recibió el correo electrónico de verificación, se puede volver a enviar por correo electrónico

Aquí hay un ejemplo de la estructura de la base de datos: http://s13.postimage.org/h8ao5oo2v/dbstructure.png

Como probablemente pueda imaginar, hay MUCHA interacción con la base de datos detrás de escena para cada uno de estos pasos particulares en el proceso. Un paso que me pregunto sobre la eficiencia es el de verificar la singularidad de ciertos elementos. Obviamente, no queremos ninguna clave API duplicada flotando, ni queremos ningún hash de verificación de correo electrónico duplicado.

Entonces, escribí una función muy simple que verifica la base de datos en busca de estas cosas antes de insertarlas en la base de datos. Sin embargo, este proyecto es del orden de cientos de veces más grande que cualquiera que haya emprendido antes. He creado y mantenido proyectos que atendían entre 500 y 1,000 usuarios antes... pero se estima que este proyecto atenderá a un mínimo de alrededor de 50,000 XNUMX usuarios al día. Estoy extremadamente feliz de haber conseguido finalmente un gran proyecto, pero me siento cada vez más intimidado por su escala.

En cualquier caso, aquí está la función que escribí para interactuar con la base de datos para verificar la singularidad de los elementos antes de almacenarlos.

function isUnique($table, $col, $data) {
  mysql_connect("localhost", "root", "") or die(mysql_error());  
  mysql_select_db("api") or die(mysql_error());
  $check = mysql_query("SELECT ".$col." FROM ".$table." WHERE ".$col."='".$data."'");
  $match = mysql_num_rows($check);
  if($match < 1) {
    return true;
  }
  return false;
  mysql_close('localhost');
}

Esta función se usa junto con otra función que solo genera una cadena aleatoria de 40 dígitos de 0-9, az y AZ para el hash de verificación de correo electrónico, así como la clave API en sí. (función a continuación)

function makeRandom($length = 40) {
  $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  $randomString = '';
  for($i = 0; $i < $length; $i++) {
    $randomString .= $characters[mt_rand(0, strlen($characters) - 1)];
  }
  return $randomString;
}

Y luego, la combinación de esas 2 funciones se usa en 3 páginas diferentes relacionadas con la emisión de claves API: Página uno para registro/solicitud, Página dos para verificación de correo electrónico, Página 3 para claves perdidas o correo electrónico no recibido. Ahora aquí está en la práctica:

$hash   = makeRandom();
$unique = isUnique('users', 'hash', $hash);
if($unique == false) {
  while($unique == false) {
    $hash   = makeRandom();
    $unique = isUnique('users', 'hash', $hash);
  }
}
else {
  $searchactive   = mysql_query("SELECT email, active FROM users WHERE email='".$email."' AND active='1'") or die(mysql_error());
  $matchactive    = mysql_num_rows($searchactive);
  $searchinactive = mysql_query("SELECT email, active FROM users WHERE email='".$email."' AND active='0'") or die(mysql_error());
  $matchinactive  = mysql_num_rows($searchinactive);

  if($matchactive > 0) {
    $hash = mysql_query("SELECT hash FROM users WHERE email='".$email."' AND active='1'") or die(mysql_error());
    $hash = mysql_fetch_assoc($hash);
    $hash = $hash['hash'];
    $msg = 'The email address you entered is already associated with an active API key. <a href="lost.php?email='.$email.'&amp;hash='.$hash.'&active=1">[Recover Lost API Key]</a>';
  }
  elseif($matchinactive > 0) {
    $hash = mysql_query("SELECT hash FROM users WHERE email='".$email."' AND active='0'") or die(mysql_error());
    $hash = mysql_fetch_assoc($hash);
    $hash = $hash['hash'];
    $msg = 'The email address you entered is already pending verification. <a href="lost.php?email='.$email.'&amp;hash='.$hash.'&active=0">[Resend Verification Email]</a>';
  }
}

Mi pregunta principal es esta: con tantas consultas en curso solo para una función tan (aparentemente) simple, ¿esto va a crear más problemas de los que resuelve? Realmente necesito asegurarme de que no haya hashes de verificación duplicados o claves API por razones obvias. Sin embargo, con unas 50 personas que utilizan esta función, ¿va a atascar el servidor debido a la cantidad de consultas SQL? La principal preocupación se debe al bucle while() que se utiliza para comprobar la singularidad del contenido generado antes de insertarlo.

Sé que esto no es una imagen completa de lo que sucede detrás de escena, pero da una pista de cómo funcionan el resto de las páginas. Si se necesita más información sobre el proceso en su conjunto, estaré encantado de publicarla.

¡Gracias por cualquier idea que puedas ofrecer!

preguntado el 03 de mayo de 12 a las 21:05

Para el hash, puede usar la dirección de correo electrónico + time() + rand() en algún orden para evitar duplicados. -

2 Respuestas

Una forma de abordar esto es no verificar si hay duplicados, sino simplemente asegurarse de que nunca sucedan en primer lugar. Entonces, simplemente versione su tabla de usuario (agregue un campo para la versión). Esto será solo un int que avanza cada vez que se cambia la fila del usuario.

Luego, cuando genere su clave aleatoria, agréguele user_id y user_version antes de almacenar la clave.

Ejemplo:

11ap0w9jfoaiwej203989wesef

Donde el primer 1 es el ID_usuario y el segundo 1 es la versión del usuario.

Entonces, incluso en la probabilidad estadísticamente pequeña de que una clave grande se genere dos veces, siempre será única porque su identificación de usuario será única.

contestado el 03 de mayo de 12 a las 21:05

idea interesante... No había pensado en eso. Parece que sería bastante simple, ya que la base de datos ya incrementa el campo ID para cada entrada almacenada. Sin embargo, todavía estaría haciendo una consulta para obtener el número de identificación actual para agregarlo, entonces, ¿reduciría esto las consultas generales realizadas? - codigo.feind

Según entiendo su sistema, ingresará su correo electrónico en la base de datos en el momento de la solicitud y les enviará el hash por correo electrónico. Ya estarás verificando el hash justo en ese momento, y asumo que los colocas en la tabla de usuarios con una bandera que dice que no están verificados. Entonces, en el momento en que autentique esa clave inicial del correo electrónico, ya habrá realizado la consulta y conocerá su identificación de fila. - jonathan barlow

Básicamente, el sistema funciona así: ingrese la dirección de correo electrónico -> verifique la base de datos para el correo electrónico existente + marcador verificado o no verificado -> agregue el correo electrónico a la base de datos o imprima enlaces para recuperar la clave perdida ... Así que supongo que tiene razón. Aunque no tengo claro a qué se refiere cuando dice "agregar una versión de usuario" a la tabla. - codigo.feind

Lo siento, el comentario anterior me hizo sonar bastante estúpido... Permítanme reformular: no tengo claro por qué está sugiriendo agregar la columna user_version a la tabla... La columna ID ya se incrementa automáticamente y está configurada en ÍNDICE único. Entonces, ¿hay alguna razón por la que también se necesitaría la columna user_version? ¿O debería bastar con añadir el ID? - codigo.feind

no importa ... releyendo tu respuesta unas cuantas veces más, finalmente pude comprender el concepto que estabas transmitiendo. (Creo)... Básicamente, está diciendo que agregue una columna "versión" a la tabla de usuarios en la que cuando se crea la entrada inicial, la versión se establece en "1", una vez que se verifica la entrada, la versión se actualiza a " 2", y así sucesivamente... Luego agregue la versión y el ID a la cadena aleatoria, otorgando así tanto un hash de verificación único como una clave API única... ¿correcto? - codigo.feind

Consideraría usar un UUID en lugar de hacer rodar su propia cadena aleatoria. A todos los efectos prácticos será un valor único.

http://dev.mysql.com/doc/refman/5.5/en/miscellaneous-functions.html#function_uuid

contestado el 03 de mayo de 12 a las 21:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.