Python学习日志17 - Python数据库编程

Python学习日志

RBHGO的主页欢迎关注

温馨提示:创作不易,如有转载,注明出处,感谢配合~

目录

Python学习日志17课 - Python数据库编程

DCL (数据控制语言)

DCL —> 授予或者召回用户权限 —> grant / revoke

在前两篇分享中,依次叙述了SQL的DDL (数据定义语言)、DML (数据操作语言)以及DQL (数据查询语言),这一篇就先与大家分享最后一种DCL。大家可以想一想,我们构建一个完整的数据库,肯定不是让自己一个人使用吧,开发人员要使用,数据分析人员要使用,前端人员可能要使用,超级管理员也要使用;所以不可能所有人都用管理员账号登入数据库,那还不乱了套了。虽然我们可以在图形化工具中手动配置,可每个数据库的操作不一样;但是SQL语句接入是一样的,所以如果您要干这一行还是好好学习了解所有的SQL语句,当然重点是学习自己的专业方向的SQL。文章内用到的数据库均来自RBHGO的学习日志16

接下来就和大家分享DCL

    -- 创建用户(可以远程登录的账号并为其指定口令)---> @号前面是账户名,后面是IP地址
    	-- IP地址可以用详细的,也可用%表示模糊,因为大部分时候同一时间会有多个guest账户同时连入数据库
    	-- 例如 % / 114.120.138.% / 127.0.0.1
        create user 'guest'@'%' identified by '123456789';
    
    -- 删除用户
        drop user 'guest'@'%';

    -- 修改密码(将名为guest的账号密码改为guest.123)
        alter user 'guest'@'%' identified by 'guest.123';

    -- 授权
        -- 授予guest对hrs库的操作权限 database_name.table_name (数据库名 英文的点 二维表名) 
        	-- 例如这里的 hrs.* ---> 表示hrs库中的所有的表
        	grant selectinsert, delete, update on hrs.* to 'guest'@'%';

        -- 全授权
            -- 授权,除了不能授予别人这一项
        		grant all privileges on *.* to 'guest'@'%';
            -- 全授权
        		grant all privileges on *.* to 'guest'@'%' with grant option;

    -- 召回权限(召回guest对hrs数据库所有对象的select/insert/delete/update权限)
        revoke selectinsert, delete, update on hrs.* from 'guest'@'%';

    -- 让权限立马生效
        flush privileges;
进入正题
Python连接 MySQL

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

安装PyMySQL

在终端或者说Terminal中输入

pip install pymysql

下载完成后,在项目文件中

import pymysql

重点提示:

因为存在SQL注入攻击,在python代码中写sql语句,不能使用任何形式的格式化(拼接)字符串

"""
Python连接 MySQL,插入数据

安装三方库:
    - pymysql ---> 纯python编写,安装一定可以成功
    - mysqlclient ---> 底层用C编写,安装有可能无法成功


事务:把若干个(增删改)操作视为一个不可分割的原子性操作,要么全部成功,要么全部失败
    - 如果操作成功,我们可以通过连接对象的commit方法手动提交
    - 如果操作失败,我们可以通过连接对象的rollback方法回滚,删除操作

Author: RBHGO
Declaration: Mia San Mia ~~~
"""
import pymysql

no = input('请输入部门编号:')
name = input('请输入部门名称:')
area = input('请输入部门地址:')

# 第一步:创建连接
# 在默认情况下创建连接,相当于开启了一个事物环境
# 事务:把若干个(增删改)操作视为一个不可分割的原子性操作,要么全部成功,要么全部失败
#     - 如果操作成功,我们可以通过连接对象的commit方法手动提交
#     - 如果操作失败,我们可以通过连接对象的rollback方法实现事务回滚,删除操作

conn = pymysql.connect(host='localhost', port=3306,
                       user='您设的用户名', password='您的密码',
                       database='hrs', charset='utf8mb4')
# 默认是手动提交,想要自动 connect()加上参数autocommit=True

try:
    # 第二步:获取游标对象
    with conn.cursor() as cursor:

        # 第三步:通过游标对象向数据库发出SQL语句获得执行结果(sql用%s做占位符,后面用元组输入内容)
        affected_rows = cursor.execute(
            'insert into tb_dept values(%s, %s, %s)',
            (no, name, area))

        print(affected_rows)

    # 第四步成功:手动提交
    conn.commit()
except pymysql.MySQLError:
    # 第四步失败:手动回滚
    conn.rollback()

finally:
    # 第五步:关闭连接释放资源
    conn.close()
"""
Python连接 MySQL,删除数据

Author: RBHGO
"""
import pymysql

no = input('请输入要删除的部门编号:')
conn = pymysql.connect(host='localhost', port=3306,
                       user='您设的用户名', password='您的密码',
                       database='hrs', charset='utf8mb4')
try:
    # 第二步:获取游标对象
    with conn.cursor() as cursor:
        # 第三步:通过游标对象向数据库发出SQL语句获得执行结果
        affected_rows = cursor.execute(
            'delete from tb_dept where dno=%s', (no,))

        print(affected_rows)

    # 第四步成功:手动提交
    conn.commit()
except pymysql.MySQLError:
    # 第四步失败:手动回滚
    conn.rollback()

finally:
    # 第五步:关闭连接释放资源
    conn.close()
"""
Python连接 MySQL,更新数据

Author: RBHGO
"""
import pymysql

name = input('修改部门名称为:')
area = input('修改部门地址为:')
no = input('要修改部门编号为:')
conn = pymysql.connect(host='localhost', port=3306,
                       user='您设的用户名', password='您的密码',
                       database='hrs', charset='utf8mb4')

try:
    # 第二步:获取游标对象
    with conn.cursor() as cursor:

        # 第三步:通过游标对象向数据库发出SQL语句获得执行结果
        affected_rows = cursor.execute(
            'update tb_dept set dname=%s, dloc=%s where dno=%s',
            (name, area, no))

        print(affected_rows)

    # 第四步成功:手动提交
    conn.commit()
except pymysql.MySQLError:
    # 第四步失败:手动回滚
    conn.rollback()

finally:
    # 第五步:关闭连接释放资源
    conn.close()
"""
Python连接 MySQL,查询

Author: RBHGO
"""
import pymysql

conn = pymysql.connect(host='localhost', port=3306,
                       user='您设的用户名', password='您的密码',
                       database='hrs', charset='utf8mb4')

try:
    with conn.cursor() as cursor:
        # 通过游标抓取数据
        cursor.execute('select dno, dname, dloc from tb_dept')
        # print(cursor.fetchone())    ---> 抓取一条
        # print(cursor.fetchall())    ---> 抓取全部
        # print(cursor.fetchmany())   ---> 抓取指定数量

        dept_row = cursor.fetchone()
        while dept_row:
            print(f'部门编号: {dept_row[0]}')
            print(f'部门名称:{dept_row[1]}')
            print(f'部门所在地: {dept_row[2]}')
            print('-' * 20)
            dept_row = cursor.fetchone()


except pymysql.MySQLError as err:
    print(err)
finally:
    conn.close()
    
    
    
    
    
"""
将游标类型定为字典,通过键值对取值,操作性更加强

Author: RBHGO
"""

import pymysql

conn = pymysql.connect(host='localhost', port=3306,
                       user='您设的用户名', password='您的密码',
                       database='hrs', charset='utf8mb4',
                       cursorclass=pymysql.cursors.DictCursor)

try:
    with conn.cursor(pymysql.cursors.DictCursor) as cursor:
        # 通过游标抓取数据
        cursor.execute('select dno as no, dname as name, dloc as loc from tb_dept')

        dept_row = cursor.fetchone()
        while dept_row:
            print(dept_row.get('no'), dept_row.get('name'), dept_row.get('loc'))
            dept_row = cursor.fetchone()

except pymysql.MySQLError as err:
    print(err)
finally:
    conn.close()
    
"""
面对对象

Author: RBHGO
"""
import pymysql


class Dept:
    def __init__(self, no, name, location):
        self.no = no
        self.name = name
        self.location = location

    def __str__(self):
        return f'编号:{self.no} \n名称:{self.name} \n所在地:{self.location}'


conn = pymysql.connect(host='localhost', port=3306,
                       user='您设的用户名', password='您的密码',
                       database='hrs', charset='utf8mb4')

try:
    with conn.cursor() as cursor:
        # 通过游标抓取数据
        cursor.execute('select dno, dname, dloc from tb_dept')

        dept_row = cursor.fetchone()
        while dept_row:
            print(dept_row.get('no'), dept_row.get('name'), dept_row.get('loc'))
            dept_row = cursor.fetchone()

except pymysql.MySQLError as err:
    print(err)
finally:
    conn.close()
"""
批量插入

Author: RBHGO
"""
import time
import pymysql

conn = pymysql.connect(host='localhost', port=3306,
                       user='您设的用户名', password='您的密码',
                       database='hrs', charset='utf8mb4')
start = time.time()
try:
    # 第二步:获取游标对象
    with conn.cursor() as cursor:
        params = []
        for i in range(1, 50001):
            params.append((f'user{i}',))
            if i % 1000 == 0:
                cursor.executemany(
                    'insert into tb_user(username) values (%s)',
                    params
                )
                params.clear()
        conn.commit()
        # 计时
        end = time.time()
        print(f'耗时{end - start:.2f}秒')
except pymysql.MySQLError:
    # 第四步失败:手动回滚
    conn.rollback()

finally:
    # 第五步:关闭连接释放资源
    conn.close()
拓展知识

削微再和大家分享一点其他知识,没有详细的分享不是他们不重要,是因为我没有经常使用,不是特别贯通,所以这里只能做一个介绍,如果有兴趣可以去查资料学习哦。

索引

索引是关系型数据库中用来提升查询性能最为重要的手段。关系型数据库中的索引就像一本书的目录,我们可以想象一下,如果要从一本书中找出某个知识点,但是这本书没有目录这件是是极其糟糕的。创建索引虽然会带来存储空间上的开销,就像一本书的目录会占用一部分的篇幅一样,但是在牺牲空间后换来的查询时间的减少也是非常显著的,空间和时间总是没法两全的。

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

  -- 简单来说:
    -- 索引的本质就相当于是一个排好序的目录,加速查询
    -- 索引通常要建在经常用户查询筛选条件的列上,这样才能有效的加速查询
    -- 不要滥用索引,因为索引虽然加速了查询,但是会让插入数据的操作变得更慢
    -- 对于InnoDB引擎来说,索引的底层是一个B+树,B+树是一个层次结构
    -- 这种结构拥有极好磁盘I/O性能,一个4层的B+树就能应对10亿级的数据体量
    -- 对于InnoDB引擎来说,主键上默认就会建索引,而且索引就是整张表的数据
    -- 这种索引也称为聚集索引(只能一个),而我们自己创建的索引都是非聚集索引

在前面的分享中也提到,我们可以使用MySQL的explain关键字来查看SQL的执行计划。

使用性能剖析系统:

​ explain select语句 —> 得到搜索语句的性能

​ 通常情况下我们就看type和rows就可以

​ type性能(坏->好) ALL -> index -> range -> ref -> ref eg -> const/svstem

SQL执行计划中,会得到以下信息:

  1. select_type:查询的类型。
    • SIMPLE:简单SELECT,不需要使用UNION操作或子查询。
    • PRIMARY:如果查询包含子查询,最外层的SELECT被标记为PRIMARY。
    • UNION:UNION操作中第二个或后面的SELECT语句。
    • SUBQUERY:子查询中的第一个SELECT。
    • DERIVED:派生表的SELECT子查询。
  2. table:查询对应的表。
  3. type:MySQL在表中找到满足条件的行的方式,也称为访问类型,包括:ALL(全表扫描)、index(索引全扫描,只遍历索引树)、range(索引范围扫描)、ref(非唯一索引扫描)、eq_ref(唯一索引扫描)、const/system(常量级查询)、NULL(不需要访问表或索引)。在所有的访问类型中,很显然ALL是性能最差的,它代表的全表扫描是指要扫描表中的每一行才能找到匹配的行。
  4. possible_keys:MySQL可以选择的索引,但是有可能不会使用
  5. key:MySQL真正使用的索引,如果为NULL就表示没有使用索引。
  6. key_len:使用的索引的长度,在不影响查询的情况下肯定是长度越短越好。
  7. rows:执行查询需要扫描的行数,这是一个预估值
  8. extra:关于查询额外的信息。
    • Using filesort:MySQL无法利用索引完成排序操作。
    • Using index:只使用索引的信息而不需要进一步查表来获取更多的信息。
    • Using temporary:MySQL需要使用临时表来存储结果集,常用于分组和排序。
    • Impossible wherewhere子句会导致没有符合条件的行。
    • Distinct:MySQL发现第一个匹配行后,停止为当前的行组合搜索更多的行。
    • Using where:查询的列未被索引覆盖,筛选条件并不是索引的前导列。
-- 创建索引
        create index idx_emp_ename on tb_emp (ename);

    -- 前缀索引
        create index idx_emp_ename on tb_emp (ename(1));

    -- 索引的覆盖性问题
    -- 如果查询的列没有被索引覆盖到,就会产生回表现象
    -- 如果不希望产生回表现象,那么我们在创建索引时,可以使用复合索引尽可能覆盖查询需要的所有的列
        select eno, ename, job from tb_emp where ename='曾霜';

    -- 复合索引
        drop index idx_emp_ename_job on tb_emp;

        create index idx_emp_ename_job on tb_emp (ename, job);

        explain select eno, ename, job from tb_emp where ename='曾';

        explain select eno, ename, job from tb_emp where ename like '曾%' order by ename;

        explain select eno, ename, job from tb_emp where ename like '曾%' order by sal;

        explain select eno, ename, job from tb_emp 
        where ename='曾霜' and job='总裁';

    -- 负向条件无法使用索引优化查询
        explain select eno, ename, job from tb_emp where ename<>'曾霜';

        explain select eno, ename, job from tb_emp where ename like '曾%';

    -- 模糊查询如果左边使用通配符会导致索引失效
        explain select eno, ename, job from tb_emp where ename like '%霜';

    -- 在筛选条件中如果使用了函数也会导致索引失效
        explain select eno, ename, job from tb_emp where concat(ename, '女士')='曾霜女士';

    -- 下面的查询无法使用复合索引(or)
        explain select eno, ename, job from tb_emp 
        where ename='曾霜' or job='总裁';

    -- 下面的查询无法使用复合索引
        explain select eno, ename, job from tb_emp where job='总裁';

    -- 删除索引
        drop index idx_emp_ename on tb_emp;
        alter table tb_emp drop index idx_emp_ename;
视图
-- 视图
-- 给查询生成一个快照,以后可以直接通过视图获得查询结果
-- 可以通过视图将用户访问数据的权限限制到具体的列上
-- 视图是一张虚拟的表.视图的可更新性要视具体情况而定(更新视图,其实是更新视图后面的表)

-- 创建视图
	create view vi_emp_simple as
	select eno, ename, job, dno from tb_emp;

-- 删除视图
	drop view vw_dept_emp_count;

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


视图的规则和限制。

  1. 视图可以嵌套,可以利用从其他视图中检索的数据来构造一个新的视图。视图也可以和表一起使用。
  2. 创建视图时可以使用order by子句,但如果从视图中检索数据时也使用了order by,那么该视图中原先的order by会被覆盖。
  3. 视图无法使用索引,也不会激发触发器(实际开发中因为性能等各方面的考虑,通常不建议使用触发器,所以我们也不对这个概念进行介绍)的执行。
存储过程
-- 存储过程(又称过程)是事先编译好存储在数据库中的一组SQL的集合,调用过程可以简化应用程序开发人员的工作,减少与数据库服务器之间的通信,对于提升数据操作的性能也是有帮助的。其实迄今为止,我们使用的SQL语句都是针对一个或多个表的单条语句,但在实际开发中经常会遇到某个操作需要多条SQL语句才能完成的情况。


    -- 查看与安全相关的变量
        show variables like '%safe%';

    -- (存储)过程:将一系列的SQL组装到一起编译存储起来以备调用
    -- 删除存储过程
        -- drop procedure if exists sp_upgrade_emp_sal;
        
    -- 创建存储过程
        delimiter $$
        
        create procedure sp_upgrade_emp_sal()
        begin
            set sql_safe_update=off;
            update tb_emp set sal=sal+200 where dno=10;
            update tb_emp set sal=sal+500 where dno=20;
            update tb_emp set sal=sal+800 where dno=30;
        end $$
        
        delimiter ;

    -- 调用存储过程直接修改数据库数据(hrs库中的部门编号对应员工工资增加)
        call sp_upgrade_emp_sal();
        

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

SQL定义函数
-- 删除函数 
    -- drop function truncate_string;

-- 用SQL语句封装函数,SQL是一种计算机语言,当然也是可以用来写函数的。
-- delimiter先把语句结束从;改为$$(写完记得改回来),在函数中还有 no sql声明这段不是sql语句(而是sql封装的函数)

delimiter $$

create function truncate_string(
	content varchar(16000),
	max_length int unsigned
) returns varchar(16000) no sql
begin
	declare result varchar(16000) default '';
	if length(content) > max_length then
		set result=concat(substring(content, 1, max_length), '……');
	else
		set result=content;
	end if;
	return result;
end $$

delimiter ;


    -- 测试
        select truncate_string('漫漫长夜,思念幻想,准备好么?你的行囊!明天就要,独自去往,迎着正午,向着朝阳。', 25);

        select
            col_id, 
            col_name,
            truncate_string(col_intro, 250)
        from 
            tb_college;

感谢学习陪伴,您的点赞,评论就是我更新的动力

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值