Running MySQL queries in Python
Being able to access MySQL databases in Python opens up quite a wide range of possibilities. It’s essential for a number of tasks such as connecting disparate datasources, performing maintenance, running regular updates, and more. While there are a number of helpful tutorials out there on how to connect to a MySQL database, they really just covers the basics and often fall short of describing a complete and good implementation, one you would want to use in production. That said, if you haven’t used the MySQLdb module before, I’d recommend skimming some of the tutorials out there (this one is decent), to get some initial familiarity with the process.
What follows is the setup I have settled on after a good bit of trial and error. It involves a little more effort than the minimum needed, but it’s a lot safer and more informative. It makes use of the logging module, whose setup I will assume. For more information on it, check out my article on simple logging in Python. You’ll wonder how you lived without it.
After developing with it for a while, I’ve found it’s easy to forget that the MySQLdb module isn’t included in the standard library. You need to verify it’s installed in the scripts you use it in. The following should suffice (assuming you aren’t using a package that sets up your dependencies):
1 2 3 4 5 6 7 8 | import sys # For MySQL DB access: try: import MySQLdb except ImportError: print "You need to install the MySQLdb module. Check", print "http://sourceforge.net/projects/mysql-python for details." sys.exit(1) |
This lets you know where to go to find the module and exits with error.
Now you have access to the MySQLdb module. But what’s the best way to access its functions? I have found it easiest to create a function that accepts a dictionary of the needed connection information as well as the query to run. It returns the data found, handling and logging errors. After this is in place, you only need to define your DB parameter dictionary (or load it from a config file), and then wrap the call to the query running function with a minimum of error handling. Here’s the function to perform the queries:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | def run_mysql(params): """Run MySQL query passed using passed connection information. """ # If you want to see params for each connection attempt, # uncomment the following line: #logger.debug("Trying to connect with: %s" % params) try: db = MySQLdb.connect(user=params['user'], passwd=params['password'], \ db=params['database'], unix_socket=params['socket'], \ host=params['host'], port=params['port'], use_unicode=True) except MySQLdb.Error, e: logger.critical("Error in connecting %d: %s" % (e.args[0], e.args[1])) sys.exit(1) cursor = db.cursor() try: cursor.execute(params['query']) data = cursor.fetchall() # If you want to see how many rows were returned: # rows_returned = cursor.rowcount except MySQLdb.Error, e: raise e cursor.close() db.close() cursor.close() db.close() return data |
This would be called by:
1 2 3 4 5 6 7 8 | db_params = {'user':'root', 'password':'$3kr3t', 'database':'test', 'socket':'/var/run/mysqld/mysqld.sock', 'host':'localhost', 'port':3306, 'query':'show tables;'} data = run_mysql(db_params) if data: print "Results:", data else: print "No data returned." |
This makes it simple to run any query against any database setup using a single function. The parameters included in db_params should cover most any Linux MySQL setup. One exception is socket, as this may differ between distributions. If you aren’t sure, just run “ps aux | grep mysql” and the process shown (assuming it’s running) should include a “–socket” option specifying where the socket file is. For servers on Windows, remove socket in favor of named pipes or shared memory. Or just use the TCP/IP default via port.
If there is an error trying to connect to the server, it is logged and the script exits. The log will include the numeric MySQL error code as well as a more or less helpful error message. Check out this page for more info on MySQL error codes and message. This approach seems to work out well, since an issue in connecting means that there is likely either a problem with the database server, or our connection information is wrong. In either case, we will probably want to stop and re-write something.
If the connection is successful, it next attempts to run the query passed. If there is a problem, it raises the error to the calling function, then cleans up the database call and connection. Finally, the data are returned.
This function allows for fast access while being fairly simple and easy to follow. It has served me well. Here is a more complete example of its use:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | def retrieve_table_names(db_params): """Prints out all table names, using the database parameters passed. """ logger.info("Looking for tables in %s..." % db_params['database']) db_params['query'] = """show tables;""" try: data = run_mysql(db_params) except MySQLdb.Error, e: logger.error("Error in finding tables in %s: %s" % \ (db_params['database'], e)) sys.exit(1) if data: print "Tables found:" for x in data: print x[0] else: logger.error("No tables found.") |
A word of caution: One thing that might be surprising is that queries resulting in warnings will error out. MySQL warnings aren’t always that bad. They might just be notes, which are informative and not critical in nature. I suggest writing queries that don’t generate actual warnings, and turn off note displays by turning setting the sql_notes session variable off (they really just don’t seem to be often useful).
March 20, 2009 - 11:59 PM Comments (4)







