Redian新闻
>
python操作MySQL和实战

python操作MySQL和实战

公众号新闻

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

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


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

戳这里提交新闻线索和高质量文章给我们。
相关阅读
MySQL的多存储引擎架构MySQL版本越高,性能越差,这你受得了吗?谷歌裁掉整个 Python 团队!PyTorch 创始人急得直骂人:“WTF!核心语言团队无可替换”MySQL-Xenon高可用果实MySQL 巨坑:永远不要在 MySQL 中使用 UTF-8!我在淘宝写SQL|ODPS SQL 优化总结谷歌Python团队全员被裁——负责内部Python所有基础设施、曾对数亿行代码执行自动重构MySQL联表查询优化配置MySQL主从复制和读写分离被波加查激励的早晨,和爱美丽逛街突发!谷歌 Python 团队全体被裁,急得 PyTorch 之父爆粗【深入学习MySQL】MySQL的索引结构为什么使用B+树?SQL 50 周年了,但 SQL 之父已经“投靠”NoSQL 了突发!谷歌Python团队解散,PyTorch之父震惊宾州雷德利克里克公园(Ridley creek park),温馨的画面【上海】Python期货量化交易实战班6期富有是为了什么?(三)吴恩达团队新作ManyICL:多样本上下文学习显著提高多模态基础模型性能!项目从 MySQL 切换 PostgreSQL,踩了太多的坑!!!甲骨文杀死了MySQL摩根大通:再见Python,你好SQL深入理解Python多进程:从基础到实战Pylon框架:在PyTorch中实现带约束的损失函数MySQL索引你用对了吗?MySQL慢查询及优化谷歌突破2万亿美元里程碑,却被曝裁掉整个Python团队?PyTorch之父怒批离谱手把手带你从自建 MySQL 迁移到云数据库,一步就能脱胎换骨【买房】Natick独栋 | 街角阳光充足 高性价比 生活便利 近Mall和紫线 | 4b2b 1,298sqft $89.9W面向数据库的操作系统:80 岁的 PostgreSQL 之父宣布推出 DBOS Cloud一个 Python 的轻量级搜索工具【买房】Watertown多家庭 稳定高投资回报 近Arsenal和小中高 | 6b3b 2,644sqft $104.5WMySQL忘记root密码解决方案《疏影 - 梅》仅剩3席|全面掌握Python, SQL等数据分析语言、工具和拓展包,高效斩获心仪offer!
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。