with tutorial retrieving has espaƱol data attribute python-2.7 sqlite3 python-db-api

python-2.7 - tutorial - sqlite3 python install



Transacciones con Python sqlite3 (7)

El .execute() normal de .execute() funciona como se espera con el cómodo modo de autocompromiso predeterminado y with conn: ... administrador de contexto with conn: ... haciendo autocompromiso O rollback, excepto para las transacciones de lectura, modificación y escritura protegidas , que se explican en el fin de esta respuesta.

El conn_or_cursor.executescript() no-estándar del módulo sqlite3 conn_or_cursor.executescript() no toma parte en el modo auto-commit (predeterminado) (y por lo tanto no funciona normalmente con el administrador de contexto with conn: ... ) pero reenvía el script en bruto. Por lo tanto, solo compromete las transacciones de confirmación automática potencialmente pendientes al inicio , antes de "ir en bruto".

Esto también significa que sin un "BEGIN" dentro de la secuencia de comandos executescript() funciona sin una transacción, y por lo tanto no hay opción de reversión por error o de lo contrario.

Entonces, con executescript() es mejor usar un BEGIN explícito (tal como lo hizo su script de creación de esquema inicial para la herramienta de línea de comandos sqlite en modo "en bruto"). Y esta interacción muestra paso a paso lo que está pasando:

>>> list(conn.execute(''SELECT * FROM test'')) [(99,)] >>> conn.executescript("BEGIN; UPDATE TEST SET i = 1; FNORD; COMMIT""") Traceback (most recent call last): File "<interactive input>", line 1, in <module> OperationalError: near "FNORD": syntax error >>> list(conn.execute(''SELECT * FROM test'')) [(1,)] >>> conn.rollback() >>> list(conn.execute(''SELECT * FROM test'')) [(99,)] >>>

El guión no llegó al "COMPROMISO". Y así podríamos ver el estado intermedio actual y decidir para deshacer (o cometer sin embargo)

Por lo tanto, un intento de try-except-rollback a través de excecutescript() ve así:

>>> list(conn.execute(''SELECT * FROM test'')) [(99,)] >>> try: conn.executescript("BEGIN; UPDATE TEST SET i = 1; FNORD; COMMIT""") ... except Exception as ev: ... print("Error in executescript (%s). Rolling back" % ev) ... conn.executescript(''ROLLBACK'') ... Error in executescript (near "FNORD": syntax error). Rolling back <sqlite3.Cursor object at 0x011F56E0> >>> list(conn.execute(''SELECT * FROM test'')) [(99,)] >>>

(Observe la reversión a través de la secuencia de comandos aquí, porque no .execute() asumió control de confirmación)

Y aquí una nota sobre el modo autocompromiso en combinación con la cuestión más difícil de una transacción protegida de lectura, modificación y escritura , que hizo que @Jeremie diga " De todas las muchas cosas escritas sobre transacciones en sqlite / python, esto es lo único que me permite hacer lo que quiero (tener un bloqueo de lectura exclusivo en la base de datos) "en un comentario sobre un ejemplo que incluía un c.execute("begin") . Aunque sqlite3 normalmente no realiza un bloqueo de lectura exclusivo de bloqueo prolongado, excepto por la duración de la recuperación de escritura real, pero más bloqueos de 5 etapas inteligentes para lograr la protección suficiente contra los cambios superpuestos.

El contexto with conn: auto-commit no pone ni activa un bloqueo lo suficientemente fuerte para la lectura-modificación-escritura protegida en el esquema de bloqueo de 5 etapas de sqlite3 . Dicho bloqueo se realiza implícitamente solo cuando se emite el primer comando de modificación de datos, por lo que es demasiado tarde. Solo un BEGIN (DEFERRED) (TRANSACTION) explícito BEGIN (DEFERRED) (TRANSACTION) desencadena el comportamiento deseado:

La primera operación de lectura en una base de datos crea un bloqueo COMPARTIDO y la primera operación de escritura crea un bloqueo RESERVADO.

De modo que una transacción protegida de lectura, modificación y escritura que utiliza el lenguaje de programación de manera general (y no una cláusula especial UPDATE de SQL atómico) se ve así:

with conn: conn.execute(''BEGIN TRANSACTION'') # crucial ! v = conn.execute(''SELECT * FROM test'').fetchone()[0] v = v + 1 time.sleep(3) # no read lock in effect, but only one concurrent modify succeeds conn.execute(''UPDATE test SET i=?'', (v,))

En caso de falla, dicha transacción de lectura-modificación-escritura podría reintentarse un par de veces.

Estoy tratando de portar algún código a Python que use bases de datos sqlite, y estoy tratando de hacer que las transacciones funcionen, y me estoy realmente confundiendo. Estoy realmente confundido por esto; He usado mucho sqlite en otros idiomas, porque es genial, pero simplemente no puedo descifrar qué está mal aquí.

Aquí está el esquema para mi base de datos de prueba (para alimentar la herramienta de línea de comandos sqlite3).

BEGIN TRANSACTION; CREATE TABLE test (i integer); INSERT INTO "test" VALUES(99); COMMIT;

Aquí hay un programa de prueba.

import sqlite3 sql = sqlite3.connect("test.db") with sql: c = sql.cursor() c.executescript(""" update test set i = 1; fnord; update test set i = 0; """)

Puede notar el error deliberado en ello. Esto hace que la secuencia de comandos SQL falle en la segunda línea, después de que se haya ejecutado la actualización.

De acuerdo con los documentos, se supone que la declaración with sql establece una transacción implícita alrededor de los contenidos, que solo se confirma si el bloque tiene éxito. Sin embargo, cuando lo ejecuto, obtengo el error esperado de SQL ... pero el valor de i se establece de 99 a 1. Espero que permanezca en 99, porque esa primera actualización debería revertirse.

Aquí hay otro programa de prueba, que llama explícitamente a commit() y rollback() .

import sqlite3 sql = sqlite3.connect("test.db") try: c = sql.cursor() c.executescript(""" update test set i = 1; fnord; update test set i = 0; """) sql.commit() except sql.Error: print("failed!") sql.rollback()

Esto se comporta exactamente de la misma manera --- cambio de 99 a 1.

Ahora estoy llamando BEGIN y COMMIT explícitamente:

import sqlite3 sql = sqlite3.connect("test.db") try: c = sql.cursor() c.execute("begin") c.executescript(""" update test set i = 1; fnord; update test set i = 0; """) c.execute("commit") except sql.Error: print("failed!") c.execute("rollback")

Esto también falla, pero de una manera diferente. Entiendo esto:

sqlite3.OperationalError: cannot rollback - no transaction is active

Sin embargo, si reemplazo las llamadas a c.execute() a c.executescript() , entonces funciona (¡me c.executescript() en 99)!

(También debo agregar que si pongo begin y commit dentro de la llamada interna a executescript entonces se comporta correctamente en todos los casos, pero desafortunadamente no puedo usar ese enfoque en mi aplicación. Además, cambiar sql.isolation_level parece hacer que no hay diferencia para el comportamiento)

¿Puede alguien explicarme qué está pasando aquí? Necesito entender esto; si no puedo confiar en las transacciones en la base de datos, no puedo hacer que mi aplicación funcione ...

Python 2.7, python-sqlite3 2.6.0, sqlite3 3.7.13, Debian.


Este es un hilo un poco viejo pero si ayuda, he encontrado que hacer una reversión en el objeto de conexión hace el truco.


Esto es lo que creo que está sucediendo según mi lectura de los enlaces sqlite3 de Python, así como de los documentos oficiales Sqlite3. La respuesta corta es que si quieres una transacción adecuada, debes apegarte a este modismo:

with connection: db.execute("BEGIN") # do other things, but do NOT use ''executescript''

Contrario a mi intuición, with connection no se llama BEGIN al ingresar el alcance. De hecho, no hace nada en __enter__ . Solo tiene un efecto cuando __exit__ el alcance, seleccionando COMMIT o ROLLBACK dependiendo de si el alcance está saliendo normalmente o con una excepción .

Por lo tanto, lo correcto es marcar siempre explícitamente el comienzo de sus transacciones mediante BEGIN . Esto hace que isolation_level sea irrelevante dentro de las transacciones, porque afortunadamente solo tiene un efecto cuando el modo autocommit está habilitado , y el modo autocommitir siempre se suprime dentro de los bloques de transacción .

Otra peculiaridad es executescript , que siempre emite un COMMIT antes de ejecutar el script . Esto puede arruinar fácilmente las transacciones, por lo que su elección es

  • utilice exactamente un executescript dentro de una transacción y nada más, o
  • evitar executescript completo; puede execute tantas veces como desee, sujeto a la limitación de una sentencia por execute .


Para cualquiera que quiera trabajar con la biblioteca sqlite3 independientemente de sus defectos, descubrí que puede mantener cierto control de las transacciones si hace estas dos cosas:

  1. establecer Connection.isolation_level = None (según los docs , esto significa el modo autocommit)
  2. evite usar executescript en absoluto, porque de acuerdo con los docs "emite primero una sentencia COMMIT", es decir, problemas. De hecho, me pareció que interfiere con cualquier transacción configurada manualmente

Entonces, la siguiente adaptación de tu prueba me funciona:

import sqlite3 sql = sqlite3.connect("/tmp/test.db") sql.isolation_level = None try: c = sql.cursor() c.execute("begin") c.execute("update test set i = 1") c.execute("fnord") c.execute("update test set i = 0") c.execute("commit") except sql.Error: print("failed!") c.execute("rollback")


Por los documentos ,

Los objetos de conexión se pueden usar como administradores de contexto que automáticamente confirman o retrotraen transacciones. En el caso de una excepción, la transacción se retrotrae; de lo contrario, la transacción se compromete:

Por lo tanto, si deja que Python salga del enunciado con cuando se produce una excepción, la transacción se retrotraerá.

import sqlite3 filename = ''/tmp/test.db'' with sqlite3.connect(filename) as conn: cursor = conn.cursor() sqls = [ ''DROP TABLE IF EXISTS test'', ''CREATE TABLE test (i integer)'', ''INSERT INTO "test" VALUES(99)'',] for sql in sqls: cursor.execute(sql) try: with sqlite3.connect(filename) as conn: cursor = conn.cursor() sqls = [ ''update test set i = 1'', ''fnord'', # <-- trigger error ''update test set i = 0'',] for sql in sqls: cursor.execute(sql) except sqlite3.OperationalError as err: print(err) # near "fnord": syntax error with sqlite3.connect(filename) as conn: cursor = conn.cursor() cursor.execute(''SELECT * FROM test'') for row in cursor: print(row) # (99,)

rendimientos

(99,)

como se esperaba.