2009-03-17 8 views
5

Je suis un peu nouveau dans les bases de données transactionnelles et j'ai rencontré un problème que j'essaie de comprendre.python postgres cursor timestamp issue

J'ai créé une démonstration simple où une connexion de base de données est stockée dans chacun des 5 threads créés par cherrypy. J'ai une méthode qui affiche une table d'horodatages stockés dans la base de données et un bouton pour ajouter un nouvel enregistrement des horodatages.

La table comporte 2 champs, un pour l'horodatage datetime.datetime.now() transmis par python et un pour l'horodatage de la base de données défini par défaut sur NOW().


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

J'ai deux méthodes qui interagissent avec la base de données. Le premier va créer un nouveau curseur, insérer un nouveau given_timestamp, valider le curseur et retourner à la page d'index. La deuxième méthode créera un nouveau curseur, sélectionnera les 10 horodatages les plus récents et les retournera à l'appelant.


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()) 

Je me attends given_time et default_time horodatages être seulement quelques microsecondes les unes des autres. Cependant, je reçois un comportement étrange. Si j'ajoute des horodateurs toutes les quelques secondes, le default_time n'est pas à quelques microsecondes de l'instant donné, mais il est généralement à quelques microsecondes du précédent donné.

 
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 

Pourtant, si j'ajoute une nouvelle timestamp environ une fois par minute, à la fois la given_time et default_time ne sont que quelques microsecondes au large comme prévu. Cependant, après l'envoi du 6ème horodatage (le nombre de threads + 1), le default_time est à quelques microsecondes du premier horodatage 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 

Même si je ferme explicitement le curseur après chaque utilisation, il semble que le curseur précédent est encore réutilisé. Comment est-ce possible si je ferme le curseur après que j'en ai fini et que je crée un nouveau curseur à chaque fois? Quelqu'un peut-il expliquer ce qui se passe ici?

plus proche d'une réponse:

J'ai ajouté un cursor.connection.commit() à la méthode get_timestamps et qui me donne maintenant des données précises avec les horodateurs. Quelqu'un peut-il expliquer pourquoi je pourrais avoir besoin d'appeler cursor.connection.commit() alors que tout ce que je fais est un select? Je devine que chaque fois que je reçois un curseur, une transaction commence (ou continue avec une unité de transaction existante, elle est validée). Y a-t-il une meilleure façon de faire cela ou est-ce que je suis coincé en train de commettre chaque fois que je reçois un curseur indépendamment de ce que je fais avec ce curseur?

Répondre

1

Pour répondre à la question posée par vos modifications les plus récentes:

Dans PostgreSQL, NOW() est pas l'heure actuelle, mais le temps au début de la transaction en cours. Psycopg2 démarre probablement une transaction implicitement pour vous, et puisque la transaction n'est jamais fermée (par une validation ou autre), l'horodatage est bloqué et devient obsolète.

solutions possibles:

  • COMMIT fréquemment (stupide si vous ne faites Selects)
  • Mettre en place psycopg2 utiliser un comportement différent pour créer automatiquement des transactions (probablement difficiles à obtenir, à droite et sera affecter d'autres parties de votre application)
  • Utilisez une fonction d'horodatage de différent, comme statement_timestamp() (non SQL standard conforme, mais sinon parfait pour ce scénario)

De the manual, section 9.9.4, l'accent ajouté:

PostgreSQL fournit un certain nombre de fonctions qui renvoient des valeurs liées à la date et l'heure. Ces fonctions SQL standard renvoient toutes valeurs basées sur l'heure de début de la transaction en cours:

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

CURRENT_TIME et CURRENT_TIMESTAMP délivrer des valeurs de fuseau horaire; LOCALTIME et LOCALTIMESTAMP fournissent des valeurs sans fuseau horaire.

CURRENT_TIME, CURRENT_TIMESTAMP, LOCALTIME et LOCALTIMESTAMP peut éventuellement être donné un paramètre de précision , ce qui provoque le résultat arrondi à autant de fractions chiffres dans le champ secondes. Sans paramètre de précision , le résultat est donné à la précision totale disponible.

...

Depuis ces fonctions renvoient la heure de début de la transaction en cours, leurs valeurs ne changent pas lors de la transaction . Ceci est considéré comme une caractéristique: l'intention est de permettre une transaction unique d'avoir une notion cohérente du « courant » temps, de sorte que plusieurs modifications dans la même transaction portent la marque même temps .

Remarque: D'autres systèmes de base de données peuvent faire progresser ces valeurs plus fréquemment.

PostgreSQL fournit également des fonctions qui renvoient l'heure de début de l'instruction en cours , ainsi que l'heure réelle à l'instant la fonction est appelée. La liste complète des fonctions de temps non-SQL standard est:

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

now() est un traditionnel PostgreSQL équivalent à CURRENT_TIMESTAMP. transaction_timestamp() est également équivalent à CURRENT_TIMESTAMP, mais est nommé pour refléter clairement ce que renvoie. statement_timestamp() renvoie l'heure de début de l'instruction en cours (plus précisément, l'heure de la réception de la dernière commande message du client). statement_timestamp() et transaction_timestamp() renvoie la même valeur lors de la première commande de une transaction, mais peut différer pendant commandes suivantes. clock_timestamp() renvoie l'heure actuelle , et par conséquent sa valeur change même au sein d'une seule commande SQL . timeofday() est une fonction historique de PostgreSQL . Comme clock_timestamp(), il renvoie l'heure actuelle réelle , mais sous la forme d'une chaîne de texte formatée plutôt que d'un horodatage avec valeur de fuseau horaire.

+0

Merci d'expliquer cela. Je n'ai pas encore testé vos suggestions, mais j'ai accepté votre réponse pour expliquer pourquoi l'horodatage serait incorrect. Cependant, maintenant je me demande s'il y a une manière que je peux créer un curseur sans commencer une transaction. – adam

+0

Vous pouvez définir Psycopg2 au niveau d'isolation de transaction 'ISOLATION_LEVEL_AUTOCOMMIT', qui ne lancera pas de transaction lorsque des commandes sont émises. Je ne sais pas à quel point ce changement serait vaste; cela pourrait casser d'autres requêtes utilisant des transactions. – kquinn

3

essayer d'appeler c.close() tel que décrit dans la documentation du module: 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

Je viens de faire ces changements et redémarré le serveur Cherrypy et j'ai toujours le même problème. – adam

+0

Mise à jour: nous sommes passés de postgres 8.0 à 8.3 et cette réponse fonctionne maintenant aussi bien. – adam

0

j'ai ajouté une validation à la méthode qui sélectionne les horodatages et qui a résolu le problème.

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 

Quelqu'un peut-il expliquer pourquoi je dois appeler cursor.connection.commit() quand tout ce que je fais est une sélection?