一、搭建NFS实验环境:
以centos8系统为例:
Linux 4.19.90-23.8.v2101.ky10.x86_64 #1 SMP Mon May 17 17:08:34 CST 2021 x86_64 x86_64 x86_64 GNU/Linux
nfs服务端:
bash
yum install -y nfs-utils rpcbind
mkdir -p /usr/share/nfs/share
echo "/usr/share/nfs/share 10.10.10.123/24(rw,sync,no_root_squash)" >> /etc/exports
exportfs -a
systemctl enable rpcbind nfs-server
systemctl start rpcbind nfs-server
exportfs -v
firewall-cmd --permanent --zone=public --add-service=nfs --add-service=mountd --add-service=rpc-bind --add-service=nscd --add-service=nfs-lock --add-service=nfs-idmap
firewall-cmd --reload
nfs客户端:
bash
yum install -y nfs-utils
mkdir -p /mnt/nfs_share
mount -t nfs 10.10.10.123:/usr/share/nfs/share /mnt/nfs_share
二、基于NFS压测SQLite3的WAL模式:
压测脚本 wal_performance_test.py:
python
import time
import sqlite3
import subprocess
import re
from datetime import datetime
def wal_performance_test():
# WAL模式
dev, ip = get_default_interface_ip()
start = time.perf_counter()
for i in range(100):
try:
conn = sqlite3.connect('/mnt/nfs_share/wal_test.db')
# 设置WAL模式必须将journal_mode设为WAL
conn.execute("PRAGMA journal_mode=WAL;")
# 调整同步策略为NORMAL提升性能
conn.execute("PRAGMA synchronous=NORMAL;")
# 设置WAL文件自动清理的检查点阈值
conn.execute("PRAGMA wal_autocheckpoint=1000;")
conn.execute('CREATE TABLE IF NOT EXISTS data (id INT, host VARCHAR(15))')
conn.execute("BEGIN TRANSACTION")
for ii in range(100):
conn.execute("INSERT INTO data VALUES (?,?)", (i*100+ii,ip,))
conn.commit()
conn.close()
time.sleep(1)
except Exception as e:
now = datetime.now()
print(now, e)
time.sleep(1)
wal_time = time.perf_counter() - start
print(f"WAL模式: {wal_time:.6f}s")
def sqlite_performance_test():
# 常规模式
dev, ip = get_default_interface_ip()
start = time.perf_counter()
for i in range(100):
try:
conn = sqlite3.connect('/mnt/nfs_share/wal_test.db')
conn.execute('CREATE TABLE IF NOT EXISTS data (id INT, host VARCHAR(15))')
conn.execute("BEGIN TRANSACTION")
for ii in range(100):
conn.execute("INSERT INTO data VALUES (?,?)", (i*100+ii,ip,))
conn.commit()
conn.close()
time.sleep(1)
except Exception as e:
now = datetime.now()
print(now, e)
time.sleep(1)
normal_time = time.perf_counter() - start
print(f"常规模式: {normal_time:.6f}s")
def get_default_interface_ip():
"""通过路由表获取默认网卡和IP"""
try:
# 获取默认路由接口 - 使用 stdout=subprocess.PIPE
result = subprocess.run(
['ip', 'route', 'show', 'default'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
check=True
)
# 解析默认路由行
match = re.search(r'dev\s+(\w+)', result.stdout)
if match:
interface = match.group(1)
# 获取该接口的IP地址
ip_result = subprocess.run(
['ip', '-4', 'addr', 'show', interface],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
check=True
)
# 解析IP地址
ip_match = re.search(r'inet\s+(\d+\.\d+\.\d+\.\d+)', ip_result.stdout)
if ip_match:
return interface, ip_match.group(1)
except subprocess.CalledProcessError as e:
print(f"命令执行失败: {e}")
print(f"错误输出: {e.stderr}")
except Exception as e:
print(f"错误: {e}")
return None, None
wal_performance_test()
分别在客户机124和125执行:
bash
python3 wal_performance_test.py
两个客户端交替出现错误 disk I/O error,总计出现6条:
test@124 wal$ python3 wal_performance_test.py
2025-12-09 14:33:21.776389 disk I/O error
2025-12-09 14:33:47.146377 disk I/O error
2025-12-09 14:34:39.921037 disk I/O error
WAL模式: 105.810731s
test@125 wal$ python3 wal_performance_test.py
2025-12-09 14:33:20.745065 disk I/O error
2025-12-09 14:34:13.513216 disk I/O error
2025-12-09 14:34:38.881051 disk I/O error
WAL模式: 101.554718s
三、验证数据:
在123机器执行:
bash
yum install -y sqlite
sqlite3 wal_test.db
sqlite> select count(*) from data;
19400
sqlite> select count(*) from data where host='10.10.10.124';
9700
sqlite> select count(*) from data where host='10.10.10.125';
9700
总的执行数据量2*100*100=20000,减去6次失败 20000-600=19400,数据量正好。
也就是不会导致数据写乱,但是加锁失败会报错,需要客户端自己重试,如果不重试数据会丢失。
四、基于NFS压测SQLite的三种事务类型:
压测脚本 nfs_transaction_test.py:
python
import time
import sqlite3
import sys
from datetime import datetime
def nfs_deferred_transaction_test():
start = time.perf_counter()
for i in range(10000):
try:
conn = sqlite3.connect('/mnt/nfs_share/nfs_test.db')
conn.execute("BEGIN TRANSACTION")
row = conn.execute('SELECT * FROM data LIMIT 1').fetchone()
counter = row[0] + 1
conn.execute("UPDATE data SET counter=?", (counter,))
conn.commit()
conn.close()
time.sleep(0.01)
except Exception as e:
now = datetime.now()
print(now, e)
time.sleep(0.01)
cost_time = time.perf_counter() - start
print(f"deferred: {cost_time:.6f}s")
def nfs_immediate_transaction_test():
start = time.perf_counter()
for i in range(10000):
try:
conn = sqlite3.connect('/mnt/nfs_share/nfs_test.db')
conn.execute("BEGIN IMMEDIATE TRANSACTION")
row = conn.execute('SELECT * FROM data LIMIT 1').fetchone()
counter = row[0] + 1
conn.execute("UPDATE data SET counter=?", (counter,))
conn.commit()
conn.close()
time.sleep(0.01)
except Exception as e:
now = datetime.now()
print(now, e)
time.sleep(0.01)
cost_time = time.perf_counter() - start
print(f"immediate: {cost_time:.6f}s")
def nfs_exclusive_transaction_test():
start = time.perf_counter()
for i in range(10000):
try:
conn = sqlite3.connect('/mnt/nfs_share/nfs_test.db')
conn.execute("BEGIN EXCLUSIVE TRANSACTION")
row = conn.execute('SELECT * FROM data LIMIT 1').fetchone()
counter = row[0] + 1
conn.execute("UPDATE data SET counter=?", (counter,))
conn.commit()
conn.close()
time.sleep(0.01)
except Exception as e:
now = datetime.now()
print(now, e)
time.sleep(0.01)
cost_time = time.perf_counter() - start
print(f"exclusive: {cost_time:.6f}s")
def main():
conn = sqlite3.connect('/mnt/nfs_share/nfs_test.db')
conn.execute('CREATE TABLE IF NOT EXISTS data (counter INT)')
row = conn.execute('SELECT * FROM data LIMIT 1').fetchone()
if row is None:
conn.execute("INSERT INTO data (counter) VALUES (?)", (0,))
conn.commit()
conn.close()
command = sys.argv[1] if len(sys.argv)>1 else ""
if command == "" or command == "1":
nfs_deferred_transaction_test()
elif command == "2":
nfs_immediate_transaction_test()
elif command == "3":
nfs_exclusive_transaction_test()
else:
print("错误:参数不足或命令无效")
if __name__ == "__main__":
main()
1、deferred事务:
两台客户机都会出现大量的报错:
test@124 wal$ python3 nfs_transaction_test.py 1
。。。。。。
2025-12-10 01:06:16.459700 database is locked
2025-12-10 01:06:16.695814 database is locked
2025-12-10 01:06:16.777952 database is locked
deferred: 287.597685s
test@125 wal$ python3 nfs_transaction_test.py 1
。。。。。。
2025-12-10 01:06:16.591695 database is locked
2025-12-10 01:06:16.657538 database is locked
2025-12-10 01:06:16.848156 database is locked
deferred: 385.892006s
数据库中的计数也少了:
sqlite> select * from data;
12948
sqlite> select * from data;
Error: database is locked
sqlite> select * from data;
13018
sqlite> select * from data;
Error: database is locked
sqlite> select * from data;
13284
sqlite> select * from data;
15139
sqlite> select * from data;
15139
2、immediate事务:
两台客户机仅有一条报错产生:
test@124 wal$ python3 nfs_transaction_test.py 2
immediate: 347.385314s
test@125 wal$ python3 nfs_transaction_test.py 2
2025-12-10 00:42:48.581516 disk I/O error
immediate: 353.346707s
从123使用linux工具查询(deferred事务)会偶尔出现报错,但最终的数据是正确的。
root@localhost share# sqlite3 nfs_test.db
SQLite version 3.32.3 2020-06-18 14:00:33
Enter ".help" for usage hints.
sqlite> select * from data;
865
sqlite> select * from data;
Error: database is locked
sqlite> select * from data;
Error: database is locked
sqlite> select * from data;
1275
sqlite> select * from data;
Error: database is locked
sqlite> select * from data;
9723
sqlite> select * from data;
20000
sqlite>
3、exclusive事务:
两台客户机都没有报错:
test@124 wal$ python3 nfs_transaction_test.py 3
exclusive: 329.862279s
test@125 wal$ python3 nfs_transaction_test.py 3
exclusive: 342.372154s
从123使用linux工具查询(deferred事务)会偶尔出现报错,但最终的数据是正确的。
root@localhost share# sqlite3 nfs_test.db
SQLite version 3.32.3 2020-06-18 14:00:33
Enter ".help" for usage hints.
sqlite> select * from data;
Error: database is locked
sqlite> select * from data;
427
sqlite> select * from data;
Error: database is locked
sqlite> select * from data;
685
sqlite> select * from data;
20000
sqlite>
--end--