zoukankan      html  css  js  c++  java
  • 2018.11.27数据库索引,元类,单例

    一.索引

    1.1什么是索引

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

    搜索引导, 所以是一种单独的,物理的 有序的 存储结构,用于加速查询

            例如: 字典  书的目录  车票上的车厢号

    1.2为什么需要索引

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

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

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

    因为项目运行中,查询操作非常频繁,为了提高用户体验,要提高查询的速度,

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

    1.3索引带来的影响

      1.加速查询

      2.降低写入(增加 删除 修改)速度

      3.会额外占用磁盘空间

    1.4索引数据结构剖析

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

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

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

    1.5聚集索引

    1.聚集索引 就是主键索引

    行中的完整记录存在聚集索引的叶子节点上

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

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

    1.6辅助索引 除了主键的索引

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

      叶子节点 存储索引字段的值 还有 主键的值

      使用辅助索引时 会产生两种现象

      1.回表    要查的数据就不在辅助索引中 需要到聚集索引中查找

    (获得所在行的主键值回到聚集索引再取查找其他字段称为回表)

      2.覆盖索引  要查的数据就在辅助索引中

    查询速度对比:

    聚集索引 > 覆盖索引 > 非覆盖索引(回表)

    1.7正确使用索引

    案例:

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

    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字段里所有的记录都是jack所以相同的太多添加反而增加I0操作所以速度反而低了)
    #由于name字段的区分度非常低 完全无法区分 ,因为值都相同 这样一来B+树会没有任何的子节点,像一根竹竿每一都匹配相当于,有几条记录就有几次io ,所有要注意 区分度低的字段不应该建立索引,不能加速查询反而降低写入效率,
    #同理 性别字段也不应该建立索引,email字段更加适合建立索引
     
    # 修改查询语句为
    select count(*) from usr where name = "aaaaaaaaa";
    #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.com";
    #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";
    #1 row 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两边都有索引才会使用索引 

    使用or的时候  如果两边都有索引 会使用索引,但是注意 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;
    drop index gender_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;
     

           

    索引总结:

    索引的正确使用姿势

    命中索引 ,条件中有索引字段匹配上

    0优先使用聚集索引

    1.无论索引如何设计 无法降低范围查询的查询速度

        select count(*) from usr where id > 1;

    即使命中索引也无法提高效率

    2.索引不应该加在值重复度很高的字段上 应该加在重复度低的字段,索引的字段数据量应该尽可能小

    3. 使用and时 当 条件中出现多个索引命中时  会自定找一个区分度最高的索引来使用

    4.使用or的时候  如果两边都有索引 会使用索引,但是注意 or两边都要执行  顺序依然从左往右,只有一边有索引会不会使用索引?  不会使用 无法加速查询

    5.优化查询 不仅仅要加索引,sql语句也需要优化 使其能命中索引

    你的条件中应该使用区别度高的索引

    6.联合索引

    为是什么使用它

    降低资源的占用 , 降低增删改的时间   会比单个字段的索引快

    建立联合索引时 应该把区分度高放最左边  区分度低的依次往右放

    按照区分度的高低 从左往右  依次排列

    查询中 把区分度高放左边 查询时 尽可能使用最左边的索引

    使用and时 无所谓书写顺序 会自动找区分度最高的

    注意联合索引在查询时  如果压根没用到最左侧索引 不能加速查询

    缺点

            索引越多越好?

         索引能够提升效率

        同时降低了写入速度

    增加额外的磁盘占用

    正常开发时*******

    优先使用聚集索引

    再次 使用联合索引  如果你的条件不包含最左侧索引  不能加速查询 这时候就应该使用单个字段索引

    二.  元类

    class Dog:
        def __init__(self):
            print("狗初始化了")
        color = "red"
        def talk(self):
            print("狗在叫!")
    d1 = Dog()
    #print(Dog.__dict__)
    #d1.talk()
    # 查看对象时哪个类实例化出来的
    print(d1.__class__)# <class '__main__.Dog'>
    print(Dog.__class__)<class 'type'>
    print(type(d1)) # <class '__main__.Dog'>
    print(type(Dog)) <class 'type'>
    可以推导出===>产生DOG的过程一定发生了:Teacher=type(...)

    用于实例化产生类的类称之为元类就是此时的type;所有类产生时都被tpye实例化

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

    2.创建类的流程分析

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

    1、 类名class_name='Teacher

    2、 基类class_bases=(object,)

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

    class_name = "pig"
    bases = (object,)
    pic_dict = {}
    class_body = """
    def __init__(self):
        print("猪初始化了")

    color = "red"

    def talk(self):
        print("猪在叫!")
    """
    # 执行一堆字符串代码  将生产的内容放到pic_dict中
    exec(class_body,{},pic_dict)#
    # print(pic_dict,type(pic_dict))
    # 调用type产生一个类
    c = type(class_name,bases,pic_dict)
    print(c)
    print(Dog)#和上面自己定义一个类输出的结果是一样的
    """
        默认情况下  所有的类都是通过type这个元类示例化的
        我们完全可以自己来实例化
        元类的作用?
        用于创建类的类 称为元类
    """
     
    3.通过元类来控制类的创建过程
    class MyMetaclass(type):
               # 什么时候执行?  MyMetaclass 定义一个类是 系统会自动去调用元类
               def __init__(cls, class_name, bases, namespace):
                                    # 既然创建类时 会自动执行 该方法 那完全编写一些逻辑在init中 来控制类的创建
                                    # 首字母必须大写否则不让创建
                                    if not class_name.istitle():
                                                         raise TypeError("类名首字母必须大写!")
     
                                    if object not in bases:
                                                         raise TypeError("必须显式的继承object")
     
                                    print(cls)
                                    print(class_name)
                                    print(bases)#object写在定义类里
                                    print(namespace)
     
     
    # 当求解释器执行到这行diamante时  自动调用了MyMetaclass
    class Foo(metaclass=MyMetaclass):  # 等同于 Foo = MyMetaclass()
               attr = 123
               pass
    class foo(metaclass=MyMetaclass):#首字母小写报错
               attr = 123
               pass
    class Foo1(object,metaclass=MyMetaclass): # 等同于 Foo = MyMetaclass()
               attr = 123
               pass
     
     
     
     
     
    4自定义元类控制类的调用
    控制类的调用过程 关键在于call函数, 类也是对象,调用类必然也会执行call函数

    Class Bar:
        def __call__(self, *args, **kwargs):
            print("run call")
    # 调用类时  还是 调用对象时执行
    b1 = Bar()
    b1()
    print(b1)

    # 在调用对象时 自动触发__call__的执行
    # 推导 b1是Bar的实例   调用b1 会触发Bar中的__call__
    # Bar 是type的实例  调用Bar 应当触发type中的__call__
     
    # 类中的__init__也被拦截了
    class MyMetaClass(type):

        def __call__(self, *args, **kwargs):
            print("MyMetaClass __call__ run!")
            print(self)

            #需求 判断实例化时的参数必须是字符串类型
            #isinstance(args[0],str)
            if type(args[0]) != str:
                raise TypeError("姓名必须是字符串类型!")
            # 这是自定义元类时  必须要有的模板  以保证可以正常实例化产生对象
            obj = object.__new__(self)
            obj.__init__(*args,**kwargs)#被拦截
            return obj

    class Foo(metaclass=MyMetaClass):
        def __init__(self,name):
            self.name = name
        pass
    # 调用类本质上就是在调用__call__ 其返回值表示实例化得到的对象
    res = Foo("a")
    print(res)

    # 调用一个类   创建出一个空对象,调用__init__来完成对象的初始化,返回该对象
    # 控制类的调用 也就是实例化过程    核心函数 元类中的__call__
    # 需要注意的是,在__call__中应当先完成基础的逻辑 1.创建空对象,2.执行__init__ 3.返回新对象
    # 在此基础上添加额外的业务逻辑
     
     

    三 单例

    什么是单例,

    单例是指的是单个实例,指的是 一个类有且仅有一个实例 就叫单例为什么要用单例

    实现单例 就通过判断是否已经创建过对象

    为什么要用单例

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

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

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

    class User:

        def __init__(self,name,age,sex):

            self.name = name

            self.age = age

            self.sex = sex

        instance = None

        # 通过指定的方法来获取实例 而不是调用类来创建新对象

        @classmethod

        def get_instance(cls,name,age,sex):

            if not cls.instance:

                cls.instance = cls(name,age,sex)

            return cls.instance

        @staticmethod

        def get_instance2(name,age,sex):

            if not User.instance:

                User.instance = User(name,age,sex)

            return User.instance

    # u1 = User("张三",20,"man")

    # u2 = User("张三",20,"man")

    u1 = User.get_instance("张三",20,"man")

    print(u1)# <__main__.User object at 0x0038CFF0>

    #通过方法可以保证u1和u2对象的一致,但不能阻止使用者直接用类来生成新的对象.

    u2 = User.get_instance2("张三",20,"man")

    print(u2)# <__main__.User object at 0x0038CFF0>

    # 通过classMethod 可以完成单例  但是还是可以通过直接调用;类产生新对象  此时就需要用到元类

    u3 = User("张三",20,"man")#<__main__.User object at 0x003A3030>

    #u3和上面u1.u2不一致使用者还是可以通过直接调用类产生新对象

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

    使用元类创造单利

    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)
                                    #arg收到的参数是下面init,属性里定义的参数一致,
                # 返回对象
            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("你发如雪!")
    p2 =CDPlayer("夜曲")
    print(p1)# <__main__.CDPlayer object at 0x008B90B0>
    print(p2)# <__main__.CDPlayer object at 0x008B90B0>
    p1.play("菊花台")
    p1.play("菊花台2")
    p1.play("菊花台3")

    # 元类实现单例 就是拦截了元类中的__call__的正常执行  使得创建对象都必须经过自己判断逻辑(这样就不能通过类(CDPlay)创建新的对象)

    # 类中的__init__也被拦截了

  • 相关阅读:
    为什么 PCB 生产时推荐出 Gerber 给工厂?
    Fedora Redhat Centos 有什么区别和关系?
    【KiCad】 如何给元件给元件的管脚加上划线?
    MCU ADC 进入 PD 模式后出现错误的值?
    FastAdmin 生产环境升级注意
    EMC EMI 自行评估记录
    如何让你的 KiCad 在缩放时不眩晕?
    KiCad 5.1.0 正式版终于发布
    一次单片机 SFR 页引发的“事故”
    java基础之集合
  • 原文地址:https://www.cnblogs.com/jutao/p/10064332.html
Copyright © 2011-2022 走看看