2012-04-18 16 views
5

Tengo un proyecto de mascota SaaS para la facturación. En él, quiero que mis clientes empiecen con el número de ticket 1001. Claramente, no puedo usar un campo automático simple en Postgres y simplemente agregar 1000 al valor, porque todos mis clientes compartirán la misma base de datos y la misma tabla tickets .. He intentado usar un tipo de columna entera y consultar (pseudo SQL) SELECT LATEST number FROM tickets WHERE client_id = [current client ID] para obtener el último número y luego usar ese número + 1 para obtener el siguiente number. El problema es que con la concurrencia, es muy posible que dos boletos terminen con el mismo número de esta manera. el número que necesito para poder hacer esto dentro de Django o con SQL en bruto (frente a usar Bash o cualquier otra cosa del género).¿Cuál es la forma correcta de secuenciar manualmente una columna en Postgres?

No estoy buscando una manera de forzar a mi ejemplo a trabajar. Solo estoy buscando una solución para mi problema de necesitar aumentar de forma independiente los números de ticket para cada cliente.

+1

Posible engaño: http://stackoverflow.com/questions/6046085/postgresql-sequence-that-ensure-a-unique-id –

+0

No es un duplicado porque eso busca un número único. El mío está buscando el siguiente número en secuencia con respecto a otro campo (el campo del cliente). De hecho, el mío afirma que no puedo usar un campo automático (es decir, serial). – orokusaki

+0

Tengo, entiendo ahora. Afortunadamente mi respuesta funciona para usted (o, mejor aún, alguien más tiene algo más elegante). –

Respuesta

5

No creo que haya una solución "barata" para este problema. La única solución que es segura (pero no necesariamente rápida) en un entorno multiusuario es tener una tabla "mostrador" con una fila para cada cliente.

Cada transacción tiene para bloquear la primera entrada del cliente antes de la inserción de un nuevo billete, algo como esto:

UPDATE cust_numbers 
    SET current_number = current_number + 1 
WHERE cust_id = 42 
RETURNING current_number; 

que va a hacer tres cosas en un solo paso

  1. aumentar la corriente "secuencial" número para ese cliente
  2. bloquear la fila para que otras transacciones que hagan lo mismo tengan que esperar un bloqueo
  3. devolver el nuevo valor de th en la columna

Con ese nuevo número, ahora puede insertar un nuevo ticket. Si la transacción se confirma, también liberará el bloqueo en la tabla cust_numbers, por lo tanto, otras transacciones "esperando un número" pueden continuar.

Puede envolver los dos pasos (actualizar ... devolviendo & la inserción) en una sola función almacenada para que la lógica detrás de esto esté centralizada. Su aplicación solo llamaría al select insert_ticket(...) sin saber cómo se genera el número de ticket.

Es posible que también desee crear un desencadenador en la tabla de clientes para insertar automáticamente una fila en la tabla cust_numbers cuando se crea un nuevo cliente.

La desventaja de esto es que efectivamente serializa las transacciones que están insertando nuevas entradas para el mismo cliente. Dependiendo de la cantidad de insertos en su sistema, esto podría ser un problema de rendimiento.

Editar
Otra desventaja de esto es, que no están obligados para insertar billetes de esa manera que puedan dar lugar a problemas si, por ejemplo, un nuevo desarrollador se olvida de esto.

+0

Excelente, gracias! Sin embargo, preguntas, A) ¿este bloqueo será para toda la mesa, o puedo bloquear una sola fila? No me gustaría que todos los clientes de 2000 tengan que esperar a que se libere este bloqueo del cliente X antes de que puedan crear más tickets (imagina 2 o 3 tickets por concurrencia por segundo), y B) ¿Qué sucede (y cuál es la recuperación adecuada? ruta), si una segunda conexión pregunta por este número mientras está bloqueado? re: "serializar las transacciones": ¿cómo sería un problema de rendimiento cuando las transacciones de tickets anteriores se completen antes que las nuevas? ¿Es un gran problema? Voy a tener un gran volumen. – orokusaki

+2

@orokusaki: Como ya he descrito, solo se bloqueará la fila para ese cliente. La segunda conexión esperará hasta que la primera se comprometa. Sí, esto disminuirá tu rendimiento. Pero puede obtener los números así (lento pero seguro) o obtenerlos rápidamente usando una secuencia, pero luego serían únicos para todos los clientes. Tienes que decidir qué es más importante –

2

Puede crear secuencias para cada cliente y luego establecer el valor de la columna en nextval('name_of_the_sequence'). Así es como funciona realmente el serial; la única diferencia en su caso sería que no usa un valor predeterminado para la columna y tiene más de una secuencia.

Crear esas secuencias eligiendo la correcta al insertar una nueva fila podría hacerse muy bien a través de un procedimiento PL/Pgsql.

+0

Aparte del problema de necesitar 2000 columnas para hacer esto, el problema fundamental con el uso de un campo serie es que una transacción retrotraída todavía usa el número que devuelve 'nextval', por lo que si los números de ticket para un cliente determinado pueden ser 1001, 1002, 1005, 1007, etc., frente a mantener una secuencia real (es decir, el valor de una columna en serie no es una medida precisa de cardinalidad). – orokusaki

+0

Por favor, lea mi respuesta. No hablé sobre columnas en serie o columnas diferentes. Puede crear secuencias por separado y obtener valores de ellas usando 'nextval()' – ThiefMaster

+0

@ThiefMaster, ¿está sugiriendo crear secuencias en un activador (cliente insertado) y luego necesitando SQL dinámico para seleccionar los valores? (Soy un novato de Postgres, así que no estoy seguro de si esto es siquiera posible, pero presumiblemente el OP necesita una forma automática de hacer esto) –

Cuestiones relacionadas