Bendi新闻
>
python操作MySQL和实战

python操作MySQL和实战

7月前

1. 事务

innodb引擎中支持事务,myisam不支持。

CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` varchar(32) DEFAULT NULL,
`amount` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

例如:李杰 给 武沛齐 转账 100,那就会涉及2个步骤。

  • 李杰账户 减100

  • 武沛齐账户 加 100

这两个步骤必须同时完成才算完成,并且如果第一个完成、第二步失败,还是回滚到初始状态。

事务,就是来解决这种情况的。 大白话:要成功都成功;要失败都失败。

事务的具有四大特性(ACID):

  • 原子性(Atomicity)

    原子性是指事务包含的所有操作不可分割,要么全部成功,要么全部失败回滚。
  • 一致性(Consistency)

    执行的前后数据的完整性保持一致。
  • 隔离性(Isolation)

    一个事务执行的过程中,不应该受到其他事务的干扰。
  • 持久性(Durability)

    事务一旦结束,数据就持久到数据库

1.1 MySQL客户端

mysql> select * from users;
+----+---------+---------+
| id | name | amount |
+----+---------+---------+
| 1 | wupeiqi | 5 |
| 2 | alex | 6 |
+----+---------+---------+
3 rows in set (0.00 sec)

mysql> begin; -- 开启事务 start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> update users set amount=amount-2 where id=1; -- 执行操作
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

mysql> update users set amount=amount+2 where id=2; -- 执行操作
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

mysql> commit; -- 提交事务 rollback;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from users;
+----+---------+---------+
| id | name | amount |
+----+---------+---------+
| 1 | wupeiqi | 3 |
| 2 | ale x | 8 |
+----+---------+---------+
3 rows in set (0.00 sec)
mysql> select * from users;
+----+---------+---------+
| id | name | amount |
+----+---------+---------+
| 1 | wupeiqi | 3 |
| 2 | ale x | 8 |
+----+---------+---------+
3 rows in set (0.00 sec)

mysql> begin; -- 开启事务
Query OK, 0 rows affected (0.00 sec)

mysql> update users set amount=amount-2 where id=1; -- 执行操作(此时数据库中的值已修改)
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

mysql> rollback; -- 事务回滚(回到原来的状态)
Query OK, 0 rows affected (0.00 sec)

mysql> select * from users;
+----+---------+---------+
| id | name | amount |
+----+---------+---------+
| 1 | wupeiqi | 3 |
| 2 | ale x | 8 |
+----+---------+---------+
3 rows in set (0.00 sec)

1.2 Python代码

import pymysql

conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='root123', charset="utf8", db='userdb')
cursor = conn.cursor()

# 开启事务
conn.begin()

try:
cursor.execute("update users set amount=1 where id=1")
int('asdf')
cursor.execute("update tran set amount=2 where id=2")
except Exception as e:
# 回滚
print("回滚")
conn.rollback()
else:
# 提交
print("提交")
conn.commit()

cursor.close()
conn.close()

2. 锁

在用MySQL时,不知你是否会疑问:同时有很多做更新、插入、删除动作,MySQL如何保证数据不出错呢?

MySQL中自带了锁的功能,可以帮助我们实现开发过程中遇到的同时处理数据的情况。对于数据库中的锁,从锁的范围来讲有:

  • 表级锁,即A操作表时,其他人对整个表都不能操作,等待A操作完之后,才能继续。

  • 行级锁,即A操作表时,其他人对指定的行数据不能操作,其他行可以操作,等待A操作完之后,才能继续。

MYISAM支持表锁,不支持行锁;
InnoDB引擎支持行锁和表锁。

即:在MYISAM下如果要加锁,无论怎么加都会是表锁。
在InnoDB引擎支持下如果是基于索引查询的数据则是行级锁,否则就是表锁。

所以,一般情况下我们会选择使用innodb引擎,并且在 搜索 时也会使用索引(命中索引)。

接下来的操作就基于innodb引擎来操作:

CREATE TABLE `L1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

在innodb引擎中,update、insert、delete的行为内部都会先申请锁(排它锁),申请到之后才执行相关操作,最后再释放锁。

所以,当多个人同时像数据库执行:insertupdatedelete等操作时,内部加锁后会排队逐一执行。

而select则默认不会申请锁。

select * from xxx;

如果,你想要让select去申请锁,则需要配合 事务 + 特殊语法来实现。

  • for update,排它锁,加锁之后,其他不可以读写。

    begin; 
    select * from L1 where name="武沛齐" for update; -- name列不是索引(表锁)
    commit;
    begin; -- 或者 start transaction;
    select * from L1 where id=1 for update; -- id列是索引(行锁)
    commit;
  • lock in share mode ,共享锁,加锁之后,其他可读但不可写。

    begin; 
    select * from L1 where name="武沛齐" lock in share mode; -- 假设name列不是索引(表锁)
    commit;
    begin; -- 或者 start transaction;
    select * from L1 where id=1 lock in share mode; -- id列是索引(行锁)
    commit;

2.1 排它锁

排它锁( for update),加锁之后,其他事务不可以读写。

应用场景:总共100件商品,每次购买一件需要让商品个数减1 。

A: 访问页面查看商品剩余 100
B: 访问页面查看商品剩余 100

此时 A、B 同时下单,那么他们同时执行SQL
update goods set count=count-1 where id=3
由于Innodb引擎内部会加锁,所以他们两个即使同一时刻执行,内部也会排序逐步执行。


但是,当商品剩余 1个时,就需要注意了。
A: 访问页面查看商品剩余 1
B: 访问页面查看商品剩余 1

此时 A、B 同时下单,那么他们同时执行SQL
update goods set count=count-1 where id=3
这样剩余数量就会出现 -1,很显然这是不正确的,所以应该怎么办呢?


这种情况下,可以利用 排它锁,在更新之前先查询剩余数量,只有数量 >0 才可以购买,所以,下单时应该执行:
begin; -- start transaction;
select count from goods where id=3 for update;
-- 获取个数进行判断
if 个数>0:
update goods set count=count-1 where id=3;
else:
-- 已售罄
commit;

基于Python代码示例:

import pymysql
import threading


def task():
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='root123', charset="utf8", db='userdb')
cursor = conn.cursor(pymysql.cursors.DictCursor)
# cursor = conn.cursor()

# 开启事务
conn.begin()

cursor.execute("select id,age from tran where id=2 for update")
# fetchall ( {"id":1,"age":10},{"id":2,"age":10}, ) ((1,10),(2,10))
# {"id":1,"age":10} (1,10)
result = cursor.fetchone()
current_age = result['age']

if current_age > 0:
cursor.execute("update tran set age=age-1 where id=2")
else:
print("已售罄")

conn.commit()

cursor.close()
conn.close()


def run():
for i in range(5):
t = threading.Thread(target=task)
t.start()


if __name__ == '__main__':
run()

2.2 共享锁

共享锁( lock in share mode),可以读,但不允许写。

加锁之后,后续其他事物可以可以进行读,但不允许写(update、delete、insert),因为写的默认也会加锁。

Locking Read Examples

Suppose that you want to insert a new row into a table child, and make sure that the child row has a parent row in table parent. Your application code can ensure referential integrity throughout this sequence of operations.

First, use a consistent read to query the table PARENT and verify that the parent row exists. Can you safely insert the child row to table CHILD? No, because some other session could delete the parent row in the moment between your SELECT and your INSERT, without you being aware of it.

To avoid this potential issue, perform the SELECT using LOCK IN SHARE MODE:

SELECT * FROM parent WHERE NAME = 'Jones' LOCK IN SHARE MODE;

After the LOCK IN SHARE MODE query returns the parent 'Jones', you can safely add the child record to the CHILD table and commit the transaction. Any transaction that tries to acquire an exclusive lock in the applicable row in the PARENT table waits until you are finished, that is, until the data in all tables is in a consistent state.

3. 数据库连接池

在操作数据库时需要使用数据库连接池。

pip3.9 install pymysql
pip3.9 install dbutils
import threading
import pymysql
from dbutils.pooled_db import PooledDB

MYSQL_DB_POOL = PooledDB(
creator=pymysql, # 使用链接数据库的模块
maxconnections=5, # 连接池允许的最大连接数,0和None表示不限制连接数
mincached=2, # 初始化时,链接池中至少创建的空闲的链接,0表示不创建
maxcached=3, # 链接池中最多闲置的链接,0和None不限制
blocking=True, # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
ping=0,
# ping MySQL服务端,检查是否服务可用。
# 如:0 = None = never, 1 = default = whenever it is requested,
# 2 = when a cursor is created, 4 = when a query is executed, 7 = always
host='127.0.0.1',
port=3306,
user='root',
password='root123',
database='userdb',
charset='utf8'
)


def task():
# 去连接池获取一个连接
conn = MYSQL_DB_POOL.connection()
cursor = conn.cursor(pymysql.cursors.DictCursor)

cursor.execute('select sleep(2)')
result = cursor.fetchall()
print(result)

cursor.close()
# 将连接交换给连接池
conn.close()

def run():
for i in range(10):
t = threading.Thread(target=task)
t.start()


if __name__ == '__main__':
run()

4. SQL工具类

基于数据库连接池开发一个公共的SQL操作类,方便以后操作数据库。

4.1 单例和方法

# db.py
import pymysql
from dbutils.pooled_db import PooledDB


class DBHelper(object):

def __init__(self):
# TODO 此处配置,可以去配置文件中读取。
self.pool = PooledDB(
creator=pymysql, # 使用链接数据库的模块
maxconnections=5, # 连接池允许的最大连接数,0和None表示不限制连接数
mincached=2, # 初始化时,链接池中至少创建的空闲的链接,0表示不创建
maxcached=3, # 链接池中最多闲置的链接,0和None不限制
blocking=True, # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
ping=0,
# ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always
host='127.0.0.1',
port=3306,
user='root',
password='root123',
database='userdb',
charset='utf8'
)

def get_conn_cursor(self):
conn = self.pool.connection()
cursor = conn.cursor(pymysql.cursors.DictCursor)
return conn, cursor

def close_conn_cursor(self, *args):
for item in args:
item.close()

def exec(self, sql, **kwargs):
conn, cursor = self.get_conn_cursor()

cursor.execute(sql, kwargs)
conn.commit()

self.close_conn_cursor(conn, cursor)

def fetch_one(self, sql, **kwargs):
conn, cursor = self.get_conn_cursor()

cursor.execute(sql, kwargs)
result = cursor.fetchone()

self.close_conn_cursor(conn, cursor)
return result

def fetch_all(self, sql, **kwargs):
conn, cursor = self.get_conn_cursor()

cursor.execute(sql, kwargs)
result = cursor.fetchall()

self.close_conn_cursor(conn, cursor)

return result


db = DBHelper()

from db import db

db.exec("insert into d1(name) values(%(name)s)", name="武沛齐666")

ret = db.fetch_one("select * from d1")
print(ret)

ret = db.fetch_one("select * from d1 where id=%(nid)s", nid=3)
print(ret)

ret = db.fetch_all("select * from d1")
print(ret)

ret = db.fetch_all("select * from d1 where id>%(nid)s", nid=2)
print(ret)

4.2 上下文管理

如果你想要让他也支持 with 上下文管理。

with 获取连接:
执行SQL(执行完毕后,自动将连接交还给连接池)

# db_context.py
import threading
import pymysql
from dbutils.pooled_db import PooledDB

POOL = PooledDB(
creator=pymysql, # 使用链接数据库的模块
maxconnections=5, # 连接池允许的最大连接数,0和None表示不限制连接数
mincached=2, # 初始化时,链接池中至少创建的空闲的链接,0表示不创建
maxcached=3, # 链接池中最多闲置的链接,0和None不限制
blocking=True, # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
ping=0,
host='127.0.0.1',
port=3306,
user='root',
password='root123',
database='userdb',
charset='utf8'
)


class Connect(object):
def __init__(self):
self.conn = conn = POOL.connection()
self.cursor = conn.cursor(pymysql.cursors.DictCursor)

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self.cursor.close()
self.conn.close()

def exec(self, sql, **kwargs):
self.cursor.execute(sql, kwargs)
self.conn.commit()

def fetch_one(self, sql, **kwargs):
self.cursor.execute(sql, kwargs)
result = self.cursor.fetchone()
return result

def fetch_all(self, sql, **kwargs):
self.cursor.execute(sql, kwargs)
result = self.cursor.fetchall()
return result

from db_context import Connect

with Connect() as obj:
# print(obj.conn)
# print(obj.cursor)
ret = obj.fetch_one("select * from d1")
print(ret)

ret = obj.fetch_one("select * from d1 where id=%(id)s", id=3)
print(ret)

总结

本节内容比较重要,也是开发中经常会使用到的技能。

  • 事务,解决批量操作同时成功或失败的问题。

  • 锁,解决并发处理的问题。

  • 数据库连接池,解决多个人请求连接数据库的问题。

  • SQL工具类,解决连接数据库代码重复的问题。

  • navicat工具


链接:https://www.cnblogs.com/sbhglqy/p/18163862

(版权归原作者所有,侵删)


微信扫码关注该文公众号作者

来源:马哥Linux运维

相关新闻

Python和AI可以在线学习了!Fedora终于要和Python 2.7说再见Rust编写的Web开发框架——同时支持Node和Python谷歌裁员波及Flutter和Python等团队,但没有被“杀死”——而是“地点重构”(aka异地重组)Python中使用MySQL模糊查询的方法LVM分区管理实战:创建和扩容的完整指南Python教程:sort和sorted实现排序之对比【上海】Python期货量化交易实战班6期深入理解Python多进程:从基础到实战仅剩3席|全面掌握Python, SQL等数据分析语言、工具和拓展包,高效斩获心仪offer!AI实战派:带你构建基于大模型和实时音视频的社交应用掌握Python多线程编程:从入门到实战全攻略小国学项目实战首次招募,人均产值2-3万一个月,可复制可团队操作(VIP课程,包学会)Python量化编程提高班2期Docker实战教程:全套学习笔记+项目实践,一站式掌握!(包学会)Python量化编程提高班2期吴恩达亲自授课,LLM当「助教」,适合初学者的Python编程课程上线AI产品增长:谈PMF之前,先做到AI Market Fit | 榕汇实战分享继裁掉 Python 团队后,谷歌 Go 团队也迎来动荡:团队灵魂人物、领导 Go 十二年的技术负责人突然宣布退位Anaconda 新 AI 路线图:更快的 Python,更轻松地应用大模型留学生无门槛入门!MIT内部Python课程,12小时学完东风岚图 AI 技术人才培养三步走:认知构建、技术提升、场景实战 | 极客时间企业版报名开启!实战派技术大佬在线编码,传授全生命周期高效开发秘籍 | Q推荐福建舰组成航母战斗群,海试已无限接近实战?
logo
联系我们隐私协议©2024 bendi.news
Bendi新闻
Bendi.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Bendi.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。