Python and DB: Understanding DBI cursors

I investigated the cursor that appears when operating the DB with Python because it is too unknown. We investigated psycopg2 for PostgreSQL and MySQLdb for MySQL from the perspective of the difference between SQL CURSOR and Python cursor and how faithfully SQL CURSOR is implemented.

Question

Conclusion

The text is long, so from the conclusion.

  1. Python cursor has roughly three implementations.
  1. In psycopg2 for PostgreSQL, declaring a ** named cursor ** is server-side. It's client-side without a name.
  2. MySQLdb for MySQL is client side only.

Python cursor implementation

type DB DBI cursor declaration method
Server side PostgreSQL psycopg2 cursor('name')
Client side(With buffer) PostgreSQL psycopg2 cursor()
Client side(With buffer) MySQL MySQLdb cursor()
Client side(No buffer) MySQL MySQLdb connect( cursorclass = MySQLdb.cursors.SSCursor )As cursor()

Correspondence table of SQL CURSOR and Python cursor

The relationship between the server-side cursor ("SSC") and the Python method is as follows: Cannot be used with client-side cursors ("CSC") unless otherwise noted.

SQL Python Remarks
DECLARE CURSOR name cursor in psycopg2('name') Not standardized depending on driver implementation. One of the weaknesses of Python.
FETCH NEXT fetchone() Embroidery in CSC
FETCH FORWARD n fetchmany(n) Embroidery in CSC
FETCH ALL fetchall() Embroidery in CSC
FETCH PRIOR Not applicable scroll(-2, mode='relative'); fetchone()Can be replaced with
FETCH FIRST Not applicable scroll(-1, mode='absolute'); fetchone()Can be replaced with
FETCH LAST Not applicable scroll(-2, mode='absolute'); fetchone()Can be replaced with
MOVE mode value scroll(value, mode) In CSC, it becomes emulation, and you can only move forward. If you try to go back, you will get a NotSupportedError exception.
CURRENT OF Equivalent Cursor line reference

Investigation

I investigated SQL CURSOR and Python cursor respectively.

SQL CURSOR

The cursor can get the result of a query such as a SELECT statement line by line, or move to the previous or next line. You can also refer to the current row from other SQL statements. The details are omitted because the purpose is not to explain the grammar.

Cursor declaration

Declare the cursor like this.

DECLARE cursor name CURSOR FOR SELECT statement

Get row

To get the next line of the cursor, execute a statement similar to the following: The normal direction is NEXT or FORWARD to get the row while moving forward. It is also possible to return in the middle, in which case specify PRIOR or BACKWARD. It is also possible to move to the beginning FIRST or the end LAST at once.

FETCH direction FROM cursor name

Move cursor

Use MOVE if you just want to move without getting a row. The direction specification is the same as FETCH.

MOVE direction FROM cursor name

Cursor line reference

You can also refer to the cursor line from other statements. If you do this, you can easily perform other operations while using the result of SELECT. For example, to see a row where a cursor is currently fetched, use CURRENT OF as follows:

UPDATE names SET name='tomochi'WHERE CURRENT OF cursor name

Python cursor

cursor is created by calling the .cursor () method of the connection object. Transactions work on connection objects, so if you create multiple cursors from one connection object, they will be executed within one transaction.

cursor method (part)

Only the methods that are likely to be related to SQL CURSOR are covered.

Experiment

Experiment and see if the Python cursor declares and uses SQL CURSOR. The method is Enable statement logging in PostgreSQL and MySQL respectively, execute normal cursors and named cursors, and compare the actually issued SQL statements.

PostgreSQL + psycopg2 Reboot with log_statement ='all' in postgresql.conf. Create a PostgreSQL db appropriately and put the table and test data.

names.sql


CREATE TABLE names (
  name varchar(100)
);

INSERT INTO names VALUES ('kenji');
INSERT INTO names VALUES ('keigo');

Anonymous cursor

psql1.py


import psycopg2
conn = psycopg2.connect(database='kenji')
cu = conn.cursor()
cu.execute('SELECT * FROM names')
print cu.fetchone()
conn.close()

The result is as follows: There is no declaration of the cursor on the server side, and all the lines are fetched.

LOG:  statement: BEGIN
LOG:  statement: SELECT * FROM names

Named cursor

Next, try SELECT with the named cursor in the same way. The only difference from the above is the name given to cursor ().

psql2.py


import psycopg2
conn = psycopg2.connect(database='kenji')
cu = conn.cursor('foo')
cu.execute('SELECT * FROM names')
print cu.fetchone()
conn.close()

The result shows that the cursor is declared on the server side and FETCH is running.

LOG:  statement: BEGIN
LOG:  statement: DECLARE "foo" CURSOR WITHOUT HOLD FOR SELECT * FROM names
LOG:  statement: FETCH FORWARD 1 FROM "foo"

MySQL + MySQLdb Add the following line to the [mysqld] section in my.cnf and reboot.

general_log_file        = /var/log/mysql/mysql.log
general_log             = 1

Normal cursor

msql1.py


import MySQLdb
conn = MySQLdb.connect(db='kenji', user='kenji')
cu = conn.cursor()
cu.execute('SELECT * FROM names')
print cu.fetchone()
conn.close()

The result is, as expected, a simple cursorless SELECT.

Connect	kenji@localhost on kenji
Query	set autocommit=0
Query	SELECT * FROM names
Quit

SSCursor Next, let's experiment using the cursor class described as "server side cursor" in the MySQLdb manual. It is enabled by passing cursorclass = MySQLdb.cursors.SSCursor to connect ().

msql2.py


import MySQLdb
import MySQLdb.cursors
conn = MySQLdb.connect(db='kenji', user='kenji',
                       cursorclass = MySQLdb.cursors.SSCursor)
cu = conn.cursor()
cu.execute('SELECT * FROM names')
print cu.fetchone()
conn.close()

The result is the same as a normal cursor. No server-side cursor was declared.

Connect	kenji@localhost on kenji
Query	set autocommit=0
Query	SELECT * FROM names
Quit

When I read the source, it is a surprising implementation that ** blocks by not reading data from the socket **. Wouldn't it be possible to execute other statements if it was blocked at the connection level? There are two rows in the names table, but what happens if I get the first row and then SELECT it separately?

msql3.py


import MySQLdb
import MySQLdb.cursors
conn = MySQLdb.connect(db='kenji', user='kenji',
                       cursorclass = MySQLdb.cursors.SSCursor)
cu1 = conn.cursor()
cu2 = conn.cursor()
cu1.execute('SELECT name as name1 FROM names')
print "CU1", cu1.fetchone()
cu2.execute('SELECT name as name2 FROM names')
print "CU2", cu2.fetchone()
conn.close()

The execution was refused.

$ python msql3.py
CU1 ('kenji',)
Traceback (most recent call last):
  File "msql3.py", line 9, in <module>
    cu2.execute('SELECT name as name2 FROM names')
  File "/usr/lib/python2.7/dist-packages/MySQLdb/cursors.py", line 174, in execute
    self.errorhandler(self, exc, value)
  File "/usr/lib/python2.7/dist-packages/MySQLdb/connections.py", line 36, in defaulterrorhandler
    raise errorclass, errorvalue
_mysql_exceptions.ProgrammingError: (2014, "Commands out of sync; you can't run this command now")
Exception _mysql_exceptions.ProgrammingError: (2014, "Commands out of sync; you can't run this command now") in <bound method SSCursor.__del__ of <MySQLdb.cursors.SSCursor object at 0x7f6d9b542f50>> ignored
Exception _mysql_exceptions.ProgrammingError: (2014, "Commands out of sync; you can't run this command now") in <bound method SSCursor.__del__ of <MySQLdb.cursors.SSCursor object at 0x7f6d9b542e50>> ignored

"you can't run this command now". I don't fully understand the MySQL Connector (Client Library) Manual, but somehow the results It seems that the next statement cannot be executed in the same thread because it has not been completely read. It becomes a state where it can be read but not written from other threads. SSCursor is useless.

Server-side cursor reference

Use one server-side cursor and one regular cursor in PostgreSQL. Browse to the server-side cursor. A very simple example of updating'kenji'in the names table to'tomochi'.

psycopg2_named_cursor_example.py


import psycopg2

conn = psycopg2.connect(database='kenji')

cu1 = conn.cursor('foo')   #Server-side cursor 1
cu2 = conn.cursor()  #Normal cursor 2

#You don't have to lock the rows, but somehow.
cu1.execute("SELECT name FROM names WHERE name=%s FOR UPDATE;", ('kenji',))

print cu1.fetchone()

cu2.execute("UPDATE names SET name='tomochi' WHERE CURRENT OF foo;")   #UPDATE the current row of cursor 1
cu2.close()

conn.commit()
conn.close()

The trace log is as follows. The SQL is running as expected.

LOG:  statement: BEGIN
LOG:  statement: DECLARE "foo" CURSOR WITHOUT HOLD FOR SELECT name FROM names WHERE name='kenji' FOR UPDATE
LOG:  statement: FETCH FORWARD 1 FROM "foo"
LOG:  statement: UPDATE names SET name='tomochi' WHERE CURRENT OF foo
LOG:  statement: COMMIT

'kenji' in the names table has been updated to'tomochi'.

kenji=> select * from names;
  name
---------
 keigo
 tomochi
(2 rows)

Move cursor

I also tried scroll () experiment, but it was omitted because there was no difference from SQL that should be noted.

Recommended Posts

Python and DB: Understanding DBI cursors
Full understanding of Python threading and multiprocessing
Understanding Python Coroutine
Understanding python self
[Python] A rough understanding of iterators, iterators, and generators
Python and numpy tips
[Python] pip and wheel
Batch design and python
Python iterators and generators
Python packages and modules
Vue-Cli and Python integration
Try to operate DB with Python and visualize with d3
Ruby, Python and map
Understanding with mathematical formulas and Python LiNGAM (ICA version)
python input and output
Python and Ruby split
Python asynchronous processing ~ Full understanding of async and await ~
Get an abstract understanding of Python modules and packages
Python3, venv and Ansible
Python asyncio and ContextVar
[Introduction to cx_Oracle] (Part 6) DB and Python data type mapping
Problems and solutions when asked for MySQL db in Python 3
Programming with Python and Tkinter
Encryption and decryption with Python
Python: Class and instance variables
3-3, Python strings and character codes
Python 2 series and 3 series (Anaconda edition)
Python and hardware-Using RS232C with Python-
Python on Ruby and angry Ruby on Python
Python real division (/) and integer division (//)
Install Python and Flask (Windows 10)
About python objects and classes
About Python variables and objects
Apache mod_auth_tkt and Python AuthTkt
Å (Ongustromu) and NFC @ Python
Understand Python packages and modules
# 2 [python3] Separation and comment out
Python shallow copy and deep copy
Python and ruby slice memo
Access Oracle DB from Python
Python installation and basic grammar
I compared Java and Python!
Python shallow and deep copy
About Python, len () and randint ()
About Python datetime and timezone
Install Python 3.7 and Django 3.0 (CentOS)
Paiza Python Primer 8: Understanding Classes
Python environment construction and TensorFlow
Python class variables and instance variables
Ruby and Python syntax ~ branch ~
[Python] Python and security-① What is Python?
Stack and Queue in Python
python metaclass and sqlalchemy declareative
Fibonacci and prime implementations (python)
Python basics: conditions and iterations
Python bitwise operator and OR
Python debug and test module
Python list and tuples and commas
Python variables and object IDs
Python list comprehensions and generators
About Python and regular expressions