Python面试题库-持续更新中
问题描述
- Python3里面的GIL锁是什么,怎么避免这个问题?
- Mysql索引,Django的model层如何加索引,怎么运用到mysql中呢?
- 索引为什么快?是怎么实现的?
题解分析:
题解1:Python3里面的GIL锁是什么,怎么避免这个问题?
是什么: Python中的GIL(Global Interpreter Lock,全局解释器锁)是CPython解释器(Python的默认实现)中的一个机制,它确保同一时刻只有一个线程执行Python字节码。GIL的设计初衷是为了简化CPython的内存管理(如引用计数)并避免多线程竞争问题,但它也导致多线程程序在CPU密集型任务中无法充分利用多核CPU。
怎么避免:
- 使用多进程替代多线程(不推荐,多进程不仅占用内存多,而且复杂)
- 使用Numpy等高效库:其底层C实现会释放GIL,多线程处理数组时可能并行。
- 分布式任务队列(如Celery):将任务分发到多台机器执行。
- 使用C扩展绕过GIL(干一些不需要操作python对象的 数据计算的活)
题解2:Mysql索引,Django的model层如何加索引,怎么运用到mysql中呢?
代码示例:
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
email = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
# 单字段索引
indexes = [
models.Index(fields=['name']),
]
# 联合索引(多字段)
indexes = [
models.Index(fields=['name', 'email']),
]
# 唯一索引
indexes = [
models.Index(fields=['email'], name='unique_email_idx', unique=True),
]
如何应用到Django
python manage.py makemigrations # 生成迁移脚本
python manage.py migrate
引申知识
- 选择合适的字段
高频查询字段: WHERE、JOIN、ORDER BY、GROUP BY 涉及的字段。
高区分度字段: 字段值重复率低(如用户ID、邮箱)。
避免冗余索引: 联合索引 (A,B) 可替代单独的 (A) 索引。
- 索引类型选择
索引类型 | 适用场景 |
---|---|
普通索引(INDEX) | 常规查询优化 |
唯一索引(UNIQUE) | 确保字段唯一性(如邮箱、手机号) |
全文索引(FULLTEXT) | 文本内容搜索(需使用MyISAM或InnoDB) |
联合索引 | 多条件组合查询 |
题解3:索引为什么快?是怎么实现的?
索引之所以能够大幅提升数据库查询速度,其核心原理在于通过高效的数据结构减少数据检索的扫描范围。
一、索引的底层数据结构
B+树:关系型数据库的核心结构
- 树形分层:B+树是多层平衡树(通常3~4层),每层存储索引键值和子节点指针。
- 有序性:所有叶子节点按索引键值排序,形成双向链表,支持高效范围查询。
示例:假设一个B+树高度为3,每页(16KB)存储1000个键值:
根节点(1页) → 1000个子节点
中间层(1000页) → 1000×1000=1,000,000个子节点
叶子层(1,000,000页) → 存储实际数据或数据指针
只需3次磁盘I/O即可定位到10亿级数据中的某一行。
- 哈希索引:精确匹配的极速方案
- O(1)时间复杂度: 通过哈希函数直接定位数据位置。
- 局限性: 不支持范围查询,仅适用于等值查询(如WHERE id = 123)。
索引加速查询的三大机制
- 减少磁盘I/O次数
- **全表扫描:**若表有1亿行,需读取所有数据页(假设每页100行,需100万次I/O)。
- **索引扫描:**通过B+树定位到目标数据,仅需3~4次I/O(树高决定)。
- 有序性优化范围查询
-- 范围查询示例:查找2023年的订单
SELECT * FROM orders WHERE order_date BETWEEN '2023-01-01' AND '2023-12-31';
- 无索引: 逐行扫描所有记录。
- 有索引: 定位到2023-01-01的叶子节点,沿双向链表向后遍历至2023-12-31。
- 覆盖索引(Covering Index)
- 无需回表:若索引包含查询所需的所有字段,直接返回索引数据。
-- 假设索引为 (user_id, name)
SELECT name FROM users WHERE user_id = 100;
-- 直接从索引叶子节点读取name,无需访问数据行。
索引的代价
- 写操作成本
插入/更新/删除: 需维护B+树结构,可能触发节点分裂或合并。
示例:插入一条新记录到中间位置,可能导致叶子节点分裂为两页。
- 空间占用
索引大小:一个联合索引(a,b,c)占用的空间可能超过原数据表。