Python自学Day36-38 关系型数据库MySQL

关系数据库入门

关系数据库概述

  • 数据持久化 – 将数据保存到能够长久保存数据的存储介质中,在掉电的情况下数据也不会丢失。
  • 数据库发展史 – 网状数据库、层次数据库、关系数据库、NoSQL数据库。
  • 关系数据库特点。
    • 理论基础:集合论和关系代数。
    • 具体表象:用二维表(有行和列)组织数据。
    • 编程语言:结构化查询语言(SQL)。
  • ER模型(实体关系模型)和概念模型图。

ER模型,全称为实体关系模型(Entity-Relationship Model),由美籍华裔计算机科学家陈品山先生提出,是概念数据模型的高层描述方式,如下图所示。

Python自学Day36-38 关系型数据库MySQL

    • 实体 – 矩形框
    • 属性 – 椭圆框
    • 关系 – 菱形框
    • 重数 – 1:1(一对一) / 1:N(一对多) / M:N(多对多)

实际项目开发中,我们可以利用数据库建模工具(如:PowerDesigner)来绘制概念数据模型(其本质就是ER模型),然后再设置好目标数据库系统,将概念模型转换成物理模型,最终生成创建二维表的SQL(很多工具都可以根据我们设计的物理模型图以及设定的目标数据库来导出SQL或直接生成数据表)。

  • 关系数据库产品。
    • Oracle – 目前世界上使用最为广泛的数据库管理系统,作为一个通用的数据库系统,它具有完整的数据管理功能;作为一个关系数据库,它是一个完备关系的产品;作为分布式数据库,它实现了分布式处理的功能。在Oracle最新的12c版本中,还引入了多承租方架构,使用该架构可轻松部署和管理数据库云。
    • DB2 – IBM公司开发的、主要运行于Unix(包括IBM自家的AIX)、Linux、以及Windows服务器版等系统的关系数据库产品。DB2历史悠久且被认为是最早使用SQL的数据库产品,它拥有较为强大的商业智能功能。
    • SQL Server – 由Microsoft开发和推广的关系型数据库产品,最初适用于中小企业的数据管理,但是近年来它的应用范围有所扩展,部分大企业甚至是跨国公司也开始基于它来构建自己的数据管理系统。
    • MySQL – MySQL是开放源代码的,任何人都可以在GPL(General Public License)的许可下下载并根据个性化的需要对其进行修改。MySQL因为其速度、可靠性和适应性而备受关注。
      PostgreSQL – 在BSD许可证下发行的开放源代码的关系数据库产品。

MySQL简介

MySQL最早是由瑞典的MySQL AB公司开发的一个开放源码的关系数据库管理系统,该公司于2008年被昇阳微系统公司(Sun Microsystems)收购。在2009年,甲骨文公司(Oracle)收购昇阳微系统公司,因此在这之后MySQL成为了Oracle旗下产品。

MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行的开源数据库,因此被广泛地应用于中小型网站开发。随着MySQL的不断成熟,它也逐渐被应用于更多大规模网站和应用,比如维基百科、谷歌(Google)、脸书(Facebook)、淘宝网等网站都使用了MySQL来提供数据持久化服务。

甲骨文公司收购后昇阳微系统公司,大幅调涨MySQL商业版的售价,且甲骨文公司不再支持另一个自由软件项目OpenSolaris的发展,因此导致自由软件社区对于Oracle是否还会持续支持MySQL社区版(MySQL的各个发行版本中唯一免费的版本)有所担忧,MySQL的创始人麦克尔·维德纽斯以MySQL为基础,成立分支计划MariaDB(以他女儿的名字命名的数据库)。有许多原来使用MySQL数据库的公司(例如:维基百科)已经陆续完成了从MySQL数据库到MariaDB数据库的迁移。

安装和配置

说明:下面的安装和配置都是以CentOS Linux环境为例,如果需要在其他系统下安装MySQL,读者可以自行在网络上查找对应的安装教程)。

刚才说过,MySQL有一个分支版本名叫MariaDB,该数据库旨在继续保持MySQL数据库在GNU GPL下开源。如果要使用MariaDB作为MySQL的替代品,可以使用下面的命令进行安装。

如果要安装官方版本的MySQL,可以在MySQL官方网站下载安装文件。首先在下载页面中选择平台和版本,然后找到对应的下载链接。下面以MySQL 5.7.26版本和Red Hat Enterprise Linux为例,直接下载包含所有安装文件的归档文件,解归档之后通过包管理工具进行安装。

如果系统上有MariaDB相关的文件,需要先移除MariaDB相关的文件。

接下来可以按照如下所示的顺序用RPM(Redhat Package Manager)工具安装MySQL。

可以使用下面的命令查看已经安装的MySQL相关的包。

配置MySQL。

MySQL的配置文件在/etc目录下,名为my.cnf,默认的配置文件内容如下所示。如果对这个文件不理解并没有关系,什么时候用到这个配置文件什么时候再了解它就行了。

启动MySQL服务。

可以使用下面的命令来启动MySQL。

在CentOS 7中,更推荐使用下面的命令来启动MySQL。

启动MySQL成功后,可以通过下面的命令来检查网络端口使用情况,MySQL默认使用3306端口。

也可以使用下面的命令查找是否有名为mysqld的进程。

使用MySQL客户端工具连接服务器。

命令行工具:

说明:启动客户端时,-u参数用来指定用户名,MySQL默认的超级管理账号为root;-p表示要输入密码(用户口令);如果连接的是其他主机而非本机,可以用-h来指定连接主机的主机名或IP地址。

如果是首次安装MySQL,可以使用下面的命令来找到默认的初始密码。

上面的命令会查看MySQL的日志带有password的行,在显示的结果中root@localhost:后面的部分就是默认设置的初始密码。

修改超级管理员(root)的访问口令为123456。

说明:MySQL较新的版本默认不允许使用弱口令作为用户口令,所以我们通过上面的前两条命令修改了验证用户口令的策略和口令的长度。事实上我们不应该使用弱口令,因为存在用户口令被暴力破解的风险。近年来,攻击数据库窃取数据和劫持数据库勒索比特币的事件屡见不鲜,要避免这些潜在的风险,最为重要的一点是不要让数据库服务器暴露在公网上(最好的做法是将数据库置于内网,至少要做到不向公网开放数据库服务器的访问端口),另外要保管好root账号的口令,应用系统需要访问数据库时,通常不使用root账号进行访问,而是创建其他拥有适当权限的账号来访问。

再次使用客户端工具连接MySQL服务器时,就可以使用新设置的口令了。在实际开发中,为了方便用户操作,可以选择图形化的客户端工具来连接MySQL服务器,包括:

  • MySQL Workbench(官方提供的工具)
  • Navicat for MySQL(界面简单优雅,功能直观强大)
  • SQLyog for MySQL(强大的MySQL数据库管理员工具)

常用命令。

查看服务器版本。

查看所有数据库。

切换到指定数据库。

查看数据库下所有表。

获取帮助。

SQL详解

基本操作

我们通常可以将SQL分为三类:DDL(数据定义语言)、DML(数据操作语言)和DCL(数据控制语言)。DDL主要用于创建(create)、删除(drop)、修改(alter)数据库中的对象,比如创建、删除和修改二维表;DML主要负责插入数据(insert)、删除数据(delete)、更新数据(update)和查询(select);DCL通常用于授予权限(grant)和召回权限(revoke)。

说明:SQL是不区分大小写的语言,为了书写方便,下面的SQL都使用了小写字母来书写。

DDL(数据定义语言)

上面的DDL有几个地方需要强调一下:

创建数据库时,我们通过default charset utf8指定了数据库默认使用的字符集,我们推荐使用该字符集,因为utf8能够支持国际化编码。如果将来数据库中用到的字符可能包括类似于Emoji这样的图片字符,也可以将默认字符集设定为utf8mb4(最大4字节的utf-8编码)。查看MySQL支持的字符集可以执行下面的语句。

如果要设置MySQL服务启动时默认使用的字符集,可以修改MySQL的配置并添加以下内容

在创建表的时候,我们可以在右圆括号的后面通过engine=XXX来指定表的存储引擎,MySQL支持多种存储引擎,可以通过show engines命令进行查看。MySQL 5.5以后的版本默认使用的存储引擎是InnoDB,它正好也就是我们推荐大家使用的存储引擎(因为InnoDB更适合互联网应用对高并发、性能以及事务支持等方面的需求)。

下面的表格对MySQL几种常用的数据引擎进行了简单的对比。

特性 InnoDB MRG_MYISAM MEMORY MyISAM
存储限制没有
事务 支持
锁机制行锁表锁 表锁 表锁
B树索引支持支持支持支持
哈希索引支持
全文检索支持(5.6+)支持
集群索引支持
数据缓存支持支持
索引缓存支持支持支持支持
数据可压缩支持
内存使用
存储空间使用
批量插入性能
是否支持外键支持

通过上面的比较我们可以了解到,InnoDB是唯一能够支持外键、事务以及行锁的存储引擎,所以我们之前说它更适合互联网应用,而且它也是较新的MySQL版本中默认使用的存储引擎。

在定义表结构为每个字段选择数据类型时,如果不清楚哪个数据类型更合适,可以通过MySQL的帮助系统来了解每种数据类型的特性、数据的长度和精度等相关信息。

在数据类型的选择上,保存字符串数据通常都使用VARCHAR和CHAR两种类型,前者通常称为变长字符串,而后者通常称为定长字符串;对于InnoDB存储引擎,行存储格式没有区分固定长度和可变长度列,因此VARCHAR类型好CHAR类型没有本质区别,后者不一定比前者性能更好。如果要保存的很大字符串,可以使用TEXT类型;如果要保存很大的字节串,可以使用BLOB(二进制大对象)类型。在MySQL中,TEXT和BLOB又分别包括TEXT、MEDIUMTEXT、LONGTEXT和BLOB、MEDIUMBLOB、LONGBLOB三种不同的类型,它们主要的区别在于存储数据的最大大小不同。保存浮点数可以用FLOAT或DOUBLE类型,而保存定点数应该使用DECIMAL类型。如果要保存时间日期,DATETIME类型优于TIMESTAMP类型,因为前者能表示的时间日期范围更大。

DML

上面的DML有几个地方需要加以说明:

MySQL中支持多种类型的运算符,包括:算术运算符(+、-、*、/、%)、比较运算符(=、<>、<=>、<、<=、>、>=、BETWEEN…AND…、IN、IS NULL、IS NOT NULL、LIKE、RLIKE、REGEXP)、逻辑运算符(NOT、AND、OR、XOR)和位运算符(&、|、^、~、>>、<<),我们可以在DML中使用这些运算符处理数据。

在查询数据时,可以在SELECT语句及其子句(如WHERE子句、ORDER BY子句、HAVING子句等)中使用函数,这些函数包括字符串函数、数值函数、时间日期函数、流程函数等,如下面的表格所示。

常用字符串函数。

函数 功能
CONCAT 将多个字符串连接成一个字符串
FORMAT 将数值格式化成字符串并指定保留几位小数
FROM_BASE64 / TO_BASE64 BASE64解码/编码
BIN / OCT / HEX 将数值转换成二进制/八进制/十六进制字符串
LOCATE 在字符串中查找一个子串的位置
LEFT / RIGHT 返回一个字符串左边/右边指定长度的字符
LENGTH / CHAR_LENGTH 返回字符串的长度以字节/字符为单位
LOWER / UPPER 返回字符串的小写/大写形式
LPAD / RPAD 如果字符串的长度不足,在字符串左边/右边填充指定的字符
LTRIM / RTRIM 去掉字符串前面/后面的空格
ORD / CHAR 返回字符对应的编码/返回编码对应的字符
STRCMP 比较字符串,返回-1、0、1分别表示小于、等于、大于
SUBSTRING 返回字符串指定范围的子串

常用数值函数。

函数功能
ABS 返回一个数的绝度值
CEILING / FLOOR 返回一个数上取整/下取整的结果
CONV 将一个数从一种进制转换成另一种进制
CRC32 计算循环冗余校验码
EXP / LOG / LOG2 / LOG10 计算指数/对数
POW 求幂
RAND 返回[0,1)范围的随机数
ROUND 返回一个数四舍五入后的结果
SQRT 返回一个数的平方根
TRUNCATE 截断一个数到指定的精度
SIN / COS / TAN / COT / ASIN / ACOS / ATAN 三角函数

常用时间日期函数。

函数 功能
CURDATE / CURTIME / NOW 获取当前日期/时间/日期和时间
ADDDATE / SUBDATE 将两个日期表达式相加/相减并返回结果
DATE / TIME 从字符串中获取日期/时间
YEAR / MONTH / DAY 从日期中获取年/月/日
HOUR / MINUTE / SECOND 从时间中获取时/分/秒
DATEDIFF / TIMEDIFF 返回两个时间日期表达式相差多少天/小时
MAKEDATE / MAKETIME制造一个日期/时间

常用流程函数。

函数 功能
IF 根据条件是否成立返回不同的值
IFNULL 如果为NULL则返回指定的值否则就返回本身
NULLIF 两个表达式相等就返回NULL否则返回第一个表达式的值

其他常用函数。

函数 功能
MD5 / SHA1 / SHA2 返回字符串对应的哈希摘要
CHARSET / COLLATION 返回字符集/校对规则
USER / CURRENT_USER 返回当前用户
DATABASE 返回当前数据库名
VERSION 返回当前数据库版本
FOUND_ROWS / ROW_COUNT 返回查询到的行数/受影响的行数
LAST_INSERT_ID 返回最后一个自增主键的值
UUID / UUID_SHORT 返回全局唯一标识符

DCL

说明:创建一个可以允许任意主机登录并且具有超级管理员权限的用户在现实中并不是一个明智的决定,因为一旦该账号的口令泄露或者被破解,数据库将会面临灾难级的风险。

索引

索引是关系型数据库中用来提升查询性能最为重要的手段。关系型数据库中的索引就像一本书的目录,我们可以想象一下,如果要从一本书中找出某个知识点,但是这本书没有目录,这将是意见多么可怕的事情(我们估计得一篇一篇的翻下去,才能确定这个知识点到底在什么位置)。创建索引虽然会带来存储空间上的开销,就像一本书的目录会占用一部分的篇幅一样,但是在牺牲空间后换来的查询时间的减少也是非常显著的。

MySQL中,所有数据类型的列都可以被索引,常用的存储引擎InnoDB和MyISAM能支持每个表创建16个索引。InnoDB和MyISAM使用的索引其底层算法是B-tree(B树),B-tree是一种自平衡的树,类似于平衡二叉排序树,能够保持数据有序。这种数据结构能够让查找数据、顺序访问、插入数据及删除的操作都在对数时间内完成。

接下来我们通过一个简单的例子来说明索引的意义,比如我们要根据学生的姓名来查找学生,这个场景在实际开发中应该经常遇到,就跟通过商品名称查找商品道理是一样的。我们可以使用MySQL的explain关键字来查看SQL的执行计划。

在上面的SQL执行计划中,有几项值得我们关注:

  • type:MySQL在表中找到满足条件的行的方式,也称为访问类型,包括:ALL(全表扫描)、index(索引全扫描)、range(索引范围扫描)、ref(非唯一索引扫描)、eq_ref(唯一索引扫描)、const/system、NULL。在所有的访问类型中,很显然ALL是性能最差的,它代表了全表扫描是指要扫描表中的每一行才能找到匹配的行。
  • possible_keys:MySQL可以选择的索引,但是有可能不会使用。
  • key:MySQL真正使用的索引。
  • rows:执行查询需要扫描的行数,这是一个预估值。

从上面的执行计划可以看出,当我们通过学生名字查询学生时实际上是进行了全表扫描,不言而喻这个查询性能肯定是非常糟糕的,尤其是在表中的行很多的时候。如果我们需要经常通过学生姓名来查询学生,那么就应该在学生姓名对应的列上创建索引,通过索引来加速查询。

再次查看刚才的SQL对应的执行计划。

可以注意到,在对学生姓名创建索引后,刚才的查询已经不是全表扫描而是基于索引的查询,而且扫描的行只有唯一的一行,这显然大大的提升了查询的性能。MySQL中还允许创建前缀索引,即对索引字段的前N个字符创建索引,这样的话可以减少索引占用的空间(但节省了空间很有可能会浪费时间,时间和空间是不可调和的矛盾),如下所示。

上面的索引相当于是根据学生姓名的第一个字来创建的索引,我们再看看SQL执行计划。

不知道大家是否注意到,这一次扫描的行变成了2行,因为学生表中有两个姓“林”的学生,我们只用姓名的第一个字作为索引的话,在查询时通过索引就会找到这两行。

如果要删除索引,可以使用下面的SQL。

或者

我们简单的为大家总结一下索引的设计原则:

  • 最适合索引的列是出现在WHERE子句和连接子句中的列。
  • 索引列的基数越大(取值多重复值少),索引的效果就越好。
  • 使用前缀索引可以减少索引占用的空间,内存中可以缓存更多的索引。
  • 索引不是越多越好,虽然索引加速了读操作(查询),但是写操作(增、删、改)都会变得更慢,因为数据的变化会导致索引的更新,就如同书籍章节的增删需要更新目录一样。
  • 使用InnoDB存储引擎时,表的普通索引都会保存主键的值,所以主键要尽可能选择较短的数据类型,这样可以有效的减少索引占用的空间,利用提升索引的缓存效果。

最后,还有一点需要说明,InnoDB使用的B-tree索引,数值类型的列除了等值判断时索引会生效之外,使用>、<、>=、<=、BETWEEN…AND… 、<>时,索引仍然生效;对于字符串类型的列,如果使用不以通配符开头的模糊查询,索引也是起作用的,但是其他的情况会导致索引失效,这就意味着很有可能会做全表查询。

视图

视图是关系型数据库中将一组查询指令构成的结果集组合成可查询的数据表的对象。简单的说,视图就是虚拟的表,但与数据表不同的是,数据表是一种实体结构,而视图是一种虚拟结构,你也可以将视图理解为保存在数据库中被赋予名字的SQL语句。

使用视图可以获得以下好处:

  • 可以将实体数据表隐藏起来,让外部程序无法得知实际的数据结构,让访问者可以使用表的组成部分而不是整个表,降低数据库被攻击的风险。
  • 在大多数的情况下视图是只读的(更新视图的操作通常都有诸多的限制),外部程序无法直接透过视图修改数据。
  • 重用SQL语句,将高度复杂的查询包装在视图表中,直接访问该视图即可取出需要的数据;也可以将视图视为数据表进行连接查询。
  • 视图可以返回与实体数据表不同格式的数据,

创建视图。

提示:因为视图不包含数据,所以每次使用视图时,都必须执行查询以获得数据,如果你使用了连接查询、嵌套查询创建了较为复杂的视图,你可能会发现查询性能下降得很厉害。因此,在使用复杂的视图前,应该进行测试以确保其性能能够满足应用的需求。

使用视图。

既然视图是一张虚拟的表,那么视图的中的数据可以更新吗?视图的可更新性要视具体情况而定,以下类型的视图是不能更新的:

  • 使用了聚合函数(SUM、MIN、MAX、AVG、COUNT等)、DISTINCT、GROUP BY、HAVING、UNION或者UNION ALL的视图。
  • SELECT中包含了子查询的视图。
  • FROM子句中包含了一个不能更新的视图的视图。
  • WHERE子句的子查询引用了FROM子句中的表的视图。

删除视图。

说明:如果希望更新视图,可以先用上面的命令删除视图,也可以通过create or replace view来更新视图。

视图的规则和限制。

  • 视图可以嵌套,可以利用从其他视图中检索的数据来构造一个新的视图。视图也可以和表一起使用。
  • 创建视图时可以使用order by子句,但如果从视图中检索数据时也使用了order by,那么该视图中原先的order by会被覆盖。
  • 视图无法使用索引,也不会激发触发器(实际开发中因为性能等各方面的考虑,通常不建议使用触发器,所以我们也不对这个概念进行介绍)的执行。

存储过程

存储过程是事先编译好存储在数据库中的一组SQL的集合,调用存储过程可以简化应用程序开发人员的工作,减少与数据库服务器之间的通信,对于提升数据操作的性能也是有帮助的。其实迄今为止,我们使用的SQL语句都是针对一个或多个表的单条语句,但在实际开发中经常会遇到某个操作需要多条SQL语句才能完成的情况。例如,电商网站在受理用户订单时,需要做以下一系列的处理。

  • 通过查询来核对库存中是否有对应的物品以及库存是否充足。
  • 如果库存有物品,需要锁定库存以确保这些物品不再卖给别人, 并且要减少可用的物品数量以反映正确的库存量。
  • 如果库存不足,可能需要进一步与供应商进行交互或者至少产生一条系统提示消息。
  • 不管受理订单是否成功,都需要产生流水记录,而且需要给对应的用户产生一条通知信息。

我们可以通过存储过程将复杂的操作封装起来,这样不仅有助于保证数据的一致性,而且将来如果业务发生了变动,只需要调整和修改存储过程即可。对于调用存储过程的用户来说,存储过程并没有暴露数据表的细节,而且执行存储过程比一条条的执行一组SQL要快得多。

下面的存储过程实现了查询某门课程的最高分、最低分和平均分。

说明:在定义存储过程时,因为可能需要书写多条SQL,而分隔这些SQL需要使用分号作为分隔符,如果这个时候,仍然用分号表示整段代码结束,那么定义存储过程的SQL就会出现错误,所以上面我们用delimiter $$将整段代码结束的标记定义为$$,那么代码中的分号将不再表示整段代码的结束,需要马上执行,整段代码在遇到end $$时才输入完成并执行。在定义完存储过程后,通过delimiter ;将结束符重新改回成分号。

上面定义的存储过程有四个参数,其中第一个参数是输入参数,代表课程的编号,后面的参数都是输出参数,因为存储过程不能定义返回值,只能通过输出参数将执行结果带出,定义输出参数的关键字是out,默认情况下参数都是输入参数。

调用存储过程。

获取输出参数的值。

删除存储过程。

在存储过程中,我们可以定义变量、条件,可以使用分支和循环语句,可以通过游标操作查询结果,还可以使用事件调度器,这些内容我们暂时不在此处进行介绍。虽然我们说了很多存储过程的好处,但是在实际开发中,如果过度的使用存储过程,将大量复杂的运算放到存储过程中,也会导致占用数据库服务器的CPU资源,造成数据库服务器承受巨大的压力。为此,我们一般会将复杂的运算和处理交给应用服务器,因为很容易部署多台应用服务器来分摊这些压力。

几个重要的概念

范式理论 – 设计二维表的指导思想

  • 第一范式:数据表的每个列的值域都是由原子值组成的,不能够再分割。
  • 第二范式:数据表里的所有数据都要和该数据表的键(主键与候选键)有完全依赖关系。
  • 第三范式:所有非键属性都只和候选键有相关性,也就是说非键属性之间应该是独立无关的。

数据完整性

实体完整性 – 每个实体都是独一无二的

  • 主键(primary key) / 唯一约束 / 唯一索引(unique)

引用完整性(参照完整性)- 关系中不允许引用不存在的实体

  • 外键(foreign key)

域完整性 – 数据是有效的

  • 数据类型及长度
  • 非空约束(not null)
  • 默认值约束(default)
  • 检查约束(check)

说明:在MySQL数据库中,检查约束并不起作用。

数据一致性

事务:一系列对数据库进行读/写的操作,这些操作要么全都成功,要么全都失败。

事务的ACID特性

  • 原子性:事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行
  • 一致性:事务应确保数据库的状态从一个一致状态转变为另一个一致状态
  • 隔离性:多个事务并发执行时,一个事务的执行不应影响其他事务的执行
  • 持久性:已被提交的事务对数据库的修改应该永久保存在数据库中

MySQL中的事务操作

开启事务环境

提交事务

回滚事务

其他内容

大家应该能够想到,关于MySQL的知识肯定远远不止上面列出的这些,比如MySQL的性能优化、管理和维护MySQL的相关工具、MySQL数据的备份和恢复、监控MySQL、部署高可用架构等问题我们在这里都没有进行讨论。当然,这些内容也都是跟项目开发密切相关的,我们就留到后续的章节中再续点进行讲解。

Python数据库编程

我们用如下所示的数据库来演示在Python中如何访问MySQL数据库。

在Python 3中,我们通常使用纯Python的三方库PyMySQL来访问MySQL数据库,它应该是目前Python操作MySQL数据库最好的选择。

安装PyMySQL。

添加一个部门。

删除一个部门。

说明:如果不希望每次SQL操作之后手动提交或回滚事务,可以像上面的代码那样,在创建连接的时候多加一个名为autocommit的参数并将它的值设置为True,表示每次执行SQL之后自动提交。如果程序中不需要使用事务环境也不希望手动的提交或回滚就可以这么做。

更新一个部门。

查询所有部门。

分页查询员工信息。

 

本文来自这个系列长期转载Python-100-Days ,本文观点不代表蓝洛水深立场,转载请联系原作者。

(0)
蓝洛水深的头像蓝洛水深管理员
上一篇 2020年6月17日 下午1:39
下一篇 2020年6月23日 下午9:22

相关推荐

发表回复

登录后才能评论
联系QQ
联系QQ
分享本页
返回顶部