MySQLdb.cursor.execute can't run multiple quer

2019-01-26 06:49发布

We're trying to run SQL files containing multiple insert statements as a single query, but it seems rollback fails when any of the statements contain an error.

MySQLd configuration:

sql_mode = STRICT_ALL_TABLES
default-storage-engine = innodb

Python code:

from contextlib import closing
import MySQLdb
database_connection = MySQLdb.connect(host="127.0.0.1", user="root")
with closing(database_connection.cursor()) as cursor:
    database_connection.begin()
    cursor.execute('DROP DATABASE IF EXISTS db_name')
    cursor.execute('CREATE DATABASE db_name')
    cursor.execute('USE db_name')
    cursor.execute('CREATE TABLE table_name(first_field INTEGER)')
with closing(database_connection.cursor()) as cursor:
    try:
        database_connection.begin()
        cursor.execute('USE db_name')
        cursor.execute('INSERT INTO table_name VALUES (1)')
        cursor.execute('INSERT INTO table_name VALUES ("non-integer value")')
        database_connection.commit()
    except Exception as error:
        print("Exception thrown: {0}".format(error))
        database_connection.rollback()
        print("Rolled back")
with closing(database_connection.cursor()) as cursor:
    try:
        database_connection.begin()
        cursor.execute('USE db_name')
        cursor.execute('INSERT INTO table_name VALUES (1); INSERT INTO table_name VALUES ("non-integer value")')
        database_connection.commit()
    except:
        print("Exception thrown: {0}".format(error))
        database_connection.rollback()
        print("Rolled back")

Expected result: "Exception thrown" and "Rolled back" printed twice.

Actual result with MySQL-python 1.2.4:

Exception thrown: (1366, "Incorrect integer value: 'non-integer value' for column 'first_field' at row 1")
Rolled back
Exception thrown: (1366, "Incorrect integer value: 'non-integer value' for column 'first_field' at row 1")
Traceback (most recent call last):
  File "test.py", line 30, in <module>
    print("Rolled back")
  File ".../python-2.7/lib/python2.7/contextlib.py", line 154, in __exit__
    self.thing.close()
  File ".../virtualenv-python-2.7/lib/python2.7/site-packages/MySQLdb/cursors.py", line 100, in close
    while self.nextset(): pass
  File ".../virtualenv-python-2.7/lib/python2.7/site-packages/MySQLdb/cursors.py", line 132, in nextset
    nr = db.next_result()
_mysql_exceptions.OperationalError: (1366, "Incorrect integer value: 'non-integer value' for column 'first_field' at row 1")

What gives? Do we really have to parse the SQL to split up statements (with all the escape and quote handling that entails) to run them in multiple executes?

6条回答
Juvenile、少年°
2楼-- · 2019-01-26 07:06

I think you need to pass multi=True to execute when using multiple statements, see http://dev.mysql.com/doc/connector-python/en/connector-python-api-mysqlcursor-execute.html

Update: This applies to the mysql.connector module, not MySQLdb used in this case.

查看更多
萌系小妹纸
3楼-- · 2019-01-26 07:08

use below line item to execute statement :

for _ in cursor.execute(query, multi=True): pass

查看更多
趁早两清
4楼-- · 2019-01-26 07:16

Tried the multi=True method, but ended up splitting the file by semi and looping through. Obviously not going to work if you have escaped semis, but seemed like the best method for me.

with connection.cursor() as cursor:
    for statement in script.split(';'):
        if len(statement) > 0:
             cursor.execute(statement + ';')
查看更多
Summer. ? 凉城
5楼-- · 2019-01-26 07:18

Apparently there is no way to do this in MySQLdb (aka. MySQL-python), so we ended up just communicateing the data to subprocess.Popen([mysql, ...], stdin=subprocess.PIPE) and checking the returncode.

查看更多
爷的心禁止访问
6楼-- · 2019-01-26 07:21

Using the mysql program via Popen will definitely work, but if you want to just use an existing connection (and cursor), the sqlparse package has a split function that will split into statements. I'm not sure what the compatiblity is like, but I have a script that does:

with open('file.sql', 'rb') as f:
    for statement in sqlparse.split(f.read()):
        if not statement:
            continue
        cur.execute(statement)

It's only ever fed DROP TABLE and CREATE TABLE statements, but works for me.

https://pypi.python.org/pypi/sqlparse

查看更多
冷血范
7楼-- · 2019-01-26 07:25

Like all Python DB-API 2.0 implementations, the cursor.execute() method is designed take only one statement, because it makes guarantees about the state of the cursor afterward.

Use the cursor.executemany() method instead. Do note that, as per the DB-API 2.0 specification:

Use of this method for an operation which produces one or more result sets constitutes undefined behavior, and the implementation is permitted (but not required) to raise an exception when it detects that a result set has been created by an invocation of the operation.

Using this for multiple INSERT statements should be just fine:

cursor.executemany('INSERT INTO table_name VALUES (%s)',
    [(1,), ("non-integer value",)]
)

If you need to execute a series of disparate statements like from a script, then for most cases you can just split the statements on ; and feed each statement to cursor.execute() separately.

查看更多
登录 后发表回答