Python与MySQL数据库操作教程

Python与MySQL数据库操作教程

MySQL是一种常用的存储系统,内容仅为基础知识.
本文包含:
第一部分介绍如何通过mysql-connector-python进行基础数据库操作,
第二部分包括SQL语法、事务、锁的基础知识。

本文不包括: Mysql的安装过程.


Python基础请看: Python基础语法

一、Python操作Mysql

1. Python-Mysql模块下载

先确保已安装mysql-connector-python包:

pip install mysql-connector-python

下面介绍的是通过这个模块进行的mysql操作

2. 基础SQL语法概述
  • DDL (数据定义语言):创建和修改数据库和表。
  • DML (数据操作语言):提供数据操作如插入,删除,修改。
  • DQL (数据查询语言):用于查询数据。
3. 创建连接

通过Python进行MySQL操作,需要创建和配置连接。

# 导入mysql的操作模块
import mysql.connector

# 连接数据库
connection = mysql.connector.connect(
    host="localhost",        # 数据库IP地址
    port: 3306,              # 数据库端口
    user="root",             # 用户名
    password="your_password",# 密码
    database="test_db"       # 默认是哪个库
)

cursor = connection.cursor()
# 数据库操作就使用 cursor
4. DDL: 表的创建和删除
创建表
# 创建表
create_table_query = """
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(100),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
"""
cursor.execute(create_table_query)
删除表
# 删除表
cursor.execute("DROP TABLE IF EXISTS users")
5. DML: 插入、删除和修改
插入数据
# 插入单行
insert_query = "INSERT INTO users (name, email) VALUES (%s, %s)"
values = ("张三", "zhangsan@example.com")
cursor.execute(insert_query, values)
connection.commit()

# 插入多行
values = [
    ("莉丝", "lisi@example.com"),
    ("王武", "wangwu@example.com")
]
cursor.executemany(insert_query, values)
connection.commit()
删除数据
# 删除指定数据
delete_query = "DELETE FROM users WHERE name = %s"
values = ("王武",)
cursor.execute(delete_query, values)
connection.commit()
修改数据
# 修改指定数据
update_query = "UPDATE users SET email = %s WHERE name = %s"
values = ("zhangsan123@example.com", "张三")
cursor.execute(update_query, values)
connection.commit()
6. DQL: 查询数据

读取全部: fetchall

# 查询数据
select_query = "SELECT * FROM users"
cursor.execute(select_query)

for row in cursor.fetchall():
    print(row)

一次仅读取一行数据: fetchone

select_query = "SELECT * FROM users"
cursor.execute(select_query)
while True:
    row = cursor.fetchone()
    if not row:
        break
    print(row)

执行结果:

(1, '张三', 'zhangsan@xxx.com')
(2, '莉丝', 'cc@cc.com')
.....
(6, '狗蛋', 'xxx@xx.com')

一次读取多条, 批读取: fetchmany(数量)

select_query = "SELECT * FROM users"
cursor.execute(select_query)
while True:
    row = cursor.fetchmany(3)
    if not row:
        break
    print(row)

执行结果:

[(1, '张三', 'zhangsan@xxx.com'), (2, '莉丝', 'cc@cc.com'), (3, '王武', 'cc@cc.com')]
[(4, '拖鞋', 'xxx@xx.com'), (5, '放肆', 'xxx@xx.com'), (6, '狗蛋', 'xxx@xx.com')]
7. 事务操作
事务优势

事务能确保数据的一致性,需要在操作后手动提交。

try:
    # 默认是False,需要手动commit或rollback完成事务, 如果设置True, 就会自动提交.
    conn.autocommit = False
    # 插入数据
    cursor.execute("INSERT INTO users (name, email) VALUES (%s, %s)", ("Dave", "dave@example.com"))
    
    # 事务提交
    connection.commit()
except Exception as e:
    # 回滚事务
    connection.rollback()
    print("Transaction failed:", e)
非事务操作

通过配置默认自动提交:

connection.autocommit = True
cursor.execute("INSERT INTO users (name, email) VALUES (%s, %s)", ("Eve", "eve@example.com"))
8. 事务隔离级别

MySQL支持下列事务隔离级别:

  • READ UNCOMMITTED: 允许读取未提交数据,存在脏读。这种级别性能最高,但数据一致性最低。
  • READ COMMITTED: 只读取已提交数据,防止脏读,但可能会出现不可重复读。
  • REPEATABLE READ: 防止脏读和不可重复读,默认值,但可能会导致幻读。
  • SERIALIZABLE: 最高隔离级别,防止所有问题,但性能开销最大。
设置事务隔离级别
  • 单次事务 (仅设置当前要执行的事务隔离级别):
cursor.execute("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ")
  • SESSION级别 (当前会话内均用这个隔离级别):
cursor.execute("SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ")

在实际应用中,选择合适的隔离级别能平衡性能和一致性。例如,对于银行账户余额查询,应优先选择 REPEATABLE READ 或更高级别,以避免读取错误或数据不一致。


二、Mysql的SQL语句介绍

1. 表创建和变更语法
  • 数据库:
CREATE {DATABASE | SCHEMA} [IF NOT EXISTS] db_name
[create_option] ...
 
# 例如
create schema test_db collate utf8_general_ci;

create_option:

选项 含义 示范
CHARACTER SET 字符集 CHARACTER SET UTF8
COLLATE 字符排序规则 COLLATE utf8_general_ci
不填写 默认
  • 创建表: 使用 CREATE TABLE 语句创建一个新表。
# 创建表, 对于 "[] " 包含的意思, 可选的、非必填.
CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name
    (create_definition,...) # 表内结构的定义, 字段,索引定义
    [table_options]         # 表设置:  存储引擎, 字符集等.
    [partition_options]     # 分区设置
# 例如示范: 
CREATE TABLE table_name (
    id NOT NULL AUTO_INCREMENT PRIMARY KEY,
    col_name 列属性定义 ,
    ...列定义...
    INDEX index_name (col_name)
    ...索引定义...
) 
...表的属性...

# ---
# IF NOT EXISTS 如果不存在时才创建
CREATE TABLE IF NOT EXISTS table_name (...) ...

实例: 创建 : users 表

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) COMMENT '用户名',
    email VARCHAR(100) COMMENT '邮箱',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) engine = InnoDB charset = utf8 collate = utf8_general_ci COMMENT '用户表'  ;

上面的表创建后如下:

字段名称 id name email created_at
数据类型 INT VARCHAR(100) VARCHAR(100) TIMESTAMP
描述 自增主键 用户名 邮箱 创建时间

其他说明:

engine: 存储引擎选择 innodb , 也是默认
charset: 设置表默认编码, 如果不设置与数据库一致;
collate: 排序规则选择, 如果不设置与数据库一致; “utf8_general_ci” 是一种基于 “utf8” 字符集的排序规则。其中,“ci” 表示 “Case Insensitive”,也就是不区分大小写的排序方式。
  • 修改表结构: 使用 ALTER TABLE 来添加、修改或删除列。
# 变更表, 对于 "[] " 包含的意思, 可选的、非必填.
ALTER TABLE tbl_name
    [alter_option [, alter_option] ...] # 变更的设置
    [partition_options]
# 对于 alter_option 部分, 
  # ADD 追加列, 列定义, 追加在哪个列之前/后.
  ADD [COLUMN] col_name column_definition [FIRST | AFTER col_name]
  # DROP 删除列
  DROP [COLUMN] col_name
  # CHANGE 修改列, 旧列名, 新列名, 新列定义, 改变列位置. 
  CHANGE [COLUMN] old_col_name new_col_name column_definition
        [FIRST | AFTER col_name]
  # 添加索引 "{}" 包含选其一索引类型, 索引名, 索引类型[可选参], key_part 哪些列创建索引.
  ADD {INDEX | KEY} [index_name] [index_type] (key_part,...) [index_option] ...

例如: 给users表添加列, 和改变已有列的属性

# 新增 age 列
ALTER TABLE users ADD COLUMN age INT; 
# 修改email 的字符最大长度为 150
ALTER TABLE users MODIFY COLUMN email VARCHAR(150);
# 为users表 name 增加普通索引
ALTER TABLE users ADD INDEX idx_name_index (name);

修改完的表长这样: 如下

字段名称 id name email created_at age
数据类型 INT VARCHAR(100) VARCHAR(150) TIMESTAMP INT
描述 自增主键 用户名 邮箱 创建时间 年龄

删除字段

   # 例如删除这个列
   ALTER TABLE users DROP COLUMN age;
  • 删除表: 使用 DROP TABLE 删除表及其所有数据。
# IF EXIST 如果表存在的情况. 
DROP TABLE IF EXISTS users;
# 直接删, 如果不存在就发生错误.
DROP TABLE users;

2. 字段数据类型表

MySQL支持的常见数据类型包括:

  • 整数类型:

    • TINYINT: 小整数类型,范围从-128到127。
    • SMALLINT: 小整数类型,范围从-32,768到32,767。
    • INT: 标准整数类型,范围从-2,147,483,648到2,147,483,647。
    • BIGINT: 大整数类型,适合更大范围的整数。
  • 字符和字符串类型:

    • VARCHAR(size): 可变长度字符串类型,最大长度为size
    • CHAR(size): 固定长度字符串类型,长度为size,不够部分会填充空格。
    • TEXT: 用于存储较大文本内容。
  • 日期和时间类型:

    • DATE: 存储日期,格式为 YYYY-MM-DD
    • TIME: 存储时间,格式为 HH:MM:SS
    • DATETIME: 存储日期和时间,格式为 YYYY-MM-DD HH:MM:SS
    • TIMESTAMP: 存储时间戳,通常用于记录事件的时间。自动更新为当前时间(如设置为默认值或自动填充)。
    • YEAR: 存储年份,格式为 YYYY
  • 浮动点数类型:

    • FLOAT: 存储浮动精度的数字,通常用于科学计算。可以指定精度和小数位数,如 FLOAT(7, 4) 表示总共7位数字,其中4位为小数。
    • DOUBLE: 双精度浮动点数,提供比 FLOAT 更高的精度。
    • DECIMAL: 定点数类型,用于存储需要精确计算的数值,常用于金融计算。定义格式为 DECIMAL(M, D),其中 M 是总位数,D 是小数位数。

3. 表索引

当Mysql 不存在索引的情况时, 数据库通常会采用全表扫描的方式来查找符合条件的数据。这意味着数据库会从表的第一行开始,逐行检查每一行记录,直到找到所有满足查询条件的行或者扫描完整个表。
索引的目的: 索引就像是一本书的目录,通过索引,数据库系统可以快速定位到符合查询条件的数据所在的位置,而不需要对整个表进行全表扫描。

Mysql的Innodb引擎索引使用B+Tree实现. 实现细节本文不做讲述.

  • 创建索引: 在 MySQL 的 InnoDB 引擎中,索引对于提升查询效率起着关键作用,尤其在处理大数据量时效果显著。索引主要分为聚簇索引和非聚簇的普通索引:
    • 聚簇索引: 通常以主键作为聚簇索引,其索引结构中不仅包含索引键值,还涵盖了完整的列数据。当通过聚簇索引查询到结果后,所需的列数据可直接获取使用,无需额外的数据查找操作,从而加快查询速度。
    • 普通索引: 普通索引仅存储索引键的值以及对应的主键值。当查询涉及到除索引列和主键之外的更多列信息时,会先根据索引找到主键,再通过主键进行二次回表查询数据;若查询仅涉及索引列和主键,则可直接返回结果,避免回表操作,一定程度上也能提高查询性能。

创建索引的多种方式

# 创建普通的索引. 
CREATE INDEX index_name ON `table_name` (field_name);
# 变更表的方式追加索引
ALTER TABLE `table_name` ADD INDEX index_name (field_name);
# 创建表语句里面创建索引
CREATE TABLE table_name (...., INDEX index_name (field_name));

例如: 给users表的name字段增加普通索引

 # 例如给users表的name字段增加普通索引
CREATE INDEX idx_name ON users (name);
  • 删除索引: 删除一个索引使用 DROP INDEX

    • 删除索引有风险: 在生产环境中,删除正在被用于数据访问的索引存在风险,尤其当表数据量庞大时,索引删除后将导致查询从基于索引的快速定位转为全表搜索,严重影响查询性能与系统响应效率。可能会导致应用程序崩溃 (例如: 同服务应用下慢查询导致线程资源耗尽、使应用无法在正常去接受任何新业务处理)。
# 将users的某个索引删除. 
DROP INDEX idx_name ON users;
  • 唯一索引: 确保列中每个值的唯一性。
    • 与普通索引相似. 值必须唯一, 允许空置.

唯一索引创建方式:

CREATE UNIQUE INDEX index_name ON `table_name` (field_name);
ALTER TABLE `table_name` ADD UNIQUE INDEX index_name (field_name);
CREATE TABLE table_name (...., UNIQUE index_name (field_name));

例如: 给 email 增加唯一索引.

CREATE UNIQUE INDEX idx_unique_email ON users (email);
  • 主健索引: 每个表只有一个主键.。
    • 主键索引具有唯一性,每个表仅能有一个,其值不允许为空。既可以在创建表时设定,也能在修改表时追加主键约束来实现,确保表中每行数据在主键列上具有唯一标识性。
    • 建议所有表都必须存在主键.
CREATE TABLE table_name (...., PRIMARY KEY (field_name));
ALTER TABLE `table_name` ADD PRIMARY KEY (field_name);

4. 插入/更新/删除数据
  • 插入数据: 使用 INSERT INTO 语句插入单行或多行数据。
# 由于id 自增主键, 所以可以忽略不写. 插入后id自动+1填充
INSERT INTO users (name, email, age) VALUES ('cat', 'cat@example.com', 18);
  • 更新数据: 使用 UPDATE 更新现有数据。
# 比如更新 id = 1 的邮箱
UPDATE users SET email = 'new_email@example.com' WHERE id = 1;
  • 删除数据: 使用 DELETE FROM 删除数据。
DELETE FROM users WHERE id = 1;

5. 查询语句
  • 基本查询:
SELECT * FROM table_name;
SELECT * FROM table_name where field_name = xx and ...;
# 排序 order by
SELECT * FROM table_name order by field_name asc;
# 分组查询 group by 
SELECT field_name, count(*) FROM table_name group by field_name;
  • 查询特定列:
SELECT name, email FROM users;
  • 排序: 使用 ORDER BY 来对结果进行排序。
SELECT * FROM users ORDER BY created_at DESC;
  • 限制返回行数: 使用 LIMIT 限制返回结果的行数。
SELECT * FROM users LIMIT 5;
SELECT * FROM users LIMIT 0, 5;
# 跳过10, 取5条
SELECT * FROM users LIMIT 10, 5;
  • 子查询: 子查询是嵌套在其他查询中的查询,可以用于 SELECTINSERTUPDATEDELETE
SELECT name FROM users WHERE id IN (SELECT user_id FROM orders WHERE amount > 100);
  • EXISTS 子查询: 检查子查询是否返回至少一行数据。
SELECT name FROM users WHERE EXISTS (SELECT 1 FROM orders WHERE user_id = users.id);

6. WHERE 条件使用

WHERE 子句用于筛选符合条件的记录。

SELECT * FROM users WHERE email LIKE '%example.com';
SELECT * FROM users WHERE age BETWEEN 20 AND 30;
SELECT * FROM users WHERE created_at >= '2024-01-01';

以下是 MySQL 中 WHERE 查询语句常用条件符号的列表,包括每个符号的说明和示例:


条件符号列表
符号 说明 示例
= 等于 SELECT * FROM users WHERE id = 1;
<>!= 不等于 SELECT * FROM users WHERE name <> '张三';SELECT * FROM users WHERE name != '张三';
> 大于 SELECT * FROM users WHERE age > 18;
< 小于 SELECT * FROM users WHERE age < 18;
>= 大于等于 SELECT * FROM users WHERE age >= 18;
<= 小于等于 SELECT * FROM users WHERE age <= 18;
BETWEEN 在范围内 SELECT * FROM users WHERE age BETWEEN 20 AND 30;
NOT BETWEEN 不在范围内 SELECT * FROM users WHERE age NOT BETWEEN 20 AND 30;
IN 值在列表中 SELECT * FROM users WHERE name IN ('', '莉丝', '王武');
NOT IN 值不在列表中 SELECT * FROM users WHERE name NOT IN ('张三', '莉丝', 'Charlie');
IS NULL 为空 SELECT * FROM users WHERE email IS NULL;
IS NOT NULL 不为空 SELECT * FROM users WHERE email IS NOT NULL;
LIKE 模糊匹配 SELECT * FROM users WHERE name LIKE 'A%';(以 A 开头)
NOT LIKE 模糊匹配排除 SELECT * FROM users WHERE name NOT LIKE 'A%';
% 通配符,表示任意多个字符 SELECT * FROM users WHERE name LIKE '%example.com';(以 example.com 结尾)
_ 通配符,表示一个字符 SELECT * FROM users WHERE name LIKE '_lice';(匹配 Alice, 但不匹配 Malice)
EXISTS 子查询中是否存在记录 SELECT * FROM users WHERE EXISTS (SELECT 1 FROM orders WHERE users.id = orders.user_id);
NOT EXISTS 子查询中是否不存在记录 SELECT * FROM users WHERE NOT EXISTS (SELECT 1 FROM orders WHERE users.id = orders.user_id);
AND 逻辑与 SELECT * FROM users WHERE age > 18 AND name LIKE 'A%';
OR 逻辑或 SELECT * FROM users WHERE age < 18 OR name LIKE 'A%';
NOT 逻辑非 SELECT * FROM users WHERE NOT (age > 18);

示例扩展
1. 使用逻辑运算符
# 匹配 name 包含 “三儿” 的所有数据
SELECT * FROM users WHERE age > 20 AND name LIKE '%三儿%';

说明: 查询年龄大于20且名字包含 三儿 的用户。

2. 使用范围查询
SELECT * FROM orders WHERE order_date BETWEEN '2024-01-01' AND '2024-12-31';

说明: 查询2024年内的所有订单。

3. 使用模糊匹配
# 匹配后缀为: @gmail.com的所有数据
SELECT * FROM users WHERE email LIKE '%@gmail.com';

说明: 查询邮箱是 Gmail 的所有用户。

4. 使用子查询
SELECT * FROM users WHERE EXISTS (SELECT 1 FROM orders WHERE orders.user_id = users.id AND orders.amount > 100);

说明: 查询下过订单且订单金额超过100的用户。

5. 使用列表匹配
SELECT * FROM products WHERE category_id IN (1, 2, 3);

说明: 查询分类ID为 1、2 或 3 的商品。


7. 分组排序
  • 分组: 使用 GROUP BY 将结果按指定列进行分组。
SELECT COUNT(*), AVG(amount) FROM orders GROUP BY user_id;
  • HAVING: 对 GROUP BY 结果进行过滤,类似于 WHERE 但它适用于分组后的结果。
SELECT user_id, COUNT(*) FROM orders GROUP BY user_id HAVING COUNT(*) > 5;
9. 聚合函数和实例

假设“orders”表数据如下

order_id user_id amount status 其他列
1 123 10.00 ok xx
2 333 20.00 fail xx
3 444 15.00 ok xx
4 123 25.00 init xx

聚合函数对一组数据执行计算并返回单一结果。

  • COUNT(): 计算行数。
SELECT COUNT(*) FROM orders where user_id = 123;

说明: 订单表查询用户ID=123 的订单总条数, 输出: 2.


  • SUM(): 计算总和。
SELECT SUM(amount) FROM orders where status = 'ok';

说明: 统计订单表状态为ok的总金额数, 结果 25


  • AVG(): 计算平均值。
SELECT AVG(amount) FROM orders;

说明: 求订单总金额的平均值.


  • MAX()MIN(): 查找最大值和最小值。
SELECT MAX(amount) FROM orders;
SELECT MIN(amount) FROM orders;

说明: 最大值: 25. 最小值: 10


继续补充关于MySQL中的相关内容:


三、MySQL中的锁

MySQL提供了多种锁机制,以保证数据的一致性和事务的并发控制。锁的使用有助于解决并发操作时可能发生的数据竞争问题。在MySQL中,主要有行级锁、表级锁以及多种锁模式,包括排他锁和共享锁。 其他的: (意向锁、间隙锁、插入意向锁、Next-Key锁等本文不做介绍, 可以查看官方资料)

1. 锁的基本概念
  • 锁的作用: 锁是为了防止多个事务对相同数据的并发修改,从而避免出现脏读、不可重复读和幻读等问题。
  • 锁的粒度: 锁可以分为两种粒度,行级锁和表级锁。
  • 锁的类型: MySQL支持多种类型的锁,如排他锁(写锁)、共享锁(读锁)。
2. 排他锁(写锁)
  • 排他锁:当事务获取了排他锁时,其他事务无法对相同的数据行进行任何类型的操作(即不能获取共享锁或排他锁)。排他锁常用于 UPDATEDELETE 操作。

    • 使用场景:排他锁确保事务的独占性,避免了其他事务修改相同数据。通常用于修改数据时。
    SELECT * FROM users WHERE id = 1 FOR UPDATE;
    
    • 在此查询中,FOR UPDATE 表示获取行级排他锁,直到事务提交或回滚。
3. 共享锁(读锁)
  • 共享锁:当事务获取了共享锁时,其他事务可以读取该数据,但不能修改。多个事务可以对相同的数据行获取共享锁,但不能获取排他锁。

    • 使用场景:共享锁常用于读取数据时,确保其他事务不能对读取的数据进行修改。
    SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
    
    • LOCK IN SHARE MODE 表示获取共享锁,允许其他事务读取数据,但不允许修改。
4. 无锁(默认情况)
  • 无锁:在默认情况下,如果没有使用任何锁,MySQL会自动进行无锁操作。查询是非阻塞的,并且可以被其他事务读取和修改。这种方式适合不需要事务保护的只读操作。

    SELECT * FROM users WHERE id = 1;
    
    • 这条查询不会加锁,任何其他事务都可以同时执行读取或修改相同的数据行。
5. 行级锁和表级锁
  • 行级锁:行级锁是MySQL最细粒度的锁,锁定数据表中的一行。它适用于InnoDB存储引擎,并能允许并发执行更多的事务,因为锁定的是数据行而不是整张表。行级锁的开销相对较高,但能够实现较高的并发性。

    SELECT * FROM users WHERE id = 1 FOR UPDATE;
    
    • 以上语句为行级锁示例,它会锁定表中id为1的行,防止其他事务修改或读取该行数据。
  • 表级锁:表级锁是MySQL的另一种锁机制,它会锁定整张表。表级锁的粒度较大,通常会导致较低的并发性能,因为表级锁在操作期间会锁住整个表,直到事务提交或回滚。表级锁适用于MyISAM存储引擎。

    LOCK TABLES users WRITE;
    
    • 以上语句为表级锁示例,它会对表 users 加写锁,防止其他事务访问该表。
6. 事务隔离级别与锁的关系

MySQL提供四种事务隔离级别,每个级别都与锁的行为密切相关。不同的事务隔离级别影响事务之间如何读取数据、锁的使用以及并发控制。 可以查看: 官方资料

  • READ UNCOMMITTED:

    • 读未提交:允许事务读取其他事务未提交的数据,可能会出现脏读。
    • 锁的行为:几乎没有锁,读取数据时不会加锁,允许其他事务同时修改数据。
  • READ COMMITTED:

    • 读已提交:只能读取已提交的数据,防止脏读,但可能会出现不可重复读。
    • 锁的行为:在读取时会加共享锁,防止读取未提交的数据。在数据修改时,会加排他锁。
  • REPEATABLE READ (MySQL的默认隔离级别):

    • 可重复读:确保在同一个事务中多次读取数据时,数据保持一致,防止脏读和不可重复读,但可能会出现幻读。
    • 锁的行为:会对读取的数据加共享锁,在修改数据时加排他锁。MySQL通过间隙锁(gap lock)防止幻读。
  • SERIALIZABLE:

    • 串行化:最严格的隔离级别,事务完全串行执行,防止所有并发问题(脏读、不可重复读、幻读)。
    • 锁的行为:对读取的数据加共享锁,对修改的数据加排他锁,甚至会锁定未查询的间隙(避免幻读)。
7. 等待情况和死锁
  • 等待情况:事务在等待获取锁时,可能会发生“等待”情况。当一个事务等待一个已锁定的资源时,可能会导致事务阻塞,直到该资源被释放。

  • 死锁:在多个事务相互持有对方需要的锁时,会发生死锁,导致这些事务相互等待,永远无法继续执行。MySQL会自动检测死锁,并回滚其中一个事务。

    • 死锁示例

      事务1:

      BEGIN;
      UPDATE users SET name = 'abc' WHERE id = 1;
      UPDATE users SET name = 'xxx' WHERE id = 2;
      COMMIT; 
      

      事务2:

      BEGIN;
      UPDATE users SET name = 'eee' WHERE id = 2;
      UPDATE users SET name = 'abc' WHERE id = 1;
      COMMIT;
      

      假设上面的结果, 并行出现, 如下解释:
      begin: 开启事物.
      事务1事物2同时执行, 事务1id=1的记录上获得排他锁, 同时执行事务2id=2记录上获得锁.
      此时: 事务1继续执行的时候, id=2已经被事务2获得锁, 需要等待事物2释放.
      事物2继续执行时, id=1已经被事务1占用锁, 所以需要等待事物1释放.
      事物2事物1相互等待, 造成死锁.
      (模拟上面的结果, 可以命令逐行执行. 模拟并发效果.)


    • 其他示范:其他并发情况会出现死锁的情况

      事物1

      begin;
      select * from users where id = 1 LOCK IN SHARE MODE;
      update users set name = 'xxx' where id = 1;
      commit;
      

      事物2

      begin;
      select * from users where id = 1 LOCK IN SHARE MODE;
      update users set name = 'xxx' where id = 1;
      commit;
      

      上述情况并发时候出现死锁的原因. (均并发时候)
      事物1,事物2 并行时, 查询时, 由于共享锁的特性,多个事务可以同时持有共享锁
      事物1 update执行时候, 需要获得id=1的写锁(排他锁), 但排他锁与共享锁不兼容, 在事务 2 还持有该记录的共享锁的情况下,事务 1 无法获取排他锁,于是事务 1 进入等待状态,等待事务 2 的共享锁释放。
      与此同时,事务 2 也执行update语句, 同样需要获取排他锁。此时事务 1 尚未释放其持有的共享锁,事务 2 也只能等待事务 1 的共享锁释放,从而形成了一种相互等待的死锁局面。
      LOCK IN SHARE MODE: 共享锁.


  • 并发时 事物内更新不存在时在插入数据, 造成的死锁.

    update users set name = 'xxx' where id = 1;
    insert into (id, name) values (1,'222');
    

    出现死锁原因:

    • UPDATE操作的行为:在 UPDATE 时,MySQL 首先会尝试锁定 id = 1 的行。

      • 如果行存在:MySQL对该行加排他锁(写锁)。
      • 如果行不存在:MySQL会在间隙(gap)上加锁,这种锁称为间隙锁(Gap Lock),以防止其他事务插入该位置的记录。
      • 并发时两个事物update, 由于不存在记录, 都会执行通过, 进入下面的语句.
      • 从官方资料上: 多事物多个间隙锁可以共存, 但会阻止其他事务插入间隙。 点击: mysql/doc/gap
    • INSERT操作的行为:在 INSERT 时,MySQL会试图插入新的记录 id = 1。

      • 如果发现冲突(例如,间隙已被锁定),事务将等待。

    因此,两个事务会因为对同一间隙或行的锁定而相互等待,导致死锁。
    解决方式: 可以执行前调整事物的隔离级别(READ COMMITTED). 或改变实现思路.


你可能感兴趣的:(python,mysql,数据库,python,mysql)