guest@blog.cmj.tw: ~/posts $

Isolation Level


好雷。不踩嗎

Isolation Level 在 DB 裡面是 ACID 中的 I,也就是 隔離性 ,主要解決在 併發(concurrency) 處理時,由於互相執行導致結果不一致的狀況。基本上分為四種等級:Read Uncommitted、Read Committed、 Repeatable Read、Serializable

下面是一個簡單的 Python publisher 程式碼,會定期增加一個 event:

#! /usr/bin/env python
# Copyright (C) 2017-2018 cmj<cmj@cmj.tw>. All right reserved.

import time
from datetime import datetime

from sqlalchemy import create_engine
from sqlalchemy import Column, Integer, DateTime
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy.ext.declarative import declarative_base

Base    = declarative_base()

class TestEvent(Base):
	__tablename__ = 'test_event_table'

	id		= Column(Integer, primary_key=True)
	created	= Column(DateTime, default=datetime.utcnow)

	def __repr__(self):
		return f'event #{self.id}'

if __name__ == '__main__':
	engine = 'mysql+pymysql://localhost/test?charset=utf8mb4'
	engine = create_engine(engine)
	Session = sessionmaker(autocommit=False, autoflush=False, bind=engine)
	session = scoped_session(Session)
	Base.metadata.create_all(engine)

	while True:
		event = TestEvent()
		session.add(event)
		session.commit()
		print(event)
		time.sleep(1)

Serializable

跟 Repeatable Read 類似,但是在 InnoDB 中會幫每個 SELECT 加上 FOR SHARE 語法。如果有使用 autocommit 則會每次 SELECT 都當作是一個 transaction。在尚未 commit 之前都會 blocking 直到 commit

:::python
engine = 'mysql+pymysql://localhost/test?charset=utf8mb4'
engine = create_engine(engine, isolation_level='SERIALIZABLE')

Repeatable Read

在 Mysql / InnoDB 中的預設選項:當第一次執行 SELECT 語法時會產生一個 snapshot,當在同一個 transaction 中再次執行時會被視為是跟第一次執行一樣的結果。

以下使用預設的 Isolation Level 撰寫的 subscriber:

:::python
#! /usr/bin/env python
# Copyright (C) 2017-2018 cmj<cmj@cmj.tw>. All right reserved.

import time
from datetime import datetime

from sqlalchemy import create_engine
from sqlalchemy import Column, Integer, DateTime
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy.ext.declarative import declarative_base

Base    = declarative_base()

class TestEvent(Base):
	__tablename__ = 'test_event_table'

	id		= Column(Integer, primary_key=True)
	created	= Column(DateTime, default=datetime.utcnow)

	def __repr__(self):
		return f'event #{self.id}'

if __name__ == '__main__':
	engine = 'mysql+pymysql://localhost/test?charset=utf8mb4'
	engine = create_engine(engine)
	Session = sessionmaker(autocommit=False, autoflush=False, bind=engine)
	session = scoped_session(Session)
	Base.metadata.create_all(engine)

	while True:
		event = session.query(TestEvent).order_by(TestEvent.id.desc()).first()
		print(event)
		time.sleep(1)

Read Committed

在 Read Committed 中依然在每次 transaction 使用 Write Lock,但是 Read Lock 會在每次 SELECT 之後釋放。透過這種方式,就可以拿到每次更新之後的結果:

:::python
engine = 'mysql+pymysql://localhost/test?charset=utf8mb4'
engine = create_engine(engine, isolation_level='READ COMMITTED')

Read Uncommitted

這是最低階的 Isolation Level:在這個層級中 髒讀 (Dirty Read) 是被允許的,也就是讀取尚未被 commit 的修改。

:::python
engine = 'mysql+pymysql://localhost/test?charset=utf8mb4'
engine = create_engine(engine, isolation_level='READ UNCOMMITTED')

)