• MySQL数据库(5)- pymysql的使用、索引


    一、pymysql模块的使用

    1、pymysql的下载和使用

           之前我们都是通过MySQL自带的命令行客户端工具mysql来操作数据库,那如何在python程序中操作数据库呢?这就需要用到pymysql模块,该模块本质就是一个套接字客户端软件,使用前需要事先安装。

           1)pymysql模块的下载

        pip3 install pymysql

           2)pymysql模块的使用

                  现有数据库mydb,其中有一个userinfo表,表中数据如下:

        mysql> select * from userinfo;
        +----+------+-----+
        | id | name | pwd |
        +----+------+-----+
        |  1 | wll  | 123 |
        |  2 | ssx  | 456 |
        +----+------+-----+

                  示例:使用Python实现用户登录,如果用户存在则登录成功,否则登录失败 

        import pymysql
        username = input('请输入用户名:')
        pwd = input('请输入密码:')
    
        # 1、连接
        conn = pymysql.connect(
            host = '127.0.0.1',
            port = 3306,
            user = 'root',
            password = '123',
            db = 'mydb',
            charset = 'utf8'
        )
        # 2、创建游标
        cur = conn.cursor()
    
        sql = "select * from userinfo where name='%s' and pwd='%s'" %(username,pwd)
        # 3、执行sql语句
        result = cur.execute(sql)
        print(result)  # result为sql语句执行后生效的行数
    
        # 4、关闭:游标和连接都要关闭
        cur.close()
        conn.close()
    
        if result:
            print('登录成功')
        else:
            print('登录失败')
    2、execute()之sql注入问题

      sql语句的注释:-- 这是注释

      一条sql语句如果是select * from userinfo where name='wll' -- haha' and pwd=''

      那么-- 之后的内容就被注释掉了(注意:--后面还有一个空格)。

           所以,上例中当用户输入如下内容就会出现问题

           # sql注入之:用户存在,绕过密码

           wll' -- 任意字符

      # sql注入之:用户不存在,绕过用户名和密码

      xxx' or 1=1 -- 任意字符

      原因是我们对sql语句进行字符串拼接时,为%s加了引号,解决方法如下:

        # 用execute()帮我们做字符串拼接,无需且一定不能再为%s加引号(因为pymysql会自动加上)
        sql = "select * from userinfo where name=%s and pwd=%s"
        result = cur.execute(sql,[username,pwd])  # 第二个参数可以是列表
        result = cur.execute(sql,(username,pwd))  # 也可以是元组

    # 当execute()的第二个参数是字典时,sql中应该加上key,如下 sql = "select * from userinfo where name=%(key1)s and pwd=%(key2)s" result = cur.execute(sql,{'key1':username,'key2':pwd})
    3、pymysql中对数据库增、删、改:conn.commit()

      commit()方法:通过pymysql对数据库进行增、删、改时,必须用commit()方法提交,否则无效。

           示例:

        import pymysql
        username = input('请输入用户名:')
        pwd = input('请输入密码:')
    
        # 1、连接
        conn = pymysql.connect(
            host = '127.0.0.1',
            port = 3306,
            user = 'root',
            password = '123',
            db = 'mydb',
            charset = 'utf8'
        )
        # 2、创建游标对象
        cur = conn.cursor()
    
        # 3、执行sql语句
        #
        sql = "insert into userinfo(name,pwd) values (%s,%s)"
        result = cur.execute(sql,[username,pwd])
        print(result)  # 输出 1
        # 同时插入多条数据
        effect_row = cur.executemany(sql,[('张三','110'),('李四','119')])
        print(effect_row)  # 输出 2
    
        #
        sql = "delete from userinfo where id=1"
        effect_row = cur.execute(sql)
        print(effect_row)  # 1
    
        #
        sql = "update userinfo set name=%s where id=2"
        effect_row = cur.execute(sql,username)
        print(effect_row)  # 1
    
        # 4、增、删、改之后一定要commit
        conn.commit()
    
        # 5、关闭:游标和连接都要关闭
        cur.close()
        conn.close()
    4、pymysql中对数据库查询:fetchone()、fetchall()、fetchmany(n)

           有如下表内容:

        mysql> select * from userinfo;
        +----+--------+-----+
        | id | name   | pwd |
        +----+--------+-----+
        |  1 | wll    | 123 |
        |  2 | ssx    | 456 |
        |  3 | 张三    | 123 |
        |  4 | 李四    | 456 |
        +----+--------+-----+

           示例一:fetchone() – 获取一行数据,第一次为首行

        import pymysql
        conn = pymysql.connect(host='localhost', port=3306, user='root', password='123', db='mydb', charset='utf8')         # 此处不能写 utf-8 ,否则报错
        cur = conn.cursor()
        sql = "select * from userinfo"
        effct_row = cur.execute(sql)
        print(effct_row)  # 4
    
        row = cur.fetchone()  # 查询第一行的数据
        print(row)  # (1, 'wll', '123')
        row = cur.fetchone()  # 从上次位置继续,即查询第二行数据
        print(row)  # (2, 'ssx', '456')
        cur.close()
        conn.close()

           示例二:fetchall() - 获取所有行数据

        import pymysql
        conn = pymysql.connect(host='localhost', port=3306, user='root', password='123', db='mydb', charset='utf8')
        cur = conn.cursor()
        sql = "select * from userinfo"
        effct_row = cur.execute(sql)
        print(effct_row)  # 4
    
        rows = cur.fetchall()  # 查询所有行的数据
        print(rows)
        # 结果为:
        # ((1, 'wll', '123'), (2, 'ssx', '456'), (3, '张三', '123'), (4, '李四', '456'))
        cur.close()
        conn.close()

           总结:从上例中输出结果可以看出,我们获取到的返回值是一个元组,每一行数据也是一个元组,所以我们无法知道数据对应的字段是什么,这个时候,可以通过如下方式将每一行的数据变为一个字典,字典的key就是字段名,value就是对应的值,如下:

        # 在实例化游标对象的时候,将属性cursor设置为    pymysql.cursors.DictCursor
        cur = conn.cursor(cursor=pymysql.cursors.DictCursor)
        # 结果为:
        # [
        #   {'id': 1, 'name': 'wll', 'pwd': '123'},
        #   {'id': 2, 'name': 'ssx', 'pwd': '456'},
        #   {'id': 3, 'name': '张三', 'pwd': '123'},
        #   {'id': 4, 'name': '李四', 'pwd': '456'}
        # ]

           示例三:移动指针位置

                  fetchone示例中,在获取行数据的时候,可以理解为,刚开始,有一个行指针指着第一行的上方,获取一行,它就向下移动一行,所以当行指针移到最后一行的时候,就不能再获取到内容,所以我们可以使用如下方法来移动行指针:

        cur.scroll(1,mode='relative')  # 相对当前位置移动
        cur.scroll(1,mode='absolute')  # 相对首行位置移动

           参数解释:

                  第一个值为移动的行数,正数为向下移动,负数为向上移动;mode指定了是相对当前位置移动,还是相对于首行移动。

           代码: 

        import pymysql
        conn = pymysql.connect(host='localhost', port=3306, user='root', password='123', db='mydb', charset='utf8')
        cur = conn.cursor(cursor=pymysql.cursors.DictCursor)
        sql = 'select * from userinfo'
        effct_row = cur.execute(sql)
    
        row = cur.fetchone()   # 查询第一行的数据
        print(row)  # {'id': 1, 'name': 'wll', 'pwd': '123'}
        row = cur.fetchone()  # 查询第二行数据
        print(row)  # {'id': 2, 'name': 'ssx', 'pwd': '456'}
    
        cur.scroll(-1,mode='relative')
        row = cur.fetchone()
        print(row)   # {'id': 2, 'name': 'ssx', 'pwd': '456'}
    
        cur.scroll(0,mode='absolute')
        row = cur.fetchone()
        print(row)   # {'id': 1, 'name': 'wll', 'pwd': '123'}
    
        cur.close()
        conn.close()

           示例四:fetchmany(n) - 获取n行数据 

        import pymysql
        conn = pymysql.connect(host='localhost', port=3306, user='root', password='123', db='mydb', charset='utf8')
        cur = conn.cursor(cursor=pymysql.cursors.DictCursor)
        sql = 'select * from userinfo'
        effct_row = cur.execute(sql)
    
        rows = cur.fetchmany(2)   # 获取2 条数据
        print(rows)
        # 结果为:
        # [
        # {'id': 1, 'name': 'wll', 'pwd': '123'}, 
        # {'id': 2, 'name': 'ssx', 'pwd': '456'}
        # ]
        cur.close()
        conn.close()

    二、索引

    1、索引的介绍

           数据库中专门用于帮助用户快速查找数据的一种数据结构,类似于字典中的目录,查找字典内容时可以根据目录查找到数据的存放位置,然后直接获取。

    2、索引的作用

           约束和加速查找。

    3、常见的几种索引

           1)普通索引:加速查找作用

                  示例一:创建表的时候设置普通索引

        create table userinfo(
            id int not null auto_increment primary key,
            name varchar(32) not null,
            email varchar(64) not null,
            index ix_name(name)   # 设置普通索引
        );

                  示例二:已经创建完表之后单独创建普通索引(慢)

        create index 索引的名字 on 表名(列名);

                  示例三:删除普通索引(快)

        drop index 索引名 on 表名;

                  示例四:查看索引

        show index from 表名;

           2)唯一索引:加速查找和约束唯一作用(可以为空)

                  示例一:创建表的时候设置唯一索引

        create table userinfo(
            id int not null auto_increment primary key,
            name varchar(32) not null,
            email varchar(64) not null,
            unique index ix_name(name)  # 设置唯一索引(name就有了唯一的约束)
        );

                  示例二:单独设置唯一索引

        create unique index 索引名 on 表名(列名);

                  示例三:删除唯一索引

        drop index 索引名 on 表名;

           3)主键索引:加速查找和约束唯一作用(不可以为空)

             示例一:创建表的时候设置主键索引

        create table userinfo(
            id int not null auto_increment primary key,  # 设置主键就是主键索引
            name varchar(32) not null,
            email varchar(64) not null,
        );
        或者
        create table userinfo(
            id int not null auto_increment,
            name varchar(32) not null,
            email varchar(64) not null,
            primary key(id)      # 设置主键,就创建主键索引
        );

             示例二:单独创建主键索引

        alter table 表名 add primary key(列名);

             示例三:删除主键索引

        alter table 表名 drop primary key;
        alter table 表名  modify 列名 int, drop primary key;

           4)联合索引(多列)

                  又分为:联合主键索引、联合唯一索引、联合普通索引。

                  应用场景:频繁的同时使用n列来进行查询,

                         如:where name = ‘alex’and email = ‘alex@qq.com’;

                  示例一:创建联合普通索引

        create index 索引名 on 表名(列名1,列名2);
    4、覆盖现象和合并现象

           示例一:查找字段和索引字段相同,则直接在索引文件中获取数据

        select name from userinfo where name = 'alex50000';  # 直接索引文件中获取
        select * from userinfo where name = 'alex50000'; # 先查索引文件,再查物理表

           示例二:多个单列索引同时作为条件时,索引则合并使用

        select * from  userinfo where name = 'alex13131' and id = 13131;
    5、如何正确使用索引

           数据库表中添加索引后确实会让查询速度起飞,但前提必须是正确的使用索引来查询,如果以错误的方式使用,则即使建立索引也会不奏效。

           使用索引,我们必须遵循以下几点:

                  1)创建索引;

                  2)命中索引;

                  3)正确使用索引;

           准备一个含有300w数据的表:

        # 1. 准备表
        create table userinfo(
            id int,
            name varchar(20),
            gender char(6),
            email varchar(50)
        );
    
        # 2. 创建存储过程,实现批量插入记录
        delimiter $$     # 声明存储过程的结束符号为$$
        create procedure auto_insert1()
        BEGIN
          declare i int default 1;
          while(i<3000000)do
            insert into userinfo values(i,concat('alex',i),'male',concat('egon',i,'@oldboy'));
            set i=i+1;
          end while;
        END$$    # $$结束
        delimiter ;     # 重新声明分号为结束符号
    
        # 3. 查看存储过程
        show create procedure auto_insert1G;
    
        # 4. 调用存储过程
        call auto_insert1();

           测试如下查询语句,体会以下不正确使用索引的情况,理解如何正确使用索引:

        # 示例一:like '%xx'
        select * from userinfo where name like '%al';
    
        # 示例二:使用函数
        select * from userinfo where reverse(name) = 'alex333';
    
        # 示例三:or
        select * from userinfo where id = 1 or email = 'alex122@oldbody';
        # 注意:当or条件中有未建立索引的列才失效,以下两种会走索引:
        select * from userinfo where id = 1 or name = 'alex1222';
        select * from userinfo where id = 1 or email = 'alex122@oldbody' and name = 'alex112'
    
        # 示例四:类型不一致
        select * from userinfo where name = 999; # 表中name字段是字符串
        # 解释:若某字段是字符串类型,则查询条件中必须带引号,否则即使该字段有索引,速度也很慢
    
        # 示例五:!=
        select count(*) from userinfo where name != 'alex';
        # 注意:如果是主键,则还是会走索引
    
        # 示例六:>
        select * from userinfo where name > 'alex';
        # 注意:如果是主键或者字段是整数类型,则还是会走索引,如下:
        select * from userinfo where id > 123
        select * from userinfo where num > 123
    
        # 示例七:order by
        select email from userinfo order by name desc;
        # 注意:当根据索引排序的时候,选择的映射如果不是索引,则不走索引
    
        # 示例八:联合索引最左前缀匹配

    PS:什么是最左前缀匹配?

      create index ix_name_email on userinfo(name,email);  # 创建联合索引,name在左

      select * from userinfo where name = 'alex';                 # 查找速度快

      select * from userinfo where name = 'alex' and email='alex@oldBody';  # 快

      select * from userinfo where  email='alex@oldBody';    # 慢

           分析:如果创建了联合索引,如上边代码,创建name和email联合索引,那么查询

                  (1)name和email时 -- 使用索引,速度快

                  (2)name            -- 使用索引,速度快

                  (3)email            -- 不使用索引,速度慢

           注意:对于同时搜索n个条件时,组合索引的性能 > 多个单列索引合并的性能。

    6、使用索引的注意事项

           1)避免使用select *;

      2)count(1)或count(列) 代替count(*);

      3)创建表时尽量使用char代替varchar;

      4)表的字段顺序固定长度的字段优先;

      5)组合索引代替多个单列索引(经常使用多个条件查询时);

      6)尽量使用短索引(create index ix_title on tb(title(16));仅限特殊的数据类型text);

      7)使用连接(join)来代替子查询;

      8)连表时注意条件类型需一致;

      9)索引散列(有重复且种类少)不适用于建索引,例如:性别不合适;

    7、执行计划

           explain + 查询SQL :用于显示SQL执行信息参数,根据参考信息可以进行SQL优化。如下示例:

      mysql> explain select * from userinfo;
      +----+-------------+----------+------+---------------+------+---------+------+---------+-------+
      | id | select_type | table    | type | possible_keys | key  | key_len | ref  | rows    | Extra |
      +----+-------------+----------+------+---------------+------+---------+------+---------+-------+
      |  1 | SIMPLE      | userinfo | ALL  | NULL          | NULL | NULL    | NULL | 2973016 | NULL  |
      +----+-------------+----------+------+---------------+------+---------+------+---------+-------+

      mysql> explain select * from (select id,name from userinfo where id <20) as A;   +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+   | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |   +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+   | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 19 | NULL |   | 2 | DERIVED | userinfo | range | PRIMARY | PRIMARY | 4 | NULL | 19 | Using where |   +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+

           参数说明:

        select_type(查询类型):
            SIMPLE      ---     简单查询
            PRIMARY    ---      最外层查询
            SUBQUERY  ---       映射为子查询
            DERIVED    ---      子查询
            UNION      ---     联合
            UNION RESULT  ---  使用联合的结果
        table(正在访问的表名)
        type(查询时的访问方式):
            性能:all < index < range < index_merge < ref_or_null < ref < eq_ref < system/const
                all --- 全表扫描,对于数据表从头到尾找一遍(如果有limit限制,则找到之后不再向下找);
                index --- 全索引扫描,对索引从头到尾找一遍;
                range --- 对索引列进行范围查找;
                index_merge --- 合并索引,使用多个单列索引搜索;
                ref --- 根据索引查找一个或多个值;
                eq_ref --- 连接时使用primary key或unique类型;
                system --- 系统,表仅有一行(=系统表),这是const连接类型的一个特例;
                const --- 常量,表最多有一个匹配行,因为仅有一行,在这行的列值可被优化器剩余部分认为是常数,const表很快,因为它们只读取一次;
        possible_keys(可能使用的索引)
        key:真实使用的
        key_len(MySQL中使用索引字节长度):
        rows(MySQL估计为了找到所需的行而要读取的行数,只是预估值):
        extra(该列包含MySQL解决查询的详细信息):
            Using index --- 此值表示mysql将使用覆盖索引,以避免访问表。不要把覆盖索引和index访问类型弄混了;
            Using where --- 这意味着mysql服务器将在存储引擎检索行后再进行过滤,许多where条件里涉及索引中的列,当(并且如果)它读取索引时,就能被存储引擎检验,因此不是所有带where子句的查询都会显示“Using where”。有时“Using where”的出现就是一个暗示:查询可受益于不同的索引;
            Using temporary --- 这意味着mysql在对查询结果排序时会使用一个临时表;
            Using filesort --- 这意味着mysql会对结果使用一个外部索引排序,而不是按索引次序从表里读取行。mysql有两种文件排序算法,这两种排序方式都可以在内存或者磁盘上完成,explain不会告诉你mysql将使用哪一种文件排序,也不会告诉你排序会在内存里还是磁盘上完成;
            Range checked for each record(index map: N) --- 这个意味着没有好用的索引,新的索引将在联接的每一行上重新估算,N是显示在possible_keys列中索引的位图,并且是冗余的;
        
    8、慢日志记录

           开启慢查询日志,可以让MySQL记录下查询超过指定时间的语句,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能。

           1)进入MySQL查询是否开启了慢查询日志

        show variables like 'slow_query%';

             参数解释:

          slow_query_log:慢查询开启状态(OFF未开启,ON为开启);

          slow_query_log_file:慢查询日志存放的位置;

           2)查看慢查询超时时间(默认10秒)

        show variables like 'long%';

           3)开启慢日志方式一:

        set global slow_query_log=1;   # 1表示开启,0表示关闭

                  注意:设置关要退出重新进入才生效。

           4)开启慢日志方式二(推荐):

                  修改my.ini配置文件(mac中为my.cnf文件),找到[mysqld],在下面添加:

        slow_query_log = 1
        slow_query_log_file=C:mysql-5.6.40-winx64datalocalhost-slow.log
        long_query_time = 1

                  参数解释:

          slow_query_log:慢查询开启状态,1为开启

          slow_query_log_file:慢查询日志存放的位置

          long_query_time:查询超过多少秒才记录,默认10秒,修改为1秒

    9、分页性能相关方案

      先回顾一下,如何取当前表中的前10条记录,每十条取一次,依次得到每页数据,如下:

        # 第1页:
        select * from userinfo limit 0,10;
        # 第2页:
        select * from userinfo limit 10,10;
        # 第3页:
        select * from userinfo limit 20,10;
        # 第4页:
        select * from userinfo limit 30,10;
        ......
        # 第200001页
        select * from userinfo limit 2000000,10;

      PS:我们会发现,越往后查询,需要的时间约长,此方法要进行全文扫描查询,越往后查,扫描查询的数据越多。

    解决方案:

           1)只有上一页和下一页的情况

                  前提:做一个记录,记录当前页的第一条数据min_id或者最后一条数据max_id

        # 下一页
        select * from userinfo where id>max_id limit 10;
        # 上一页
        select * from userinfo where id<min_id order by id desc limit 10;

           2)中间有页码的情况

        select * from userinfo where id in(
            select id from (select * from userinfo 
                where id > pre_max_id limit (cur_max_id-pre_max_id)*10) as A 
            order by A.id desc limit 10
        );
  • 相关阅读:
    给你一个亿-电视节目总结
    给你一个亿-电视节目总结
    我的写作、爱好和好友
    我的写作、爱好和好友
    互联网和移动互联网怎么挣钱?
    互联网和移动互联网怎么挣钱?
    IT人都很忙(茫)
    Java实现 LeetCode 345 反转字符串中的元音字母
    Java实现 蓝桥杯 算法训练 谁干的好事?
    Java实现 蓝桥杯 算法训练 谁干的好事?
  • 原文地址:https://www.cnblogs.com/li-li/p/9810867.html
Copyright © 2020-2023  润新知