Python九阳神功之陆:数据库持久化与缓存·乾坤大挪移

前言
上一篇我们掌握了数据生产与流转 的内功心法,本篇进入数据存储与访问的实战领域。持久化是系统的根基,缓存是性能的灵魂。本文将带领你从 SQL 到 NoSQL,从 MySQL 到 Redis,打通数据存储的任督二脉。
一、数据库概论
1.1 什么是数据库
数据库(Database)是存储和管理数据的系统,数据按特定格式组织,支持增、删、改、查等操作。
1.2 数据库分类
| 类型 | 存储形式 | 代表产品 | 特点 |
|---|---|---|---|
| 关系型数据库(SQL) | 行列表格 | MySQL、Oracle、SQL Server | 结构化、支持SQL查询、事务ACID |
| 非关系型数据库(NoSQL) | 键值对/文档/列/图 | Redis、MongoDB、HBase | 松散结构、高性能、API调用 |
二、SQL 基础
2.1 SQL 分类
| 分类 | 全称 | 关键字 | 作用 |
|---|---|---|---|
| DDL | 数据定义语言 | CREATE、DROP、ALTER |
定义数据库/表结构 |
| DML | 数据操作语言 | INSERT、UPDATE、DELETE |
操作表记录 |
| DQL | 数据查询语言 | SELECT、FROM、WHERE |
查询数据(重点) |
| DCL | 数据控制语言 | GRANT、REVOKE |
权限管理 |
2.2 通用语法
- 以分号
;结尾 - 不区分大小写(关键字建议大写)
- 支持注释:
/* */、--、# - 支持缩进增强可读性
三、DDL --- 数据定义语言
3.1 数据库操作
sql
-- 创建数据库(存在则忽略)
CREATE DATABASE IF NOT EXISTS hm_db CHARSET utf8;
-- 查看所有数据库
SHOW DATABASES;
-- 使用数据库
USE hm_db;
-- 删除数据库(慎用!)
DROP DATABASE IF EXISTS hm_db;
3.2 表操作
sql
-- 创建表
CREATE TABLE IF NOT EXISTS user_info (
id INT PRIMARY KEY AUTO_INCREMENT,
user_name VARCHAR(32) NOT NULL,
phone CHAR(11),
id_card CHAR(18) UNIQUE,
city VARCHAR(255) DEFAULT '北京'
);
-- 查看所有表
SHOW TABLES;
-- 查看表结构
DESC user_info;
-- 删除表
DROP TABLE IF EXISTS user_info;
3.3 常用数据类型
| 类型 | 说明 |
|---|---|
INT |
整数(默认长度11) |
VARCHAR(n) |
变长字符串 |
CHAR(n) |
定长字符串 |
DECIMAL(m,n) |
精确小数 |
DATE |
日期(YYYY-MM-DD) |
DATETIME |
日期时间 |
3.4 约束速查
| 约束 | 关键字 | 作用 |
|---|---|---|
| 主键 | PRIMARY KEY |
非空唯一 |
| 自增 | AUTO_INCREMENT |
自动递增 |
| 非空 | NOT NULL |
不能为空 |
| 唯一 | UNIQUE |
不能重复 |
| 默认 | DEFAULT |
预设默认值 |
| 外键 | FOREIGN KEY |
关联另一张表 |
四、DML --- 数据操作语言
4.1 插入(INSERT)
sql
-- 指定字段插入(推荐)
INSERT INTO user_info (user_name, phone, id_card)
VALUES ('张三', '13888888888', '11010519900325002X');
-- 插入多条
INSERT INTO user_info (user_name, phone, id_card)
VALUES
('张三', '13888888888', '11010519900325002X'),
('李四', '13999999999', '11010519900325002Y');
-- 不指定字段(必须按顺序填所有列)
INSERT INTO user_info VALUES (NULL, '王五', '13777777777', '11010519900325002Z', '上海');
4.2 更新(UPDATE)
sql
-- ⚠️ 必须带条件,否则全表更新!
UPDATE user_info SET phone = '13666666666' WHERE user_name = '张三';
4.3 删除(DELETE)
sql
-- ⚠️ 必须带条件!
DELETE FROM user_info WHERE id = 1;
-- 清空表(两种方式)
DELETE FROM user_info; -- 自增保留
TRUNCATE TABLE user_info; -- 自重重置
五、DQL --- 数据查询语言(核心)
5.1 基础查询
sql
-- 查询所有列
SELECT * FROM products;
-- 查询指定列
SELECT name, price FROM products;
-- 去重
SELECT DISTINCT is_self FROM products;
-- 别名
SELECT name AS 商品名称, price 价格 FROM products;
5.2 条件查询(WHERE)
| 运算符 | 说明 |
|---|---|
=、!=、<>、>、<、>=、<= |
比较 |
AND、OR、NOT |
逻辑 |
BETWEEN x AND y |
连续范围 |
IN (x, y, z) |
枚举范围 |
LIKE + %(任意)_(单字符) |
模糊匹配 |
IS NULL / IS NOT NULL |
空值判断 |
sql
-- 价格大于3000且自营
SELECT * FROM products WHERE price > 3000 AND is_self = '自营';
-- 价格在 2199~4499 之间
SELECT * FROM products WHERE price BETWEEN 2199 AND 4499;
-- 品牌包含'华为'
SELECT * FROM products WHERE name LIKE '%华为%';
-- 分类为空
SELECT * FROM products WHERE category_id IS NULL;
5.3 排序(ORDER BY)
sql
-- 默认升序(ASC),降序(DESC)
SELECT * FROM products ORDER BY price DESC;
-- 多字段排序:先按价格降序,同价按评分升序
SELECT * FROM products ORDER BY price DESC, score ASC;
5.4 聚合函数
| 函数 | 作用 |
|---|---|
COUNT(*) |
统计行数(不忽略 NULL) |
SUM(列) |
求和 |
AVG(列) |
平均值 |
MAX(列) |
最大值 |
MIN(列) |
最小值 |
sql
SELECT
COUNT(*) AS 总商品数,
ROUND(AVG(price), 2) AS 均价,
MAX(price) AS 最高价,
MIN(price) AS 最低价
FROM products;
5.5 分组(GROUP BY)
sql
-- 按分类统计
SELECT
category_id,
COUNT(*) AS 商品数,
AVG(price) AS 均价
FROM products
GROUP BY category_id;
-- 加过滤条件(WHERE 分组前,HAVING 分组后)
SELECT
category_id,
AVG(price) AS 均价
FROM products
WHERE is_self = '自营'
GROUP BY category_id
HAVING 均价 > 2000;
5.6 分页(LIMIT)
sql
-- 第1页(每页4条)
SELECT * FROM products ORDER BY price DESC LIMIT 0, 4;
-- 第2页:LIMIT 偏移量, 条数
SELECT * FROM products ORDER BY price DESC LIMIT 4, 4;
-- 可读性写法
SELECT * FROM products ORDER BY price DESC LIMIT 4 OFFSET 4;
5.7 SQL 执行顺序(重要!)
书写顺序:SELECT → DISTINCT → 聚合函数 → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT
执行顺序:FROM → WHERE → GROUP BY → 聚合函数 → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT
六、多表查询
6.1 表关系
| 关系 | 场景 |
|---|---|
| 1对多 | 商品 → 分类 |
| 多对多 | 员工 ↔ 角色 ↔ 权限 |
| 1对1 | QQ号 → 用户详情 |
6.2 外键(Foreign Key)
概念:从表某列引用主表主键,保证数据完整性。
注意 :实际开发中更常用逻辑外键(由程序保证一致性),物理外键会降低开发灵活性。
6.3 连接查询
sql
-- ① 交叉连接(笛卡尔积)------ 无意义,了解即可
SELECT * FROM category, products;
-- ② 内连接(取交集)
SELECT c.name, p.name
FROM category c
INNER JOIN products p ON c.id = p.category_id;
-- ③ 左外连接(左表全保留)
SELECT c.name, p.name
FROM category c
LEFT JOIN products p ON c.id = p.category_id;
-- ④ 右外连接(右表全保留)
SELECT c.name, p.name
FROM products p
RIGHT JOIN category c ON p.category_id = c.id;
-- ⑤ 全外连接(MySQL 用 UNION 模拟)
SELECT * FROM category LEFT JOIN products ON category.id = products.category_id
UNION
SELECT * FROM category RIGHT JOIN products ON category.id = products.category_id;
6.4 自连接
sql
-- 查询河北省所有城市
SELECT city.title
FROM areas AS province
JOIN areas AS city ON province.id = city.pid
WHERE province.title = '河北省';
6.5 子查询
sql
-- 查询河北省所有市(单值子查询)
SELECT * FROM areas WHERE pid = (SELECT id FROM areas WHERE title = '河北省');
-- 查询河北/江苏所有市(多值子查询)
SELECT * FROM areas WHERE pid IN (SELECT id FROM areas WHERE title IN ('河北省', '江苏省'));
七、PyMySQL --- Python 操作 MySQL
7.1 安装与连接
bash
pip install pymysql
python
import pymysql
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='root',
password='root',
database='jing_dong'
)
cur = conn.cursor()
7.2 查询操作
python
# 执行查询
cur.execute("SELECT * FROM goods")
data = cur.fetchall() # 取所有
one = cur.fetchone() # 取一条
cur.close()
conn.close()
7.3 增删改(需提交事务)
python
try:
cur.execute("INSERT INTO goods(name) VALUES ('魅族手机')")
conn.commit() # 提交事务
except Exception:
conn.rollback() # 回滚
finally:
cur.close()
conn.close()
7.4 SQL 注入与防范
错误写法(拼接字符串):
python
# 危险!输入 `' OR '1'='1` 可绕过验证
sql = f"SELECT * FROM user WHERE user='{account}' AND pwd='{pwd}'"
正确写法(参数化):
python
sql = "SELECT * FROM user WHERE user=%s AND pwd=%s"
cur.execute(sql, (account, pwd)) # 参数化自动转义
7.5 事务回滚案例
python
try:
cur.execute("UPDATE nh_table SET account=0.5 WHERE user_id=1")
cur.execute("UPDATE jh_table SET account=1 WHERE user_id=1")
cur.execute("UPDATE alipay_table SET account=17 WHERE user_id=1")
conn.commit()
except Exception:
conn.rollback() # 任一步骤失败,全部撤销
八、Redis --- 内存中的瑞士军刀
8.1 Redis 简介
Redis(Remote Dictionary Server) 是基于内存的键值对存储数据库,常用于缓存、消息队列、分布式锁等场景。
核心特性:
- 基于内存,读写极快
- 支持数据持久化(RDB/AOF)
- 丰富的数据类型:String、Hash、List、Set、Sorted Set
- 支持过期时间、发布订阅
8.2 安装与连接
bash
# 安装 redis-py
pip install redis
python
import redis
r = redis.Redis(
host='127.0.0.1',
port=6379,
db=0,
decode_responses=True # 自动解码为字符串
)
8.3 String 操作
python
# 设置/获取
r.set('name', '老高')
r.get('name') # '老高'
# 批量设置/获取
r.mset({'name': '老高', 'age': 36})
r.mget(['name', 'age']) # ['老高', '36']
# 自增/自减
r.incr('age') # 37
r.decrby('age', 2) # 35
8.4 Hash 操作(字典)
python
# 单个字段
r.hset('user:001', 'name', '张三')
r.hget('user:001', 'name')
# 批量
r.hmset('user:001', {'name': '张三', 'age': 25})
# 获取全部
r.hgetall('user:001') # {'name': '张三', 'age': '25'}
# 获取所有键/值
r.hkeys('user:001') # ['name', 'age']
r.hvals('user:001') # ['张三', '25']
8.5 List 操作
python
# 左侧/右侧插入
r.lpush('queue', 'a', 'b', 'c') # ['c', 'b', 'a']
r.rpush('queue', 'd') # ['c', 'b', 'a', 'd']
# 范围获取
r.lrange('queue', 0, -1) # 全部
# 弹出
r.lpop('queue') # 'c'
r.rpop('queue') # 'd'
8.6 Set 操作(集合)
python
# 添加
r.sadd('skills', 'python', 'mysql', 'redis')
# 获取所有
r.smembers('skills') # {'redis', 'mysql', 'python'}
# 判断存在
r.sismember('skills', 'python') # True
# 集合运算
r.sadd('set_a', 1, 2, 3)
r.sadd('set_b', 2, 3, 4)
r.sinter('set_a', 'set_b') # {2, 3} 交集
r.sunion('set_a', 'set_b') # {1, 2, 3, 4} 并集
r.sdiff('set_a', 'set_b') # {1} 差集
8.7 Sorted Set 操作(有序集合)
python
# 添加(分数自动排序)
r.zadd('ranking', {'张三': 95, '李四': 88, '王五': 92})
# 升序获取(带分数)
r.zrange('ranking', 0, -1, withscores=True)
# [('李四', 88.0), ('王五', 92.0), ('张三', 95.0)]
# 降序获取(前三名)
r.zrevrange('ranking', 0, 2, withscores=True)
# 修改分数
r.zincrby('ranking', 5, '李四') # 李四 +5 分
# 获取排名(从0开始)
r.zrank('ranking', '张三') # 2(正序)
r.zrevrank('ranking', '张三') # 0(倒序)
8.8 键操作与过期时间
python
# 查看所有键
r.keys('*')
# 检查是否存在
r.exists('name') # 1 或 0
# 删除
r.delete('name')
# 设置过期时间(秒)
r.expire('session_token', 3600)
# 查看剩余时间(-1 永久,-2 已过期)
r.ttl('session_token')
# 设置值并指定过期
r.setex('captcha', 60, '123456')
# 取消过期(设为永久)
r.persist('session_token')
九、实战:Redis 缓存工具类
python
import json
import redis
class RedisUtils:
def __init__(self, host='127.0.0.1', port=6379, db=0):
self._client = redis.Redis(
host=host, port=port, db=db,
decode_responses=True
)
def set_cache(self, key, value, expire_time=3600):
"""写入缓存(支持任意 Python 对象)"""
try:
self._client.setex(key, expire_time, json.dumps(value))
return True
except Exception as e:
print(f"写入失败: {e}")
return False
def get_cache(self, key):
"""读取缓存,自动反序列化"""
try:
data = self._client.get(key)
return json.loads(data) if data else None
except Exception:
return None
def del_cache(self, key):
"""删除缓存"""
return self._client.delete(key) > 0
def clear_cache(self):
"""清空当前数据库(慎用)"""
return self._client.flushdb()
# 使用示例
cache = RedisUtils()
cache.set_cache('user:001', {'name': '老高', 'age': 36})
print(cache.get_cache('user:001')) # {'name': '老高', 'age': 36}
总结
| 层次 | 技术 | 适用场景 |
|---|---|---|
| SQL | MySQL DDL/DML/DQL | 结构化数据存储、复杂关联查询 |
| 连接 | PyMySQL | Python 程序操作 MySQL |
| 缓存 | Redis(String/Hash/List/Set/ZSet) | 高频读、会话管理、排行榜 |
| 工具 | RedisUtils 封装 | 统一缓存读写接口 |
一条主线贯穿全文:从「数据库设计」到「SQL 增删改查」,再到「Python 连接操作」,最终抵达「Redis 缓存加速」------ 这是数据存储从持久化到高性能的完整链路。