FastAPI や Flask などで SQLite3 を利用するとき、非同期処理などを行う際に、ほぼ同じ時刻に複数のプロセスが実行され SQLite3 からデータをとる(SELECT
)ことが頻繁に発生する。この際に Python プログラムから "Recursive use of cursors not allowed" のようなエラーが出力され、プロセスが中断される。
File "/Users/username/.pyenv/versions/3.11/envs/microapp/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 867, in run
result = context.run(func, *args)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/username/Library/CloudStorage/OneDrive-Personal/projects/AppLab/app-jcddb/app.py", line 137, in get_stats
db.execute(sql)
File "/Users/username/Library/CloudStorage/OneDrive-Personal/projects/AppLab/app-jcddb/app.py", line 39, in execute
self.cur.execute(sql)
sqlite3.ProgrammingError: Recursive use of cursors not allowed.
SQLite3 がマルチスレッドまたはプロセスに対応していないために、ほぼ同じ時刻に複数のアクセスが入ると、処理が追いつかなくなります。
このエラーを回避する対策として、複数の処理を一つずつ実行するように制御すればよい。例えば、クエリーが実行されたときに、フラグを確認して True であれば、それを False に変更してクエリーを実行します。実行後にそのフラグをまた True に戻す。
このとき、もし少しだけ遅れた処理が入ってきたとき、その時点でフラグが False であるならば、そのクエリーを実行せずに、0.1 秒を待ってから、再度フラグの値を確認して実行をトライするように制御する。
プログラムでは例えば次のように実装できる。
class DB():
def __init__(self, host):
self.host = host
self.con = sqlite3.connect(host,
check_same_thread=False,
uri=True,
isolation_level='DEFERRED')
self.cur = self.con.cursor()
self.cur.execute('PRAGMA foreign_keys = ON')
self.is_unlock = True
def select(self, sql):
while True:
if self.is_unlock:
self.is_unlock = False
self.cur.execute(sql)
output = self.cur.fetchall()
self.is_unlock = True
return output
time.sleep(0.1)
あとはこれを呼び出して実行すれば、ほとんどの場合、Recursive use of cursors not allowed. というエラーがでなくなる。これでも出る場合は、time.sleep
の値を調整するとよいかもしれない。
db = DB('path/to/db.sqlite3')
x = db.select('SELECT * FROM locations')
print(x)