2022 面试记录
# 捷风数据
- (问答题)以任何语言打印一个矩阵,输入是矩阵的行数 rowNum 和矩阵的列数 colNum,这里保证输入的必然是自然数,且小于 Integer.MAX 要求在命令行打印一个如下的蛇形矩阵:
>>> 3*5
# 输出为:
1,6,7,12,13
2,5,8,11,14
3,4,9,10,15
>>> 2*2
# 输出为:
1, 4
2, 3
2
3
4
5
6
7
8
9
10
要求实现的人提供一个 JAR 包或者一个 python 文件, 在命令行使用
python test.py 34
java -jar xxxjar 3 4.
2
3
来打印一个 3*4 的矩阵
# 深光科技
- 索引原理,回表查询,b+树和 b 树区别?为什么不使用红黑树/二叉树,聚簇索引
- redis 使用,集群选举制度
Redis 集群中的选举制度如下:
Redis 集群采用的是一种主从复制的结构,其中有一个或多个主节点(master)负责接受和处理写操作,而其他节点(slave)则作为主节点的备份,只负责接受和处理读操作。在 Redis 集群中,主节点的选举是自动进行的。
当一个 Redis 主节点失效或下线时,Redis 集群会自动进行主节点的选举过程以重新选择一个合适的节点作为新的主节点。选举过程中使用的算法是 Raft 算法,它是一种一致性算法,用于在分布式系统中实现一致性和容错性。
具体来说,当主节点不可用时,Redis 集群中的其他节点会进行投票来选举新的主节点。选举过程包括三个阶段:选举提议、投票和承诺。在选举提议阶段,会有一个节点提出自己成为新的主节点并发送选举请求,其他节点会收到请求后进行投票。投票阶段,节点会通过比较各自的时间戳和优先级等信息来决定是否接受提议节点作为新的主节点。如果接受,则发送承诺消息。当超过一半的节点接受提议节点作为新的主节点时,选举过程成功完成,该节点将成为新的主节点。
总之,Redis 集群中的选举制度是通过 Raft 算法自动进行的,在主节点失效时进行选举以选择新的主节点来实现高可用性和容错性。
- WSGI 实现了什么?
WSGI(Web Server Gateway Interface)是 Python Web 应用程序和 Web 服务器之间的一种统一接口标准。它定义了 Web 服务器如何与 Python 应用程序进行通信,以便处理 HTTP 请求和响应。
通过实现 WSGI,Python Web 框架和 Web 服务器可以实现解耦,使得可以在不同的框架和服务器之间自由切换和组合。WSGI 接口规定了两个主要的组件:
应用程序:即 Python Web 应用程序,它是一个可调用的对象或函数,它接收一个字典作为参数,包含了 HTTP 请求的环境变量和其他相关信息,并返回一个包含 HTTP 响应的可迭代对象。
服务器:是一个符合 WSGI 协议的 Web 服务器,它负责接收 HTTP 请求,并将请求转发给应用程序进行处理,最后将应用程序返回的响应发送回客户端。
通过 WSGI 的实现,Web 服务器可以与各种不同的 Python Web 框架进行交互,例如 Django、Flask、Tornado 等,而且相同的应用程序也可以在不同的 Web 服务器上运行,提供更大的灵活性和可扩展性。
总结来说,WSGI 实现了 Python Web 应用程序与 Web 服务器之间的统一接口,使得不同的应用程序和服务器可以更加灵活地组合和切换,提高了 Python Web 开发的可移植性和可扩展性。
WSGI 规定,Web 程序必须有一个可调用对象,且该可调用对象接收两个参数,返回一个可迭代对象:
environ
:字典,包含请求的所有信息start_response
:在可调用对象中调用的函数,用来发起响应,参数包括状态码,headers 等
web.py、flask、django 技术选型为什么倾向于选用 flask,(面试官倾向于选择 Django)
事务
flask 运行机制
基类,在哪看到过?
脑裂是什么?
dict 的实现
celery 实际推荐使用 rabbitMQ,为什么?
redis 不是一个真正的消息队列,我们只是把 list 当作消息队列来使用,而 rabbitMQ 实现了消息队列中的更多功能。
具体来说:
- 可靠性:
- RabbitMQ 是一个可靠的消息队列系统,它使用 AMQP(Advanced Message Queuing Protocol)作为消息传递协议,提供了丰富的功能和机制来确保消息的可靠传递。它使用持久化存储消息,即使在 RabbitMQ 服务器重新启动后也能保证消息的不丢失。
- Redis 作为一个内存数据库,在消息代理方面相对简单,对于持久化和可靠性的支持较弱。在 Redis 的默认配置下,如果 Redis 服务器重启,未被消费的消息将会丢失,可能会影响系统的稳定性和可靠性。
- 灵活性和扩展性:
- RabbitMQ 提供了强大的消息路由和交换机机制,可以根据实际需求定制复杂的消息流程。它支持多种消息模式,如发布/订阅、工作队列、RPC 等,能够满足各种复杂的消息处理场景。
- Redis 在消息代理方面的功能相对简单,主要用作一个简单的消息队列,不如 RabbitMQ 的灵活性和扩展性。
- 可管理性:
- RabbitMQ 提供了丰富的管理工具和接口,可以很方便地监控、管理和配置消息队列系统。它提供了 Web 界面、CLI 工具和 RESTful API 等多种管理方式,使得运维人员可以更好地管理和监控系统。
- Redis 的管理功能相对较弱,虽然也提供了一些基本的管理命令和工具,但对于复杂的消息队列管理需求而言,可能较为不足。
综上所述,尽管 Redis 作为一种内存数据库也可以用作 Celery 的消息代理,但在实际生产中,由于 RabbitMQ 具有更强的可靠性、灵活性和可管理性,以及更适合复杂消息队列处理的特点,更推荐将 RabbitMQ 作为 Celery 的消息代理。
# 二面
- nginx 配置,字段含义
$host
,还有哪些关键字?
在 nginx 配置中,$host
是一个内置变量,它指代客户端请求中的 Host 头部字段的值。host 变量的值按照如下优先级获得:
- 请求行中的 host;
- 请求头中的 Host 头部;
- 与一条请求匹配的 server name
除了$host,nginx 还有许多其他的关键字和变量,其中一些常用的关键字包括:
1. $uri:请求的URI(不包括查询参数)
2. $args:请求的查询参数
3. $request_method:请求的HTTP方法,如GET、POST等
4. $remote_addr:客户端的IP地址
5. $server_name:当前服务器的名称
6. $request_uri:完整的请求URI(包括查询参数)
7. $http_user_agent:客户端的User-Agent头部字段的值
8. $http_referer:客户端的Referer头部字段的值
9. $http_cookie:客户端的Cookie头部字段的值
10. $http_x_forwarded_for:客户端的X-Forwarded-For头部字段的值
2
3
4
5
6
7
8
9
10
这些关键字和变量可以在 nginx 配置文件中的任何地方使用,例如在 location 块、if 语句中等,以便根据需要进行条件判断、重定向、日志记录等操作。
Nginx Cheatsheet (opens new window) Nginx 配置文件中的关键字是什么?详细解释来了-简易百科 (opens new window)
- iteritems,items 区别?
在 Python 2 中,iteritems()
和items()
是字典对象的两个方法,用于返回字典中的键值对。它们的区别在于返回的对象类型和性能。
iteritems()
方法返回一个迭代器对象,该迭代器逐个返回字典中的键值对。这种方法在处理大型字典时效率更高,因为它不会立即生成一个完整的列表,而是按需生成每个键值对。这在节省内存和提高性能方面非常有用。items()
方法返回一个包含所有键值对的列表。这种方法会立即生成并返回一个完整的列表对象。这在小型字典上使用效果更好,因为它可以一次性返回所有的键值对。# python2 >>> a = {'a':1,'b':2} >>> b = a.iteritems() >>> type(b) <type 'dictionary-itemiterator'> >>> next(b) ('a', 1)
1
2
3
4
5
6
7在 Python 3 中,
iteritems()
方法已被删除,只保留了items()
方法,它返回一个视图对象,可以用于遍历字典中的键值对。这种视图对象在遍历时动态地反映了字典的变化。In [1]: a = {'a':1,'b':2} In [2]: b = a.items() In [3]: type(b) Out[3]: dict_items In [4]: next(b) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-4-adb3e17b0219> in <module> ----> 1 next(b) TypeError: 'dict_items' object is not an iterator In [6]: c = iter(b) In [7]: next(c) Out[7]: ('a', 1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 多线程多进程编程?进程通信方式,queue 无法跳出,遇到过吗,如何解决的?
如果在进程通信中使用队列(queue)时无法跳出,可能是由于以下几个原因:
队列中没有数据:在使用队列进行进程通信时,如果发送方没有往队列中放入数据,接收方将会一直等待队列中有数据才能取出。因此,可以检查一下是否确保在发送方向队列中放入了数据。
队列中的数据无法被取出:在使用队列进行进程通信时,如果接收方无法取出队列中的数据,可能是由于接收方的代码逻辑有误。可以检查一下接收方的代码,确保正确地使用了队列的取出操作。
在使用 Queue 的时候,如果使用的默认的 get 后者 put 方法,(即(block=True, timeout=None)
)不管是多线程,还是多进程,在从队列中取出 url 的时候一定要加上判定条件,while queue.qsize()!=0
或者 while not queue.empty()
,不然进程或者线程会一直等待。
ref: Python 多进程使用 Queue 后卡死问题解决方案_python 进程池传 queue 卡死_kikilover 的博客-CSDN 博客 (opens new window)
数据库索引优化举例?
Python 底层加速
内存不足杀死进程如何调试?
埋点,打印 log、pdb 调试 当 Python 程序内存不足被杀死时,可以尝试以下调试方法:检查代码中是否存在内存泄漏的问题,例如未关闭文件、未释放资源等。使用内存分析工具(如
memory_profiler
)来确定程序中的内存使用情况,并找出可能导致内存泄漏的地方。使用
sys.settrace
函数设置一个跟踪函数,以便在程序运行期间跟踪内存使用情况。示例如下:
import sys def trace_memory(frame, event, arg): # 在这里记录内存使用情况 print(event, sys.getsizeof(arg)) sys.settrace(trace_memory) # 运行你的代码
1
2
3
4
5
6
7
8
9- 使用
guppy
库中的hpy
模块来检查程序中的内存使用情况。示例如下:
from guppy import hpy h = hpy() # 在需要检查内存使用情况的地方调用h.heap()方法 print(h.heap())
1
2
3
4
5
6尝试减少程序所需的内存使用量,例如使用生成器而不是列表来迭代大量数据、使用适当的数据结构等。
使用
resource
模块来设置进程的资源限制,例如设置虚拟内存限制、堆栈大小等。示例如下:
import resource # 设置虚拟内存限制为1GB resource.setrlimit(resource.RLIMIT_AS, (1e9, -1))
1
2
3
4- 在运行程序时使用
pympler.muppy
和pympler.summary
来获取内存快照和对象摘要信息。示例如下:
from pympler import muppy, summary all_objects = muppy.get_objects() sum1 = summary.summarize(all_objects) summary.print_(sum1)
1
2
3
4
5以上方法可以帮助你找到 Python 程序内存不足的问题所在,并进行相应的调试和优化。
索引无效的情况都有哪些?
索引无效的情况有以下几种:
索引列没有被包含在查询条件中:如果查询条件没有使用到索引列,那么索引就无效,查询会扫描全表而不是使用索引。
索引列上使用了函数或表达式:如果在索引列上使用了函数或表达式,那么索引就无效,查询会扫描全表而不是使用索引。
索引列上存在类型转换:如果在索引列上进行了类型转换,那么索引就无效,查询会扫描全表而不是使用索引。
索引列上存在大量重复值:如果索引列上存在大量重复值,那么索引的选择性就很低,查询优化器可能会选择全表扫描而不是使用索引。
索引列上数据分布不均匀:如果索引列上的数据分布不均匀,即某些值出现的频率很高,而其他值出现的频率很低,那么索引的选择性就很低,查询优化器可能会选择全表扫描而不是使用索引。
索引列上存在大量更新操作:如果索引列上存在大量的更新操作,那么频繁的更新操作可能会导致索引的维护成本过高,查询优化器可能会选择全表扫描而不是使用索引。
需要注意的是,以上情况并不是绝对的,查询优化器会根据具体情况来选择使用索引还是全表扫描。
线上 linux 查看问题点的命令?
netstat、top、lsof -i 、ps
各个 web 框架的区别或者优缺点?
Web.py:
- 简单、轻量级的框架,适合小型项目或快速原型开发。
- 没有内置的 ORM(对象关系映射)工具,需要手动处理数据库操作。
- 灵活性高,可以自由选择组件和库。
- 性能较好,因为它没有过多的抽象层。
Django:
- 全功能的框架,适合中大型项目开发。
- 内置了强大的 ORM 工具,简化了数据库操作。
- 提供了许多内置的功能模块和插件,如认证、管理后台等。
- 学习曲线较陡峭,配置复杂,但一旦熟悉,可以提高开发效率。
Flask:
- 简洁、灵活的框架,适合小型项目或快速原型开发。
- 与 Django 相比,学习曲线较平缓,配置相对简单。
- 没有内置的 ORM 工具,但可以与其他 ORM 库集成。
- 可扩展性强,可以根据项目需求选择需要的插件和库。
总结:
- Web.py 是一个轻量级的框架,适合小型项目或快速原型开发。
- Django 是一个全功能的框架,适合中大型项目开发。
- Flask 是一个简洁、灵活的框架,适合小型项目或快速原型开发。 选择适合自己项目需求的框架,可以提高开发效率和便利性。
# 集简慧通
- left join 与 right join
left join:顾名思义,就是“左连接”,表 1 左连接表 2,以左为主,表示以表 1 为主,关联上表 2 的数据,查出来的结果显示左边的所有数据,然后右边显示的是和左边有交集部分的数据。
right join:“右连接”,表 1 右连接表 2,以右为主,表示以表 2 为主,关联查询表 1 的数据,查出表 2 所有数据以及表 1 和表 2 有交集的数据。
join:其实就是“inner join”,为了简写才写成 join,两个是表示一个的,内连接,表示以两个表的交集为主,查出来是两个表有交集的部分,其余没有关联就不额外显示出来,这个用的情况也是挺多的。
- Django 的生命周期
Django 的生命周期指的是 Django 应用程序的运行过程中,各个组件的创建、初始化、处理请求、返回响应以及销毁的过程。下面是 Django 应用程序的典型生命周期:
启动 Django:在启动 Django 时,会加载项目的配置文件和各个应用程序的设置,并创建 Django 的应用程序实例。
URL 解析:当接收到一个 HTTP 请求时,Django 会根据 URL 配置文件中的规则来解析请求的 URL,找到匹配的视图函数或视图类。
视图处理:Django 调用匹配的视图函数或视图类来处理请求。在这个阶段,Django 可能会执行一些预处理的操作,例如身份验证、权限检查等,同时涉及到对 Model 进行处理。
模板渲染:如果视图函数或视图类需要使用模板来生成响应内容,Django 会根据指定的模板文件和上下文数据来渲染模板,并生成最终的响应内容。
响应返回:Django 将生成的响应内容返回给客户端,并关闭与客户端的连接。
清理资源:在请求处理完毕后,Django 会进行一些清理操作,例如关闭数据库连接、释放内存等。
销毁应用程序:当 Django 应用程序关闭时(例如服务器关闭或重启),Django 会执行一些最终的清理操作,并销毁应用程序实例。
需要注意的是,Django 的生命周期是循环的,即每次接收到一个 HTTP 请求,都会经历上述的生命周期过程。
- 静态方法和类方法的区别
静态方法使用@staticmethod 装饰器,不接受类或者示例参数,无法访问或修改实例或者类的属性和方法;类方法使用@classmethod 装饰,接受的第一个参数为 cls,可以访问类对象或属性,并进行修改。
- MySQL B+树索引和哈希索引的区别
B+树是一个平衡的多叉树,从根节点到每个叶子节点的高度差值不超过 1,而且同层级的节点间有指针相互链接。
在 B+树上的常规检索,从根节点到叶子节点的搜索效率基本相当,不会出现大幅波动,而且基于索引的顺序扫描时,也可以利用双向指针快速左右移动,效率非常高。
哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似 B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快。
如果是等值查询,那么哈希索引明显有绝对优势,因为只需要经过一次算法即可找到相应的键值;当然了,这个前提是,键值都是唯一的。如果键值不是唯一的,就需要先找到该键所在位置,然后再根据链表往后扫描,直到找到相应的数据;
从示意图中也能看到,如果是范围查询检索,哈希索引就毫无用武之地了,因为原先是有序的键值,经过哈希算法后,有可能变成不连续的了,就没办法再利用索引完成范围查询检索;
同理,哈希索引也没办法利用索引完成排序,以及 like "xxx%"
这样的部分模糊查询(这种部分模糊查询,其实本质上也是范围查询);
哈希索引也不支持多列联合索引的最左匹配规则;
B+树索引的关键字检索效率比较平均,不像 B 树那样波动幅度大,在有大量重复键值情况下,哈希索引的效率也是极低的,因为存在所谓的哈希碰撞问题。
MySQL 中的 B+树索引和哈希索引是两种常见的索引类型,它们在实现原理和适用场景上有一些区别。
实现原理:
- B+树索引:B+树是一种平衡树的数据结构,它将索引的键值按照一定的规则组织成树形结构,可以在 O(logN)的时间复杂度内进行查找、插入和删除操作。
- 哈希索引:哈希索引使用哈希函数将索引的键值映射为一个固定长度的哈希值,然后将哈希值作为索引进行查找。哈希索引的查找速度非常快,通常可以在 O(1)的时间复杂度内完成。
适用场景:
- B+树索引:适用于范围查询、排序和分组等操作,可以高效地支持任意范围的查找。B+树索引适合于有序的、有重复值的数据集。
- 哈希索引:适用于等值查询,即通过精确的条件查找数据。哈希索引不支持范围查询,因为哈希函数的特性决定了哈希索引中的键值是无序的。
存储空间:
- B+树索引:B+树索引需要占用一定的存储空间来存储索引的节点和键值,随着数据量的增加,索引的大小也会增加。
- 哈希索引:哈希索引需要占用较小的存储空间,因为哈希值的长度是固定的,不随数据量的增加而增加。
内存使用:
- B+树索引:B+树索引可以利用操作系统的缓存机制,将热点数据存储在内存中,提高查询性能。
- 哈希索引:哈希索引需要将全部索引数据加载到内存中,如果数据量过大,可能会导致内存不足的问题。
综上所述,B+树索引适用于范围查询和有序数据集,而哈希索引适用于等值查询和较小的数据集。在选择索引类型时,需要根据具体的业务需求和数据特点进行选择。
- 主键索引和唯一索引的区别
- 主键是一种约束,唯一索引是一种索引,两者在本质上是不同的。
- 主键创建后一定包含一个唯一性索引,唯一性索引并不一定就是主键。
- 唯一性索引列允许空值,而主键列不允许为空值。
- 主键可以被其他表引用为外键,而唯一索引不能。
- 一个表最多只能创建一个主键,但可以创建多个唯一索引。
- 主键索引和普通索引的区别
主键索引和普通索引是数据库中常见的两种索引类型,它们的区别主要体现在以下几个方面:
唯一性:主键索引要求索引列的值是唯一的,即每个索引值只能对应一条记录,而普通索引则允许索引列的值重复。
空值:主键索引不允许存在空值,即索引列的值不能为空,而普通索引可以包含空值。
表现形式:主键索引可以是聚集索引(clustered index)或非聚集索引(non-clustered index),而普通索引只能是非聚集索引。
存储方式:主键索引的存储方式与数据行的存储方式相同,即主键索引和数据行存储在同一位置,而普通索引则是独立存储的。在 MySQL 中,主键索引的叶子节点存储的是整个数据行的值,而普通索引的叶子节点存储的是索引字段的值和对应的主键值。
查询性能:主键索引的查询性能通常比普通索引更高,因为主键索引的唯一性和聚集性可以减少磁盘 I/O 的次数,提高查询效率。
总的来说,主键索引是一种特殊的索引类型,用于唯一标识一条记录,具有唯一性和非空性的特点;而普通索引是一种常见的索引类型,用于提高查询性能,可以包含重复值和空值。
__new__
和__init__
区别
- Python 传值还是传引用
- Python 是否支持重载
不支持,但是也不影响。因为 Python 可以处理这种问题。 python-中重载
- 闭包
- 迭代器与生成器
- 下划线和双下划线的区别
Python 中没有真正的私有属性,但是可以用这种方式来表示私有;但是单下划线可以强制导入,而双下划线只能通过_ClassName__var
来获取。
# 龙创悦动
- token 的工作机制
Token 的工作机制可以分为两个方面:身份验证和访问控制。
首先,在身份验证方面,Token 用于验证用户的身份。当用户登录到一个应用程序时,服务器会生成一个 Token 并将其返回给用户。这个 Token 通常是一个加密的字符串,其中包含了用户的身份信息,如用户名、角色等。用户在后续的请求中,需要将这个 Token 发送给服务器进行身份验证。服务器会验证 Token 的合法性,并根据其中的信息判断用户是否有权限访问请求的资源。
其次,在访问控制方面,Token 用于控制用户对资源的访问权限。服务器在验证 Token 的合法性后,会根据其中的信息判断用户是否有权限访问请求的资源。例如,某些资源可能只允许管理员角色的用户访问,而其他角色的用户则无法访问。服务器会根据 Token 中的角色信息进行权限判断,并返回相应的结果。
Token 的工作机制通常涉及到加密算法和密钥的使用,以保证 Token 的安全性。服务器会使用密钥对 Token 进行加密和解密,以防止 Token 被篡改或伪造。同时,服务器也会定期更换密钥,以增加 Token 的安全性。
基于 Token 的身份验证的过程如下:
用户通过用户名和密码发送请求。
程序验证。
程序返回一个签名的 token 给客户端。
客户端储存 token,并且每次用于每次发送请求。
服务端验证 token 并返回数据。
索引、事务/innoDB 和 MyISAM 的对比
事务:一个最小的不可再分的工作单元
- web 框架的对比
# 大地量子
- range 返回的对象类型
Python3 range()
函数返回的是一个可迭代对象(类型是对象),而不是列表类型, 所以打印的时候不会打印列表。
Python3 list()
函数是对象迭代器,可以把range()
返回的可迭代对象转为一个列表,返回的变量类型为列表。
python2 中xrange
返回惰性可迭代对象,可以遍历而不消耗,类型为 range
对象,而不是迭代器,无法使用next
取值!有len
方法
- python 版本问题
- 一行打印乘法口诀表
print('\n'.join(['\t'.join(f'{j} * {i} = {i*j}' for j in range(1,i+1)) for i in range(1,10)]))
列表生成式
聚集索引的概念,每页数据为什么是 16k,可以是 32k 吗?可以使用 uuid 作为聚簇索引的主键吗?
聚集索引是一种特殊的数据库索引,在聚集索引中,数据按照索引的顺序存储,因此聚集索引决定了数据在磁盘上的物理排列方式。
每页数据 16k 是因为数据库管理系统(DBMS)在处理数据时,将数据分成固定大小的块,称为页面(page)。16k 是一种常见的页面大小,它是经过优化和平衡后的一个折衷选择。较小的页面大小可能会导致额外的磁盘 IO 操作,而较大的页面大小可能会浪费存储空间。因此,16k 被广泛应用于许多数据库系统。
虽然理论上可以使用 32k 作为页面大小,但是这种情况很少见。较大的页面大小可能会导致内存利用率下降,增加 IO 操作的开销,并且可能导致数据碎片化。
提示
可以把页面大小的设计类比成一本书的纸张大小。页面太大,会降低书本的便携性;而页面太小,则会导致每页存储的有效信息太少,所以常见书本以 16 开或 32 开为主。
可以使用 UUID 作为聚簇索引的主键,但是通常不建议这样做。UUID 是一个全局唯一的标识符,它的值是随机生成的。将 UUID 作为聚簇索引的主键会导致数据在磁盘上的随机分布,增加了插入和更新操作的开销,并且可能导致数据碎片化。在大规模的数据库系统中,通常会选择使用自增主键作为聚簇索引的主键,以提高性能和减少碎片化的可能性。
增加 page_size
的大小:如改为 32k,64k。数据库的页块大小改变需要改动源码。
- 聚集索引的顺序问题
聚集索引不是物理上连续的,而是逻辑上连续的。(面试官怎么问这个问题的记不住了,好像是说先插入主键为 1,3,4,之后插入 2 是否可以插入?)
聚集索引是按照索引列的值进行物理排序的,它决定了数据在磁盘上的存储顺序。当你插入主键 id 为 1、2、4 的数据后,聚集索引会按照主键 id 的值进行排序,数据会按照 1、2、4 的顺序存储在磁盘上。
因此,你可以在插入主键 id 为 3 的数据,数据库会根据聚集索引的顺序将其插入到正确的位置,使得数据仍然保持有序。
在MySQL中,无论是按照顺序插入还是非顺序插入,只要主键是唯一的,就可以成功插入。但在实际应用中,这两种插入方式可能会有以下区别:
插入效率:如果是InnoDB存储引擎,主键是聚簇索引,数据记录是按照主键顺序存储的。如果按照主键顺序插入,数据页分裂的可能性会小一些,插入效率可能会高一些。如果是非顺序插入,可能会引起更多的数据页分裂,插入效率可能会低一些。
数据查询效率:如果数据是按照主键顺序插入的,查询效率可能会更高,因为数据物理存储的顺序和主键的顺序一致,可以更好地利用磁盘的顺序读取特性。
空间使用:如果是非顺序插入,可能会导致数据页的利用率不高,占用更多的存储空间。
但这些区别在实际应用中可能并不明显,具体情况还需要根据实际的数据量、数据分布、查询模式等因素来考虑。
但是这种顺序插入,可能会导致页分裂问题!
页分裂
页分裂是指当一个数据页存储的数据满时,需要将其分裂成两个数据页来存储更多的数据。 在数据库中,数据是按照数据页的形式存储的,每页可以存储 16KB 的数据(默认)。当一页数据满了之后,如果需要继续存储更多的数据,就会导致数据页的分裂。页分裂会导致索引树的混乱,使得一部分数据在老页面,一部分在新的页面,并且新页面可能被分配到任何可用的页,这样就会影响数据库查询的性能。
- 事务四大特性,之间是否存在关联?
事务四大特性是指原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。
这四个特性之间存在一定的关联。
原子性和一致性:原子性指事务中的操作要么全部成功执行,要么全部不执行,不会出现部分执行的情况。一致性指事务执行前后数据库的状态要保持一致。原子性和一致性是相互关联的,如果事务中的操作是原子性的,即要么全部成功执行,要么全部不执行,那么事务执行前后数据库的状态就能保持一致。
隔离性和一致性:隔离性指并发执行的事务之间是相互隔离的,每个事务都感觉不到其他事务的存在。一致性指事务执行前后数据库的状态要保持一致。隔离性和一致性是相互关联的,如果事务之间是相互隔离的,互不干扰,那么事务执行前后数据库的状态就能保持一致。
持久性和一致性:持久性指事务一旦提交,其结果就是永久性的,即使系统发生故障也不会丢失。一致性指事务执行前后数据库的状态要保持一致。持久性和一致性是相互关联的,如果事务一旦提交,其结果是永久性的,即使系统发生故障,也能保持事务执行前后数据库的状态一致。
综上所述,事务四大特性之间存在关联,彼此相互支持和保证数据库的一致性。
- innoDB 和 MyISAM 的对比
InnoDB 引擎的四大特性是:
事务支持:InnoDB 引擎支持 ACID(原子性、一致性、隔离性和持久性)事务,可以确保数据的完整性和一致性。它支持提交(commit)和回滚(rollback)操作,可以保证在多个并发事务处理过程中的数据一致性。
行级锁定:InnoDB 引擎支持行级锁定,这意味着在同一时间可以对不同的行进行并发操作,提高了并发性能。与其他引擎如 MyISAM 相比,InnoDB 引擎的行级锁定能够减少锁冲突,提高并发性能。
外键约束:InnoDB 引擎支持外键约束,可以定义表与表之间的关系,保证数据的引用完整性。外键约束可以自动检查和维护数据的完整性,避免了数据不一致的问题。
高可靠性及快速恢复数据:InnoDB 引擎具有高可靠性,支持数据的持久性存储和恢复。它使用 redo log 和 undo log 来记录和恢复数据的更改,可以在系统崩溃或断电后恢复数据的一致性。此外,InnoDB 引擎还支持自动崩溃恢复和自动故障切换,提高了数据库的稳定性和可靠性。
生成器取元素的方式,除了 next 还有哪些?
除了使用 next
方法取出生成器的下一个元素之外,还可以使用以下方法:
- 使用
for
循环:可以直接在for
循环中迭代生成器,自动调用next
方法取出元素,直到生成器耗尽或达到停止条件。
for item in generator:
# 处理元素
2
- 使用
list
函数:可以将生成器转换为列表,通过一次性调用list
函数获取生成器的所有元素。
elements = list(generator)
- 使用
iter
函数:可以将生成器转换为迭代器对象,然后使用iter
函数的__next__
方法取出元素。
iterator = iter(generator)
element = iterator.__next__()
2
- 使用
*
运算符:可以将生成器的所有元素解包到一个列表中。
elements = [*generator]
需要注意的是,生成器是一种惰性计算的机制,只有在需要时才会生成元素。因此,每次取出元素时都会从生成器中消耗一个元素,直到生成器耗尽。如果生成器已经耗尽,再次调用 next
方法会引发 StopIteration
异常。
- 新式类和经典类
# py2
class Py2Old:
pass
class Py2New(object):
pass
#py3
class Py3New:
pass
2
3
4
5
6
7
8
9
10
- 垃圾回收机制
gc.get_threshold()
(700, 10, 10)
2
700 代表新创建的对象减去从新创建的对象中回收的数量的差值大于 700 就进行一次 0 代回收当 0 代回收进行 10 次的时候就进行一代回收(并且一代回收的时候也进行 0 代回收),同理,当一代回收进行 10 次的时候就进行 2 代回收(并且二代回收的时候也进行 0 代回收和一代回收)
# 众趣科技
三次握手,四次挥手
索引的原理
顺序访问指针,所以查询快
怎么分析慢查询?
explain 查询是否为全表扫描,是的话加索引
最左匹配原则?
MySQL 索引的最左匹配原则是指,在使用联合索引时,可以利用索引的最左边的列来进行查询,但是如果查询中没有使用到索引的最左边的列,那么该索引将不会被使用。
举个例子,假设有一个联合索引(col1, col2, col3),如果查询语句中只使用了 col2 和 col3 进行筛选,而没有使用 col1,那么该索引将不会被使用。因为 MySQL 的索引是按照索引列的顺序来存储和搜索的,如果查询中没有使用到索引的最左边的列,那么 MySQL 无法利用该索引进行快速搜索。
需要注意的是,最左匹配原则并不是说只能使用联合索引的最左边的列进行查询,而是指如果查询语句中没有使用到索引的最左边的列,那么该索引将不会被使用。也就是说,如果查询中使用了索引的最左边的列,那么 MySQL 可以利用该索引进行搜索,但是如果查询中没有使用到索引的最左边的列,那么该索引将不会被使用。
最左匹配原则在设计索引时非常重要,可以根据查询的特点来选择合适的索引列顺序,以提高查询性能。
乐观锁、悲观锁?
乐观锁和悲观锁是数据库中常用的并发控制机制。
乐观锁(Optimistic Locking)是一种乐观的并发控制策略。在乐观锁机制中,假设不会发生并发冲突,因此在读取数据的时候不会加锁,只有在更新数据的时候才会检查是否有其他事务修改了数据。乐观锁通常使用版本号或时间戳来实现。当一个事务要更新数据时,会先读取数据并记录版本号或时间戳,然后在更新时检查是否有其他事务修改了数据,如果有则回滚操作,重新读取数据并重新尝试更新。
悲观锁(Pessimistic Locking)是一种悲观的并发控制策略。在悲观锁机制中,假设会发生并发冲突,因此在读取数据的时候会加锁,确保其他事务无法修改数据。悲观锁通常使用行级锁或表级锁来实现。当一个事务要读取数据时,会先加锁,其他事务无法修改数据,直到该事务释放锁。这种机制可以防止并发冲突,但也会导致并发性能下降。
乐观锁适用于读操作较多的场景,因为读操作不会加锁,可以提高并发性能。悲观锁适用于写操作较多的场景,因为写操作需要加锁保证数据的一致性。选择乐观锁还是悲观锁取决于具体的业务需求和并发情况。
当涉及到实现乐观锁和悲观锁时,具体的实现方式会根据数据库的类型和使用的框架而有所不同。以下是一个使用 Python 和 MySQL 数据库的示例代码:
- 乐观锁的实现:
import pymysql def update_data(id, new_value, version): conn = pymysql.connect(host='localhost', user='root', password='password', db='test') cursor = conn.cursor() try: # 表中需要有版本号字段 cursor.execute("SELECT value, version FROM data WHERE id = %s", (id,)) row = cursor.fetchone() if row: current_value, current_version = row if current_version == version: cursor.execute("UPDATE data SET value = %s, version = version + 1 WHERE id = %s", (new_value, id)) conn.commit() print("Update successful!") else: print("Conflict detected! Retry the operation.") else: print("Data not found!") except Exception as e: print("Error:", e) conn.rollback() finally: cursor.close() conn.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26在上述代码中,首先通过
SELECT
语句获取要更新的数据的当前值和版本号。然后,检查当前版本号是否与传入的版本号相同。如果相同,则执行UPDATE
语句更新数据并增加版本号。如果版本号不相同,则说明有其他事务修改了数据,需要重新尝试更新。- 悲观锁的实现:
import pymysql def update_data(id, new_value): conn = pymysql.connect(host='localhost', user='root', password='password', db='test') cursor = conn.cursor() try: cursor.execute("SELECT * FROM data WHERE id = %s FOR UPDATE", (id,)) row = cursor.fetchone() if row: cursor.execute("UPDATE data SET value = %s WHERE id = %s", (new_value, id)) conn.commit() print("Update successful!") else: print("Data not found!") except Exception as e: print("Error:", e) conn.rollback() finally: cursor.close() conn.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21在上述代码中,通过
SELECT ... FOR UPDATE
语句获取要更新的数据并加锁,确保其他事务无法修改数据。然后,执行UPDATE
语句更新数据,并提交事务。需要注意的是,上述代码仅为示例,实际实现中需要根据具体的业务需求和数据库类型进行相应的调整。
深拷贝,浅拷贝?
gRPC 工作原理?
多态解释一下?
A 类中的 walk 和 b 类中的 walk 方法可以接受不同类型的参数,但执行的逻辑不一样。 面向对象之封装、继承、多态 | 别院牧志知识库
WSGI 与 ASGI
什么是 WSGI
先说一下CGI
,(通用网关接口, Common Gateway Interface/CGI),定义客户端与 Web 服务器的交流方式的一个程序。例如正常情况下客户端发来一个请求,根据HTTP
协议 Web 服务器将请求内容解析出来,经过计算后,再将内容封装好,例如服务器返回一个HTML
页面,并且根据HTTP
协议构建返回内容的响应格式。涉及到TCP
连接、HTTP
原始请求和相应格式的这些,都由一个软件来完成,这时,以上的工作需要一个程序来完成,而这个程序便是CGI
。
那什么是WSGI
呢?维基 (opens new window) 上的解释为,Web 服务器网关接口(Python Web Server Gateway Interface,WSGI),是为Python
语言定义的 Web 服务器和 Web 应用程序或框架之间的一种简单而通用的接口。从语义上理解,貌似WSGI
就是Python
为了解决Web 服务器端与客户端之间的通信问题而产生的,并且WSGI
是基于现存的CGI
标准而设计的,同样是一种程序(或者Web
组件的接口规范)。
WSGI (opens new window) 区分为两部分:一种为“服务器”或“网关”,另一种为“应用程序”或“应用框架”。
所谓的WSGI
中间件同时实现了API
的两方,即在WSGI
服务器和WSGI
应用之间起调解作用:从WSGI
服务器的角度来说,中间件扮演应用程序,而从应用程序的角度来说,中间件扮演服务器。中间件具有的功能:
- 重写环境变量后,根据目标 URL,将请求消息路由到不同的应用对象。
- 允许在一个进程中同时运行多个应用程序或应用框架。
- 负载均衡和远程处理,通过在网络上转发请求和相应消息。
- 进行内容后处理,例如应用
XSLT
样式表。
可以说WSGI
就是基于Python
的以CGI
为标准做一些扩展。
异步网关协议接口,一个介于网络协议服务和Python
应用之间的标准接口,能够处理多种通用的协议类型,包括HTTP
,HTTP2
和WebSocket
。
然而目前的常用的WSGI
主要是针对HTTP
风格的请求响应模型做的设计,并且越来越多的不遵循这种模式的协议逐渐成为Web
编程的标准之一,例如WebSocket
。
ASGI
尝试保持在一个简单的应用接口的前提下,提供允许数据能够在任意的时候、被任意应用进程发送和接受的抽象。并且同样描述了一个新的,兼容HTTP
请求响应以及WebSocket
数据帧的序列格式。允许这些协议能通过网络或本地socket
进行传输,以及让不同的协议被分配到不同的进程中。
- WSGI 和 ASGI 的区别在哪
以上,WSGI
是基于HTTP
协议模式的,不支持WebSocket
,而ASGI
的诞生则是为了解决Python
常用的WSGI
不支持当前Web
开发中的一些新的协议标准。同时,ASGI
对于WSGI
原有的模式的支持和WebSocket
的扩展,即ASGI
是WSGI
的扩展。
ref:WSGI&ASGI - 简书 (opens new window)
redis 缓存热点数据,哪些数据属于热点数据
Redis 的过期策略有两种:定期删除和惰性删除。
定期删除: Redis 默认使用的是定期删除策略。它会以一定的频率(默认每秒钟 10 次)随机抽取一部分设置了过期时间的 key,并检查它们是否过期,如果过期则删除。这种策略的优点是简单高效,缺点是可能会造成大量过期 key 未被及时删除,占用过多内存。
惰性删除: 惰性删除是指在获取某个 key 时,Redis 会先检查该 key 是否过期,如果过期则删除。这种策略的优点是能够及时删除过期 key,节省内存空间,缺点是在获取过期 key 时会有一定的性能损耗。
Redis 还提供了一些配置参数来调整过期策略的行为,包括:
maxmemory-policy:指定内存达到上限时的淘汰策略,默认是 noeviction,表示不淘汰数据,直接返回错误。
redis的淘汰策略:默认是 noeviction noeviction:当内存使用达到阈值的时候,所有引起申请内存的命令会报错; allkeys-lru:尝试回收,最近未使用或者使用比较少的键。(范围是:所有的键) volatile-lru:尝试回收,最近未使用或者使用比较少的键。(范围是:设置了过期时间的键) allkeys-random:随机移除某个key。(范围是:所有的键) volatile-random:随机移除某个key。(范围是:设置了过期时间的键) volatile-ttl:回收过期时间较短的key。(范围是:设置了过期时间的键)
1
2
3
4
5
6
7maxmemory-samples:指定定期删除策略每次抽取的 key 数量,默认是 5。
lazyfree-lazy-eviction:是否开启惰性删除策略,默认是 no,表示关闭。
根据实际需求,可以根据以上配置参数来调整 Redis 的过期策略。
# 其他
已经有了 GIL 多线程为什么还要加锁?
尽管 GIL(全局解释器锁)可以确保在同一时间只有一个线程在 Python 解释器中执行,但在多线程环境中,由于线程的切换和竞争条件的存在,仍然需要使用锁来保护共享资源的访问。
以下是为什么在使用 GIL 的情况下仍然需要加锁的几个原因:
保护共享资源:在多线程环境中,多个线程可能同时访问和修改共享资源,如全局变量、共享数据结构等。为了避免数据的不一致性和竞争条件,需要使用锁来保护共享资源,确保在任何时刻只能有一个线程访问或修改它。
原子操作:某些操作需要保证原子性,即在多线程环境中不会被中断。例如,对于一个整型变量的自增操作,在多线程环境中可能出现竞争条件,导致结果不准确。通过使用锁可以确保这样的操作在同一时刻只能由一个线程执行,保证结果的正确性。
避免数据竞争:数据竞争指的是多个线程同时访问和修改同一块内存区域,导致不可预测的结果。GIL 只能保证在同一时间只有一个线程在解释器中执行,但不能保证线程之间的执行顺序。使用锁可以控制线程的执行顺序,避免数据竞争的问题。
需要注意的是,GIL 只是针对 Python 解释器级别的多线程,对于涉及到 I/O 操作、调用 C 扩展等情况,GIL 会主动释放,允许其他线程执行。因此,在这些情况下,使用锁仍然是必要的。
float
相加精度问题在计算机中,浮点数的精度问题是由于浮点数的二进制表示和十进制表示之间的转换造成的。由于浮点数的二进制表示是有限的,所以在进行浮点数的加法运算时,可能会出现精度丢失的情况。
例如,考虑以下代码:
a = 0.1 b = 0.2 c = a + b print(c)
1
2
3
4预期输出应该是 0.3,但实际上输出是 0.30000000000000004。
这是因为 0.1 和 0.2 在二进制表示中是无限循环的,而计算机的浮点数表示只能存储有限的位数,所以在进行加法运算时会出现舍入误差,导致最终的结果不准确。
为了解决这个问题,可以使用 Decimal 模块进行精确的浮点数计算。例如:
from decimal import Decimal a = Decimal('0.1') b = Decimal('0.2') c = a + b print(c)
1
2
3
4
5
6这样就可以得到精确的结果 0.3。
另外,还可以使用 round 函数来对浮点数进行四舍五入处理,以减少精度误差。例如:
a = 0.1 b = 0.2 c = round(a + b, 1) print(c)
1
2
3
4这样就可以得到结果 0.3。 参阅 聊一聊 0.1+0.2=0.30000000000000004 这件事 (opens new window)
为什么协程比线程快?切换为什么快?
协程比线程快的原因主要有两点:
协程的切换是用户级的,而线程的切换是内核级的。线程的切换需要操作系统的介入,涉及到用户态和内核态之间的切换,需要保存和恢复线程的上下文,开销较大。而协程的切换是在用户代码中完成的,不需要操作系统的介入,切换的开销较小。
协程的切换是协作式的,而线程的切换是抢占式的。线程的切换是由操作系统的调度器决定的,可能在任何时候发生。而协程的切换是由程序员显式地控制的,只会在协程主动让出执行权时发生。这种协作式的切换可以更好地控制资源的使用和调度,避免了线程切换时的竞争和冲突。
总的来说,协程的切换快是因为它的切换开销小且可控。这使得协程在处理大量的轻量级任务时更加高效,并且可以更好地利用计算资源。
futures
模块- 多进程里面的单进程和单进程消耗的资源哪个更多还是一样多?
- 设计模式
- redis 缓存使用?io 模型?多路复用解释一下?数据结构内部实现?
Redis 缓存使用:
- 将常用的数据存储在 Redis 中,通过缓存提高读取数据的速度。
- 在查询数据时,先从 Redis 中查询,如果存在则直接返回,如果不存在再从数据库中查询,并将查询结果存储到 Redis 中,方便下次查询。
- 在更新数据时,先更新数据库中的数据,再删除 Redis 中的缓存,确保数据的一致性。
Redis 的 I/O 模型是使用了多路复用来实现非阻塞 I/O。
Redis 可以使用多种 I/O 模型,包括 epoll、select、kqueue 等。在 Linux 系统上,Redis 默认使用 epoll 作为其 I/O 模型。
通过多路复用,Redis 可以同时监听多个文件描述符(sockets),当某个文件描述符有事件发生时,Redis 会将其加入到一个就绪队列中,然后通过事件处理器来处理就绪的文件描述符。
在每个事件循环中,Redis 会通过事件处理器来处理就绪的文件描述符,执行相应的操作。这样可以在不阻塞的情况下处理多个连接,提高了 Redis 的并发性能。
总的来说,Redis 的 I/O 模型使用多路复用来实现非阻塞 I/O,从而提高了并发性能。
多路复用: 多路复用是指通过一种机制,使得一个进程可以同时监听多个文件描述符,一旦某个文件描述符就绪(可读、可写或异常等事件就绪),就能够通知应用程序进行相应的操作。
多路复用的实现方式有多种,常见的有:
- select:通过 select 系统调用来监听多个文件描述符,一旦有就绪的文件描述符,就返回并通知应用程序进行相应操作。
- poll:与 select 类似,通过 poll 系统调用来监听多个文件描述符。
- epoll:是 Linux 特有的多路复用机制,通过 epoll 系统调用来监听多个文件描述符,性能更好。
数据结构内部实现: Redis 内部使用了一些数据结构来存储数据,主要包括字符串、哈希表、列表、集合和有序集合。
- 字符串:Redis 的字符串是动态字符串,通过 SDS(Simple Dynamic String)实现,支持动态扩容和惰性释放内存。
- 哈希表:Redis 的哈希表是字典,使用链地址法解决哈希冲突,支持动态扩容,底层实现为数组+链表/跳表。
- 列表:Redis 的列表是双向链表,支持在表头和表尾进行操作,底层实现为双向链表。
- 集合:Redis 的集合是无序的字符串集合,底层使用哈希表实现。
- 有序集合:Redis 的有序集合是有序的字符串集合,底层使用跳表和哈希表实现,通过跳表实现有序性,通过哈希表实现快速查找。
这些数据结构的内部实现都使用了一些优化技巧,如压缩列表、跳表等,来提高性能和减少内存占用。