Source code for relstorage.adapters.mysql.oidallocator
# -*- coding: utf-8 -*-################################################################################ Copyright (c) 2009 Zope Foundation and Contributors.# All Rights Reserved.## This software is subject to the provisions of the Zope Public License,# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS# FOR A PARTICULAR PURPOSE.###############################################################################"""IOIDAllocator implementations."""from__future__importabsolute_importfromzope.interfaceimportimplementerfrom..interfacesimportIOIDAllocatorfrom..oidallocatorimportAbstractTableOIDAllocatorfrom.lockerimportlock_timeout
[docs]@implementer(IOIDAllocator)classMySQLOIDAllocator(AbstractTableOIDAllocator):# https://dev.mysql.com/doc/refman/5.7/en/example-auto-increment.html# "Updating an existing AUTO_INCREMENT column value in an InnoDB# table does not reset the AUTO_INCREMENT sequence as it does for# MyISAM and NDB tables."## Thus we must do a pure INSERT, not a REPLACE or UPDATE..## Alternatively, ALTER TABLE ... AUTO_INCREMENT = X;# But: That behaves different by version (persistent on 8, non-persistent# on 5.7; that's probably ok.) However, it causes an auto-commit, which# is certainly not desired as this is used by a store connection.## https://dev.mysql.com/doc/refman/5.7/en/innodb-auto-increment-handling.html#innodb-auto-increment-lock-modes# In InnoDB, it must be indexed as a leading column.## "In all lock modes (0, 1, and 2), if a transaction that# generated auto-increment values rolls back, those auto-increment# values are 'lost'."## "If you delete the row containing the maximum value for an# AUTO_INCREMENT column, the value is not reused for a MyISAM or# InnoDB table. If you delete all rows in the table with DELETE# FROM tbl_name (without a WHERE clause) in autocommit mode, the# sequence starts over for all storage engines except InnoDB and# MyISAM."## Regarding `TRUNCATE TABLE`:## "Any AUTO_INCREMENT value is reset to its start value. This is# true even for MyISAM and InnoDB, which normally do not reuse# sequence values."def_set_min_oid_from_range(self,cursor,n):# A simple statement like the following can easily deadlock:## INSERT INTO new_oid (zoid)# SELECT %s# WHERE %s > (SELECT COALESCE(MAX(zoid), 0) FROM new_oid)### Session A: new_oid() -> Lock 1# Session B: new_oid() -> Lock 2 (row 1 is invisible)# Session A: new_oid() -> Lock 3 (row 2 is invisible)# Session B: set_min_oid(2) -> Hang waiting for lock# Session A: new_oid() -> Lock 4: Deadlock, Session B rolls back.## Partly this is because MAX() is local to the current session.# We deal with this by using a stored procedure to efficiently make# multiple queries.self.driver.callproc_no_result(cursor,'set_min_oid(%s)',(n,))# Notes on new_oids:# Generate a new auto_incremented ID. This will never conflict# with any other session because generated IDs are guaranteed# to be unique. However, the DELETE statement may interfere# with other sessions and lead to deadlock; this is true even# when using the 'DELETE IGNORE'. The INSERT statement takes out an# exclusive lock on the PRIMARY KEY index.## Session A: INSERTS 2000. -> lock 2000# Session B: INSERTS 3000. -> lock 3000# Session B: DELETE < 3000 -> wants lock on 2000; locks everything under.# Session A: DELETE < 2000 -> hold lock on 2000; wants to lock things under.## ORDER BY zoid (ASC or DESC) just runs into more lock issues reported for# individual rows.## Using the single-row update example provided by# https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html# also runs into deadlocks (naturally).## Our solution is to just let rows build up if the delete fails. Eventually# a GC, which happens at startup, will occur and hopefully get most of them.# cursor.lastrowid is used in the superclass; that's a DB-API# extension. Fortunately, all supported drivers implement it. (In# the past we used cursor.connection.insert_id(), which was# specific to MySQLdb and PyMySQL.) 'SELECT LAST_INSERT_ID()' is# the pure-SQL way to do this.def_timeout(self,cursor,batch_timeout):returnlock_timeout(cursor,batch_timeout)