zoukankan      html  css  js  c++  java
  • 数据库学习

      今天学习了数据库的索引和面向对象中的元类

      一、索引

        什么是索引

          在关系数据库中,索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构;也称之为key

          索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。

        为什么需要索引

          思考:一个项目正常运行后,对数据库的操作中,哪些操作是最频繁的?

          对数据库的写操作(增加、删除、修改)频繁吗?

          对数据库的读操作(查询)频繁吗?

          相比较下,对数据的读操作会更加频繁,比例在10:1左右,也就是说对数据库的查询操作是非常频繁的

          随着时间的推移,表中的记录会越来越多,此时如果查询速度太慢的话对用户体验是非常不利的

          索引是提升查询效率最有效的手段

          在数据库中插入数据会引发索引的重建

          简单的说索引就是用来帮我们加快查询速度的

        索引的实现原理

          如何能实现加快查询的效果

            索引最终的目的是要尽可能降低io次数,减少查找的次数,以最少的io找到需要的数据,此时B+树闪亮登场

            光有数据结构还不行,还需要有对应的算法做支持,就是二分查找法

            有了B+数据结构后查找数据的方式就不再是逐个的对比了,而是通过二分查找法来查找

            另外,其实大多数文件系统都是使用B+来完成的!

          应该尽可能的将数据量小的字段作为索引,这样一个叶子节点能存储的数据就更多,从而降低树的高度;

          例如:name和id,应当将id设置为索引而不是name

        聚集索引

          叶子节点保存的就是完成的一行记录,如果设置了主键,主键就作为聚集索引

          如果没有主键,则找第一个NOT NULL 且 QUNIQUE的列作为聚集索引

          如果也没有这样的列,innoDB会在表内自动产生一个聚集索引,它是自增的

        辅助索引

          除了聚集索引之外的索引都称之为辅助索引或第二索引,包括foreign key 与 unique

          辅助索引的特点:

          其叶子节点保存的是索引数据与所在行的主键值,InnoDB用这个 主键值来从聚集索引中搜查找数据

        覆盖索引

          覆盖索引指的是需要的数据仅在辅助索引中就能找到:

          

    # 假设stu表的name字段是一个辅助索引
    select name from stu where name = "jack";

          这样的话则不需要再查找聚集索引数据已经找到

        回表

          如果要查找的数据在辅助索引中不存在,则需要回到聚集索引中查找,这种现象称之为回表

    # name字段是一个辅助索引 而sex字段不是索引
    select sex from stu where name = "jack";

          需要从辅助索引中获取主键的值,再拿着主键值到聚集索引中找到sex的值

          查询速度对比:

          聚集索引 > 覆盖索引 > 非覆盖索引

        正确使用索引

          案例:

          首先准备一张表数据量在百万级别

    create table usr(id int, name char(10),gender char(3),email char(30));
    #准备数据
    delimiter //
    create procedure addData(in num int)
    begin
    declare i int default 0;
    while i < num do
        insert into usr values(i,'jack','m',concat('xxxx',i,'@qq.com'));
    set i = i+1;
    end while;
    end //
    delimiter ;
    #执行查询语句  观察查询时间
    select count(*) from usr where id = 1;
    # 1 row in set (3.85 sec)
    # 时间在秒级别 比较慢
    
    
    1.
    # 添加主键
    alter table usr add primary key(id);
    # 再次查询
    select count(*) from usr where id = 1;
    # 1 row in set(0.00 sec)
    # 基于在毫秒级就能完成 提升非常大
    
    2.
    # 当条件为范围查询时
    select count(*) from usr where id > 1;
    # 速度依然很慢 对于这种查询没有办法可以优化因为需要的数据就是那么多
    # 缩小查询范围  速度立马就快了
    select count(*) from usr where id > 1 and id < 10;
    
    #当查询语句中匹配字段没有索引时 效率测试 
    select count(*) from usr where name = 'jack';
    # 1 row in set (2.85 sec)
    # 速度慢
    
    
    
    3.
    # 为name字段添加索引
    create index name_index on usr(name);
    # 再次查询
    select count(*) from usr where name = 'jack';
    # 1 row in set (3.89 sec)
    # 速度反而降低了 为什么?
    # 由于name字段的区分度非常低 完全无法区分 , 因为值都相同 这样一来B+树会没有任何的子节点,像一根竹竿每一次都匹配 相当于有几条记录就有几次io , 所以要注意 区分度低的字段不应该建立索引,不能加速查询反而降低写入效率,
    # 同理 性别字段也不应该建立索引,email字段更加适合建立索引
    
    # 修改查询语句为
    select count(*) from usr where name = 'aaaaaa';
    # 1 row in set (0.00 sec) 速度非常快因为在树根位置就已经判断出树中没有这个数据 全部跳过了
    # 模糊匹配时
    select count(*) from usr where name like 'xxx'; #
    select count(*) from usr where name like 'XXX%'; #
    select count(*) from usr where name like '%xxx'; #
    # 由于索引是比较大小 会从左边开始匹配 很明显所有字符都能匹配% 所以全都匹配了一遍
    
    4.索引字段不能参加运算
    select count(*) from usr where id * 12 = 120;
    # 速度非常慢原因在于 mysql需要取出所有列的id 进行运算之后才能判断是否成立
    # 解决方案
    select count(*) from usr where id = 120/12;
    # 速度提升了 因为在读取数据时 条件就一定固定了 相当于
    select count(*) from usr where id = 10;
    # 速度自然快了
    
    5.有多个匹配条件时 索引的执行顺序 and和or
    # 先看and
    # 先删除所有的索引
    alter table usr drop primary key;
    drop index name_index on usr;
    
    #测试
    select count(*) from usr where name = 'jack' and gender = 'm' and id = 1 and email = 'xxxx2@qq.com';
    #1 row in set (1.34 sec) 时间在秒级
    
    # 为name字段添加索引
    create index name_index on usr(name);
    # 测试
    select count(*) from usr where name = 'jack' and gender = 'm' and id = 1 and email 'xxxx2@qq.con';
    # 1 row in set (17.82 sec) 反而时间更长了
    
    # 为gender字段添加索引
    create index gender_index on usr(gender);
    # 测试
    select count(*) from usr where name = 'jack' and gender = 'm' and id = 1 and email = 'xxxx2@qq.com';
    # 1 row in set (16.83 sec) gender字段任然不具备区分度
    
    # 为id加上索引
    alter table usr add primary key(id);
    # 测试
    select count(*) from usr where name = 'jack' and gender = 'm' and id = 1 and email = 'xxxx1@qq.com';
    # 1row in set (0.00 sec) id 字段区分度高 速度提升
    # 虽然三个字段都有索引 mysql并不是从左往右傻傻的去查 而是找出一个区分度高的字段优先匹配
    # 改为范围匹配
    select count(*) from usr where name = 'jack' and gender = 'm' and id > 1 and email = 'xxxx1@qq.com';
    # 速度变慢了
    
    # 删除id 索引 为email建立索引
    alter table usr drop primary key;
    create index email_index on usr(email);
    # 测试
    select count(*) from usr where name = 'jack' and gender = 'm' and id  =1 and email = 'xxxx2@qq.com';
    # 1 row in set(0.00 sec) 速度非常快
    # 对于 or条件 都是从左往右匹配
    select count(*)  from usr where name = 'jackxxxx' or email = 'xxxx0@qq.com';
    # 注意 必须or两边都有索引才会使用索引
    
    6.多字段联合索引
    为什么需要联合索引
    案例:
    select count(*) from  usr where name = 'jack' and gender = 'm' and id >3 and email = 'xxxx2@qq.com';
    假设所有字段都是区分度非常高的字段,那么除了id为谁添加索引都能够提升速度,但是如果sql语句中没有出现所有字段,那就无法加速查询,最简单的办法是为每个字段都加上索引,但是索引也是一种数据,会占用内存空间,并且降低写入效率
    此处就可以使用联合索引,
    联合索引最重要的是顺序 按照最左匹配原则 应该将区分度高的放在左边 区分度低的放到右边
    # 删除其他索引
    drop index name_index on usr;
    drop index email_index on usr;
    # 联合索引
    create index mul_index on usr(email,name,gender,id);
    # 查询测试
    select count(*) from usr where name = 'xx' and id = 1 and email = 'xx';
    只要语句中出现了最左侧的索引(email)无论在前在后都能提升效率
    drop index mul_index on usr;

      二、元类

          1.什么是元类

            一切源自于一句话:python中一切皆为对象。既然如此类是不是也是对象呢?

    class Teacher(object):
        school = 'tsinghua'
        def __init__(self,name,age):
            self.name = name
            self.age = age
        def say(self):
            print('%s says welcome to the Beijing' %self.name)
    
    t1 = oldboyTeacher('egon',18)
    print(type(t1))    #查看对象t1的类是<class '__main__.oldboyTeacher'>

        所有的对象都是实例化或者说调用类而得到的(调用类的过程称之为类的实例化),比如对象t1是调用类Teacher得到的

        一切皆对象的话  类也必然是一个对象,验证一下

      

    tcls = Teacher
    li = [Teacher]
    def func(cls):
        print(cls)
    func(Teacher)
    # 完全没问题把他当作对象来使用 和其他对象没有任何区别

        t1是通过Teacher实例化得到的,那Teacher对象是哪个类实例化的呢?

    print(type(Teacher))
    # <class 'type'>

        可以推导出===>产生Teacher的过程一定发生了:Teacher = type(...)

        用于实例化产生类的类称之为元类 就是此时的type类;

        Teacher是通过type实例化得到的,既然如此,是不是可以自己调用type来实例化一个class呢?

        2.创建类的流程分析

          class关键字在帮我们创建类时,必然帮我们调用了元类Teacher = type(...),那调用type时传入的参数是什么呢?必然是类的关键组成部分,一个类有三大组成部分,分别是

          1.类名class_name = 'Teacher'

          2.基类们class_bases = (object,)

          3.类的名称空间class_dic,类的名称空间是执行类体代码而得到的

          调用type时会依次传入以上三个参数

          自己来实例化一个类

        

    class_name = 'Teacher'
    class_body = '''
        def __init__(self,name,age):
            self.name = name
            self.age = age
        def say(self):
            print('%s says welcome to the Beijing' %self.name)      
    '''
    class_dic = exce(class_body)
    bases = (object,)
    Teacher = type(class_name
    ,bases
    ,class_body)

          综上,class关键字帮我们创建一个类应该细分为以下四个过程

          1.获取类名

          2.获取基类

          3.获取名称空间

          4.实例化元类得到类

        补充__call__函数得到执行时机

        该方法会在调用对象时自动触发执行(对象加括号)

    class Foo:
        def _-call__(self,*args,**kwargs):
            print('run')
    f = Foo()
    f()

        自定义元类控制类的创建

          一个类没有声明自己的元类,默认他的元类就是type,除了使用内置元类type,我们也可以通过继承type来自定义元类,然后使用metaclass关键字参数为一个指定元类

    class Mymeta(type):    #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
        pass
    class Teacher(object,metaclass = Mymeta):    #Teacher = Mymete('Teacher',(object),{...})
        school = 'tsinghua'
        def __init__(self,name,age):
            self.name = name
            self.age = age
        def say(self):
            print('%s says welcome to the Beijing'%self.name)

        需求

        1.规范类名必须大写

        2.类中必须包含文档注释

    class MyMate(type):
        def __init__(self,name,bases,dic):
            print('run')
            if not dic.get('__doc__')
                raise TypeError('类必须有文档注释!')
            if not name.istitle():
                raise TypeError('类名必须大写开头!')
            super().__init__(name,bases,dic)
    class Foo(object,metaclass = MyMate):
        pass

        自定义元类控制类的调用

          控制类的调用过程 关键在于call函数,类也是对象,调用类必然也会执行call函数

    class MyMate(type):
        def __init__(self,name,bases,dic):
            print('run')
            if not dic.get('__doc__'):
                raise TypeError('类必须有文档注释!')
            if not name.istitle():
                raise TypeError('类名必须大写开头!')
            super().__init__(name,bases,dic)
        def __call__(self,*args,**kwargs):
            #创建空对象
            #调用init
            #返回初始化后的对象
            obj = object.__new__(self)
            self.__init__(obj,*args,**kwargs)
            return obj
    class Foo(object,metaclass = MyMate):
        def __init__(self):
            print('初始化对象')
        pass
    f = Foo()
    print(f)

      元类实现单例

          什么是单例,

          单例指的是单个实例,值一个类只能有一个实例对象

          为什么要用单例

          当一个类的实例中的数据不会变化时使用单例,数据是不变的

          例如开发一个音乐播放器程序,音乐播放器可以封装为一个对象,那你考虑一下,当你切歌的时候,是重新创建一个播放器,还是使用已有的播放器?

          因为播放器中的数据和业务逻辑都是相同的没有必要创建新的,所以最好使用单例模式,以节省资源,

    #使用classmethod 实现单例
    class Player():
        def __init__(self):
            print('创建播放器了')
        __play = None
        @classmethod
        def get_player(cls):
            if not cls.__play:
                cls.__play = Player()
            return cls.__play
    
    p1 = Player.get_player();
    p1 = Player.get_player();
    p1 = Player.get_player();
    p1 = Player.get_player();

        该方法无法避免使用者直接调用类来实例化,这样就不是单例了

        使用元类实现单例模式

    class MyMetaClass(type):
        instance = None
        def __call__(cls,*args,**kwargs):
            if not MyMetaClass.instance:
                MyMetaClass.instance = object.__new__(cls)
              print('创建新的播放器对象')
                MyMetaClass.instance.__init__(*args,**kwargs)
            return MyMetaClass.instance
    
    class CDPlayer(metaclass = MyMetaClass):
        def play(self,music):
            print('切换音乐',music)
        def __init__(self,music_name):
            self.music_name = music_name
    
    p1 = CDPlayer('你发如雪!')
    p1.play('菊花台')
    p1.play('时光机')

        

          

  • 相关阅读:
    对于HTTP过程中POST内容加密的解决方案
    电脑重启后IDEA项目中import class 报错
    前端Button点击无反应--记一次Debug经历
    plsql 中substr函数和instr函数的灵活应用
    浅谈Plsql 中inner join 和left join的使用
    Mysql查询优化
    Mysql单机安裝
    centos查看文件被哪个进程占用
    jvm垃圾收集器详解
    navicat破解
  • 原文地址:https://www.cnblogs.com/xiaocaiyang/p/10029009.html
Copyright © 2011-2022 走看看