2009-03-17 18 views
5

Jestem trochę nowy w transakcyjnych bazach danych i natrafiłem na problem, który próbuję zrozumieć.python postgres kursor znacznik czasu wydania

Stworzyłem prostą demonstrację, w której połączenie z bazą danych jest przechowywane wewnątrz każdego z 5 wątków stworzonych przez cherrypy. Mam metodę, która wyświetla tabelę znaczników czasowych przechowywanych w bazie danych i przycisk, aby dodać nowy rekord znaczników czasu.

tabela ma 2 pola, jeden dla datetime.datetime.now() znacznik czasu przekazany przez pythona i jeden dla znacznika bazy danych ustawiony na domyślny TERAZ().


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

Mam 2 metody interakcji z bazą danych. Pierwszy utworzy nowy kursor, wstawi nową given_timestamp, zatwierdzić kursor i powróci na stronę indeksu. Druga metoda utworzy nowy kursor, wybierz 10 ostatnich znaczników czasu i zwróć je do osoby dzwoniącej.


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

Spodziewałbym się, że znaczniki czasu given_time i default_time są od siebie oddalone o kilka mikrosekund. Jednak dostaję dziwne zachowanie. Jeśli dodaję sygnatury czasowe co kilka sekund, parametr default_time nie jest o kilka mikrosekund wyłączony z określonego czasu, ale zwykle jest o kilka mikrosekund wyłączony z poprzedniego 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 

Jednak, jeśli mogę dodać nowy znacznik czasu mniej więcej raz na minutę, zarówno given_time i default_time tylko kilka mikrosekund off, jak oczekiwano. Jednak po przesłaniu szóstego znacznika czasu (liczba wątków + 1) wartość default_time jest o kilka mikrosekund dłuższa od pierwszego znacznika date_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 

chociaż jestem jawnie zamykając kursor po każdym użyciu, wydaje się, że poprzedni kursor jest nadal do ponownego wykorzystania. Jak to możliwe, jeśli zamykam kursor po tym, jak skończę i za każdym razem tworzę nowy kursor? Czy ktoś może mi wyjaśnić, co tu się dzieje?

Bliżej odpowiedź:

Dodałem cursor.connection.commit() metody get_timestamps i że teraz daje mi dokładnych danych o znacznikach czasu. Czy ktoś może wyjaśnić, dlaczego mógłbym potrzebować wywołać cursor.connection.commit(), gdy wszystko, co robię, jest select? Zgaduję, że za każdym razem, gdy dostaję kursor, rozpoczyna się transakcja (lub kontynuowana z istniejącą jednostką transakcyjną, która zostaje zatwierdzona). Czy istnieje lepszy sposób to zrobić, czy też utknąłem zobowiązujące za każdym razem, gdy uzyskać kursor niezależnie od tego, co robię z tego kursora?

Odpowiedz

1

Aby odpowiedzieć na pytanie postawione przez swoich ostatnich edycji:

w PostgreSQL, NOW() jest nie aktualny czas, ale czas na początku bieżącej transakcji. Psycopg2 prawdopodobnie rozpoczyna transakcję niejawnie dla ciebie, a ponieważ transakcja nie jest nigdy zamknięta (przez zatwierdzenie lub nie), znacznik czasu zostaje "zablokowany" i staje się nieaktualny.

Ewentualne poprawki:

  • Commit często (głupie jeśli robisz tylko wybiera)
  • Konfigurowanie psycopg2 użyć innego sposobu do automatycznego tworzenia transakcji (chyba trudne, aby uzyskać prawo i będzie wpływać na inne części aplikacji)
  • użytkowania inną funkcję datownika, jak statement_timestamp() (nie SQL zgodny ze standardami, ale w inny sposób idealny dla tego scenariusza)

Od the manual, section 9.9.4, podkreślenie dodane:

PostgreSQL oferuje szereg funkcji, które zwracają wartości związanych do bieżącej daty i czasu. Te funkcje SQL standard wszystko wrócić wartości w oparciu o czas rozpoczęcia bieżącej transakcji :

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

CURRENT_TIME i CURRENT_TIMESTAMP dostarczania wartości z strefy czasowej; LOCALTIME i LOCALTIMESTAMP dostarczają wartości bez strefy czasowej.

CURRENT_TIME, CURRENT_TIMESTAMP, LOCALTIME i LOCALTIMESTAMP może ewentualnie mieć parametr dokładność , co powoduje, że wynik do zaokrągla się, że wiele ułamkowe cyfr w zakresie sekund. Bez parametru dokładności wynikiem jest podana do pełnej dostępnej dokładności.

...

Od funkcje te zwracają czas rozpoczęcia bieżącej transakcji, ich wartości nie zmieniają się w czasie transakcji . Jest to uważane za cecha: intencją jest umożliwienie pojedyncza transakcja mieć spójne pojęcie „bieżącej” czasie, tak, że wielokrotne modyfikacje w ramach tej samej transakcji ponoszą sam znacznik czasu.

Uwaga: Inne systemy baz danych mogą częściej przesuwać te wartości.

PostgreSQL udostępnia również funkcje które zwracają czas rozpoczęcia bieżącej instrukcji , a także rzeczywisty czas aktualny w chwili funkcja nazywa. Pełna lista funkcji czasu non-SQL-standard:

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

now() to tradycyjny PostgreSQL równoważne z CURRENT_TIMESTAMP. transaction_timestamp() to również równoważne CURRENT_TIMESTAMP, ale jest nazwany, aby wyraźnie odzwierciedlać to, co zwraca . statement_timestamp() zwraca czas rozpoczęcia bieżącego oświadczenia (dokładniej, czas otrzymania najnowszego polecenia od klienta). statement_timestamp() i transaction_timestamp() zwraca podczas pierwszej komendy tę samą wartość, ale może się różnić podczas kolejnych poleceń. clock_timestamp() zwraca aktualny czas bieżący , a zatem jego wartość zmienia się nawet w ramach pojedynczego polecenia SQL . timeofday() to historyczna funkcja PostgreSQL. Podobnie jak clock_timestamp(), zwraca bieżący czas w postaci , ale w postaci ciągu tekstowego w formacie zamiast znacznika czasu z wartością strefy czasowej.

+0

Dziękuję za wyjaśnienie tego. Nie testowałem jeszcze twoich sugestii, ale przyjąłem twoją odpowiedź za wykonanie zadania wyjaśnienia, dlaczego znacznik czasu byłby niepoprawny. Jednak teraz zastanawiam się, czy istnieje sposób, w jaki mogę utworzyć kursor bez rozpoczynania transakcji. – adam

+0

Możesz ustawić Psycopg2 na poziomie izolacji transakcji 'ISOLATION_LEVEL_AUTOCOMMIT', który nie rozpocznie transakcji po wydaniu komend. Nie wiem, jak dalece byłyby to zmiany; może to spowodować złamanie innych zapytań, które wykorzystują transakcje. – kquinn

3

Spróbuj wywołanie c.close(), jak to opisano w dokumentacji 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

Właśnie wprowadziłem te zmiany i zrestartowałem serwer cherrypy i nadal mam ten sam problem. – adam

+0

Aktualizacja: przenieśliśmy się z postgres 8.0 do 8.3 i ta odpowiedź działa teraz również. – adam

0

I dodaje się zaangażować się w sposób, który wybiera się znaczniki czasu i został rozwiązany.

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 

Czy ktoś może wyjaśnić, dlaczego musiałbym wywołać cursor.connection.commit(), gdy wszystko, co robię, jest select?

Powiązane problemy