2009-03-17 12 views
5

Soy algo nuevo en las bases de datos transaccionales y he encontrado un problema que intento comprender.python postgres cursor timestamp número

He creado una demostración simple donde se almacena una conexión de base de datos dentro de cada uno de los 5 subprocesos creados por cherrypy. Tengo un método que muestra una tabla de marcas de tiempo almacenadas en la base de datos y un botón para agregar un nuevo registro de marcas de tiempo.

la tabla tiene 2 campos, uno para datetime.datetime.now() timestamp pasado por python y otro para la marca de tiempo de la base de datos establecida en forma predeterminada AHORA().


CREATE TABLE test (given_time timestamp, 
        default_time timestamp DEFAULT NOW()); 

Tengo 2 métodos que interactúan con la base de datos. El primero creará un nuevo cursor, insertará un nuevo given_timestamp, confirmará el cursor y regresará a la página de índice. El segundo método creará un nuevo cursor, seleccionará las 10 marcas de tiempo más recientes y las devolverá a la persona que llama.


import sys 
import datetime 
import psycopg2 
import cherrypy 

def connect(thread_index): 
    # Create a connection and store it in the current thread 
    cherrypy.thread_data.db = psycopg2.connect('dbname=timestamps') 

# Tell CherryPy to call "connect" for each thread, when it starts up 
cherrypy.engine.subscribe('start_thread', connect) 

class Root: 
    @cherrypy.expose 
    def index(self): 
     html = [] 
     html.append("<html><body>") 

     html.append("<table border=1><thead>") 
     html.append("<tr><td>Given Time</td><td>Default Time</td></tr>") 
     html.append("</thead><tbody>") 

     for given, default in self.get_timestamps(): 
      html.append("<tr><td>%s<td>%s" % (given, default)) 

     html.append("</tbody>") 
     html.append("</table>") 

     html.append("<form action='add_timestamp' method='post'>") 
     html.append("<input type='submit' value='Add Timestamp'/>") 
     html.append("</form>") 

     html.append("</body></html>") 
     return "\n".join(html) 

    @cherrypy.expose 
    def add_timestamp(self): 
     c = cherrypy.thread_data.db.cursor() 
     now = datetime.datetime.now() 
     c.execute("insert into test (given_time) values ('%s')" % now) 
     c.connection.commit() 
     c.close() 
     raise cherrypy.HTTPRedirect('/') 

    def get_timestamps(self): 
     c = cherrypy.thread_data.db.cursor() 
     c.execute("select * from test order by given_time desc limit 10") 
     records = c.fetchall() 
     c.close() 
     return records 

if __name__ == '__main__': 

    cherrypy.config.update({'server.socket_host': '0.0.0.0', 
          'server.socket_port': 8081, 
          'server.thread_pool': 5, 
          'tools.log_headers.on': False, 
          }) 

    cherrypy.quickstart(Root()) 

Yo esperaría que las marcas de tiempo y given_time default_time sean sólo unos pocos microsegundos separados unos de otros. Sin embargo, estoy obteniendo un comportamiento extraño. Si agrego marcas de tiempo cada pocos segundos, el tiempo predeterminado no está a unos pocos microsegundos del tiempo_dido, pero generalmente está a unos pocos microsegundos del anterior given_time.

 
Given Time     Default Time 
2009-03-18 09:31:30.725017 2009-03-18 09:31:25.218871 
2009-03-18 09:31:25.198022 2009-03-18 09:31:17.642010 
2009-03-18 09:31:17.622439 2009-03-18 09:31:08.266720 
2009-03-18 09:31:08.246084 2009-03-18 09:31:01.970120 
2009-03-18 09:31:01.950780 2009-03-18 09:30:53.571090 
2009-03-18 09:30:53.550952 2009-03-18 09:30:47.260795 
2009-03-18 09:30:47.239150 2009-03-18 09:30:41.177318 
2009-03-18 09:30:41.151950 2009-03-18 09:30:36.005037 
2009-03-18 09:30:35.983541 2009-03-18 09:30:31.666679 
2009-03-18 09:30:31.649717 2009-03-18 09:30:28.319693 

Sin embargo, si añado una nueva marca de tiempo una vez por minuto, tanto el given_time y default_time son sólo unos pocos microsegundos fuera como se esperaba. Sin embargo, después de enviar la sexta marca de tiempo (el número de subprocesos + 1), el tiempo predeterminado está a unos microsegundos de la primera marca de tiempo given_time.

 
Given Time     Default Time 
2009-03-18 09:38:15.906788 2009-03-18 09:33:58.839075 
2009-03-18 09:37:19.520227 2009-03-18 09:37:19.520293 
2009-03-18 09:36:04.744987 2009-03-18 09:36:04.745039 
2009-03-18 09:35:05.958962 2009-03-18 09:35:05.959053 
2009-03-18 09:34:10.961227 2009-03-18 09:34:10.961298 
2009-03-18 09:33:58.822138 2009-03-18 09:33:55.423485 

A pesar de que estoy cerrando explícitamente el cursor después de cada uso, se observa que el cursor anterior aún está siendo reutilizado. ¿Cómo es posible si estoy cerrando el cursor después de que termine con él y creando un nuevo cursor cada vez? ¿Alguien puede explicar lo que está pasando aquí?

cerca de una respuesta:

He añadido una cursor.connection.commit() al método get_timestamps y que ahora me da datos precisos con las marcas de tiempo. ¿Alguien puede explicar por qué podría necesitar llamar a cursor.connection.commit() cuando todo lo que hago es una selección? Supongo que cada vez que obtengo un cursor, una transacción comienza (o continúa con una unidad de transacción existente). ¿Hay una manera mejor de hacer esto o estoy atrapado cometiendo cada vez que obtengo un cursor independientemente de lo que hago con ese cursor?

Respuesta

1

Para hacer frente a la cuestión planteada por las ediciones más recientes:

En PostgreSQL, NOW() es no la hora actual, pero el tiempo al inicio de la transacción actual. Psycopg2 probablemente esté comenzando una transacción implícitamente para usted, y dado que la transacción nunca se cierra (por una confirmación o de otro modo), la marca de tiempo se "atasca" y se vuelve obsoleta.

arreglos posibles:

  • comprometerse con frecuencia (tonto si sólo está haciendo selecciona)
  • configurar psycopg2 utilizar el comportamiento diferente para la creación automática de transacciones (probablemente difícil de hacerlo bien, y lo hará afectar a otras partes de su aplicación)
  • utilizar una función de marca de tiempo diferente, como statement_timestamp() (no-SQL compatible con el estándar, pero por lo demás perfecto para este escenario)

De the manual, section 9.9.4, énfasis añadido:

PostgreSQL ofrece una serie de funciones que devuelven valores relacionados a la fecha y hora actuales. Estos funciones SQL estándar Todas Volver valores basados ​​en la hora de inicio de la transacción actual :

  • CURRENT_DATE
  • CURRENT_TIME
  • CURRENT_TIMESTAMP
  • CURRENT_TIME(precision)
  • CURRENT_TIMESTAMP(precision)
  • LOCALTIMELOCALTIMESTAMP
  • LOCALTIME(precision)
  • LOCALTIMESTAMP(precision)

CURRENT_TIME y CURRENT_TIMESTAMP entregas valores con la zona horaria; LOCALTIME y LOCALTIMESTAMP entregan valores sin zona horaria.

CURRENT_TIME, CURRENT_TIMESTAMP, LOCALTIME, y LOCALTIMESTAMP puede opcionalmente darse un parámetro de precisión , lo que provoca el resultado a se redondeará a que muchos fraccionarios dígitos en el campo de segundos. Sin un parámetro de precisión , el resultado es dado a la precisión total disponible.

...

Desde estas funciones devuelven el tiempo inicio de la transacción actual, sus valores no cambian durante la transacción . Esto se considera una característica : la intención es permitir a un sola transacción para tener una noción coherente de la "actual" tiempo, de modo que múltiples modificaciones dentro de la misma transacción llevan la misma marca de tiempo.

Nota: Otros sistemas de bases de datos pueden avanzar estos valores con mayor frecuencia.

PostgreSQL también proporciona funciones que devuelven la hora de inicio de la instrucción actual , así como la tiempo real de corriente en el instante de la función se llama. La lista completa de las funciones de tiempo no SQL estándar es:

  • now()
  • transaction_timestamp()
  • statement_timestamp()
  • clock_timestamp()
  • timeofday()

now() PostgreSQL es un tradicional equivalente a CURRENT_TIMESTAMP. transaction_timestamp() es igualmente equivalente a CURRENT_TIMESTAMP, pero lleva el nombre para reflejar claramente lo que devuelve. statement_timestamp() devuelve la hora de inicio de la actual declaración (más específicamente, la hora de recepción del último comando mensaje del cliente). statement_timestamp() y transaction_timestamp() devolver el mismo valor durante el primer comando de una transacción, pero podría diferir durante comandos subsiguientes. clock_timestamp() devuelve la hora actual , y por lo tanto su valor cambia incluso dentro de un único comando SQL . timeofday() es una función histórica PostgreSQL. Como clock_timestamp(), devuelve la hora actual , pero como una cadena de texto formateada en lugar de una marca de tiempo con un valor de zona horaria.

+0

Gracias por explicar esto. Aún no he probado sus sugerencias, pero acepté su respuesta por hacer el mejor trabajo al explicar por qué la marca de tiempo sería incorrecta. Sin embargo, ahora me pregunto si existe una forma de crear un cursor sin iniciar una transacción. – adam

+0

Puede configurar Psycopg2 en el nivel de aislamiento de transacción 'ISOLATION_LEVEL_AUTOCOMMIT', que no iniciará transacciones cuando se emitan comandos. Sin embargo, no sé cuán amplio sería ese cambio; hacerlo podría romper otras consultas que usan transacciones. – kquinn

3

intente llamar c.close() como se describe en la documentación del módulo: http://tools.cherrypy.org/wiki/Databases

def add_timestamp(self): 
     c = cherrypy.thread_data.db.cursor() 
     now = datetime.datetime.now() 
     c.execute("insert into test (given_time) values ('%s')" % now) 
     c.connection.commit() 
     c.close() 
     raise cherrypy.HTTPRedirect('/') 

def get_timestamps(self): 
     c = cherrypy.thread_data.db.cursor() 
     c.execute("select * from test order by given_time desc limit 10") 
     records = c.fetchall() 
     c.close() 
     return records 
+0

Acabo de hacer esos cambios y reinicié el servidor cherrypy y sigo teniendo el mismo problema. – adam

+0

Actualización: pasamos de postgres 8.0 a 8.3 y esta respuesta ahora también funciona. – adam

0

he añadido un envío al método que selecciona las marcas de tiempo y que se ha resuelto el problema.

def get_timestamps(self): 
    c = cherrypy.thread_data.db.cursor() 
    c.execute("select * from test order by given_time desc limit 10") 
    records = c.fetchall() 
    c.connection.commit() # Adding this line fixes the timestamp issue 
    c.close() 
    return records 

Puede alguien explicar por qué me tendría que llamar cursor.connection.commit() cuando todo lo que estoy haciendo es un selecto?