MySQL是一种常用的存储系统,内容仅为基础知识.
本文包含:
第一部分介绍如何通过mysql-connector-python
进行基础数据库操作,
第二部分包括SQL语法、事务、锁的基础知识。
本文不包括: Mysql的安装过程.
Python基础请看: Python基础语法
先确保已安装mysql-connector-python
包:
pip install mysql-connector-python
下面介绍的是通过这个模块进行的mysql操作
通过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
# 创建表
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")
# 插入单行
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()
读取全部: 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')]
事务能确保数据的一致性,需要在操作后手动提交。
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"))
MySQL支持下列事务隔离级别:
cursor.execute("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ")
cursor.execute("SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ")
在实际应用中,选择合适的隔离级别能平衡性能和一致性。例如,对于银行账户余额查询,应优先选择 REPEATABLE READ
或更高级别,以避免读取错误或数据不一致。
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 | 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 | 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;
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
是小数位数。当Mysql 不存在索引的情况时, 数据库通常会采用全表扫描的方式来查找符合条件的数据。这意味着数据库会从表的第一行开始,逐行检查每一行记录,直到找到所有满足查询条件的行或者扫描完整个表。
索引的目的: 索引就像是一本书的目录,通过索引,数据库系统可以快速定位到符合查询条件的数据所在的位置,而不需要对整个表进行全表扫描。
Mysql的Innodb引擎索引使用B+Tree实现. 实现细节本文不做讲述.
创建索引的多种方式
# 创建普通的索引.
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);
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;
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;
SELECT
、INSERT
、UPDATE
和 DELETE
。SELECT name FROM users WHERE id IN (SELECT user_id FROM orders WHERE amount > 100);
SELECT name FROM users WHERE EXISTS (SELECT 1 FROM orders WHERE user_id = users.id);
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); |
# 匹配 name 包含 “三儿” 的所有数据
SELECT * FROM users WHERE age > 20 AND name LIKE '%三儿%';
说明: 查询年龄大于20且名字包含 三儿
的用户。
SELECT * FROM orders WHERE order_date BETWEEN '2024-01-01' AND '2024-12-31';
说明: 查询2024年内的所有订单。
# 匹配后缀为: @gmail.com的所有数据
SELECT * FROM users WHERE email LIKE '%@gmail.com';
说明: 查询邮箱是 Gmail 的所有用户。
SELECT * FROM users WHERE EXISTS (SELECT 1 FROM orders WHERE orders.user_id = users.id AND orders.amount > 100);
说明: 查询下过订单且订单金额超过100的用户。
SELECT * FROM products WHERE category_id IN (1, 2, 3);
说明: 查询分类ID为 1、2 或 3 的商品。
GROUP BY
将结果按指定列进行分组。SELECT COUNT(*), AVG(amount) FROM orders GROUP BY user_id;
GROUP BY
结果进行过滤,类似于 WHERE
但它适用于分组后的结果。SELECT user_id, COUNT(*) FROM orders GROUP BY user_id HAVING COUNT(*) > 5;
假设“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 |
聚合函数对一组数据执行计算并返回单一结果。
SELECT COUNT(*) FROM orders where user_id = 123;
说明: 订单表查询用户ID=123
的订单总条数, 输出: 2
.
SELECT SUM(amount) FROM orders where status = 'ok';
说明: 统计订单表状态为ok
的总金额数, 结果 25
SELECT AVG(amount) FROM orders;
说明: 求订单总金额的平均值.
SELECT MAX(amount) FROM orders;
SELECT MIN(amount) FROM orders;
说明: 最大值: 25
. 最小值: 10
继续补充关于MySQL中锁的相关内容:
MySQL提供了多种锁机制,以保证数据的一致性和事务的并发控制。锁的使用有助于解决并发操作时可能发生的数据竞争问题。在MySQL中,主要有行级锁、表级锁以及多种锁模式,包括排他锁和共享锁。 其他的: (意向锁、间隙锁、插入意向锁、Next-Key锁等本文不做介绍, 可以查看官方资料)
排他锁:当事务获取了排他锁时,其他事务无法对相同的数据行进行任何类型的操作(即不能获取共享锁或排他锁)。排他锁常用于 UPDATE
或 DELETE
操作。
SELECT * FROM users WHERE id = 1 FOR UPDATE;
FOR UPDATE
表示获取行级排他锁,直到事务提交或回滚。共享锁:当事务获取了共享锁时,其他事务可以读取该数据,但不能修改。多个事务可以对相同的数据行获取共享锁,但不能获取排他锁。
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
LOCK IN SHARE MODE
表示获取共享锁,允许其他事务读取数据,但不允许修改。无锁:在默认情况下,如果没有使用任何锁,MySQL会自动进行无锁操作。查询是非阻塞的,并且可以被其他事务读取和修改。这种方式适合不需要事务保护的只读操作。
SELECT * FROM users WHERE id = 1;
行级锁:行级锁是MySQL最细粒度的锁,锁定数据表中的一行。它适用于InnoDB存储引擎,并能允许并发执行更多的事务,因为锁定的是数据行而不是整张表。行级锁的开销相对较高,但能够实现较高的并发性。
SELECT * FROM users WHERE id = 1 FOR UPDATE;
表级锁:表级锁是MySQL的另一种锁机制,它会锁定整张表。表级锁的粒度较大,通常会导致较低的并发性能,因为表级锁在操作期间会锁住整个表,直到事务提交或回滚。表级锁适用于MyISAM存储引擎。
LOCK TABLES users WRITE;
users
加写锁,防止其他事务访问该表。MySQL提供四种事务隔离级别,每个级别都与锁的行为密切相关。不同的事务隔离级别影响事务之间如何读取数据、锁的使用以及并发控制。 可以查看: 官方资料
READ UNCOMMITTED:
READ COMMITTED:
REPEATABLE READ (MySQL的默认隔离级别):
SERIALIZABLE:
等待情况:事务在等待获取锁时,可能会发生“等待”情况。当一个事务等待一个已锁定的资源时,可能会导致事务阻塞,直到该资源被释放。
死锁:在多个事务相互持有对方需要的锁时,会发生死锁,导致这些事务相互等待,永远无法继续执行。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
同时执行, 事务1
在id=1
的记录上获得排他锁, 同时执行事务2
在id=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 的行。
INSERT操作的行为:在 INSERT 时,MySQL会试图插入新的记录 id = 1。
因此,两个事务会因为对同一间隙或行的锁定而相互等待,导致死锁。
解决方式: 可以执行前调整事物的隔离级别(READ COMMITTED). 或改变实现思路.