limit 订阅
LIMIT是一个英文词语,名词、动词,作名词时意思是“限制;限度;界线”,作及物动词时意思是“限制;限定”。 展开全文
LIMIT是一个英文词语,名词、动词,作名词时意思是“限制;限度;界线”,作及物动词时意思是“限制;限定”。
信息
外文名
LIMIT
词    性
名词、动词
LIMIT单词发音
英[ˈlɪmɪt];美[ˈlɪmɪt] [1] 
收起全文
精华内容
下载资源
问答
  • limit
    千次阅读
    2021-12-12 15:11:15

    背景:

    android11手机,targetVersion已经升上来了,读系统图片库,报如下错误:

    java.lang.IllegalArgumentException: Invalid token LIMIT

    网上随手一搜,知道了原来在android11上,SQL最终在生成阶段会进行检查,直接在sortOrder字段中携带LIMIT是会被禁止的,这是为了防止注入攻击等。androidO上已经有了新的方法来解决这个问题。

    解决:

    既然是androidO就增加了,直接增加一个判断是不是就好了:

            val contentResolver = requireActivity().contentResolver
            val limitClause = if (limit > 0) "LIMIT $limit" else ""
            val selection = "${MediaStore.Images.Media.MIME_TYPE} IN (${SUPPORTED_IMAGE_MIME_TYPES.joinToString(",") { "?" }})"
            val selectionArgs = SUPPORTED_IMAGE_MIME_TYPES.map { it.value }.toTypedArray()
            val cursor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_COLUMNS,
                    Bundle().apply {
                        putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection)
                        putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs)
                        putStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS,
                            arrayOf(MediaStore.Images.Media.DATE_ADDED))
                        putInt(ContentResolver.QUERY_ARG_SORT_DIRECTION,
                            ContentResolver.QUERY_SORT_DIRECTION_DESCENDING)
                        if (limit > 0) {
                            putInt(ContentResolver.QUERY_ARG_LIMIT, limit)
                        }
                        putInt(ContentResolver.QUERY_ARG_OFFSET, 0)
                    },
                    null)
            } else {
                contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                    IMAGE_COLUMNS,
                    selection,
                    selectionArgs,
                    "${MediaStore.Images.Media.DATE_ADDED} DESC $limitClause")
            }

    如此区分版本就OK了,完美。

    然而经过测试,发现这个是有问题的,在version<11时,limit字段没有效果,而version>=11则正常。

    解决again:

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)

     问题解决。

    分析

    先看正常的调用:

        public final @Nullable Cursor query(@RequiresPermission.Read @NonNull Uri uri,
                @Nullable String[] projection, @Nullable String selection,
                @Nullable String[] selectionArgs, @Nullable String sortOrder,
                @Nullable CancellationSignal cancellationSignal) {
            Bundle queryArgs = createSqlQueryBundle(selection, selectionArgs, sortOrder);
            return query(uri, projection, queryArgs, cancellationSignal);
        }

     这个方法最后直接调用了新方法。看createSqlQueryBundle方法的实现:

        public static @Nullable Bundle createSqlQueryBundle(
                @Nullable String selection,
                @Nullable String[] selectionArgs,
                @Nullable String sortOrder) {
    
            if (selection == null && selectionArgs == null && sortOrder == null) {
                return null;
            }
    
            Bundle queryArgs = new Bundle();
            if (selection != null) {
                queryArgs.putString(QUERY_ARG_SQL_SELECTION, selection);
            }
            if (selectionArgs != null) {
                queryArgs.putStringArray(QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs);
            }
            if (sortOrder != null) {
                queryArgs.putString(QUERY_ARG_SQL_SORT_ORDER, sortOrder);
            }
            return queryArgs;
        }

    注意,这个方法把包含LIMIT的sortOrder字段放在了bundle里,key“QUERY_ARG_SQL_SORT_ORDER”。

    而我们直接调用新方法,LIMIT是存在于QUERY_ARG_LIMIT这个key里面。

    接下来,直接通过AIDL调用到Binder服务端了:

                try {
                    qCursor = unstableProvider.query(mPackageName, mAttributionTag, uri, projection,
                            queryArgs, remoteCancellationSignal);
                } catch (DeadObjectException e) {
                    // ...
                }

    这里使用的接口的IContentProvider:

    public interface IContentProvider extends IInterface {
        public Cursor query(String callingPkg, @Nullable String attributionTag, Uri url,
                @Nullable String[] projection,
                @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal)
                throws RemoteException;

    和query相关的就这一个接口了。和IContentProvider相关的服务侧对象是ContentProviderNative类(看起来比较复杂,其实就是把Bundle一锅传过来了):

        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                throws RemoteException {
            try {
                switch (code) {
                    case QUERY_TRANSACTION:
                    {
                        data.enforceInterface(IContentProvider.descriptor);
    
                        String callingPkg = data.readString();
                        String callingFeatureId = data.readString();
                        Uri url = Uri.CREATOR.createFromParcel(data);
    
                        // String[] projection
                        int num = data.readInt();
                        String[] projection = null;
                        if (num > 0) {
                            projection = new String[num];
                            for (int i = 0; i < num; i++) {
                                projection[i] = data.readString();
                            }
                        }
    
                        Bundle queryArgs = data.readBundle();
                        IContentObserver observer = IContentObserver.Stub.asInterface(
                                data.readStrongBinder());
                        ICancellationSignal cancellationSignal = ICancellationSignal.Stub.asInterface(
                                data.readStrongBinder());
    
                        Cursor cursor = query(callingPkg, callingFeatureId, url, projection, queryArgs,
                                cancellationSignal);
                        if (cursor != null) {
                            CursorToBulkCursorAdaptor adaptor = null;
    
                            try {
                                adaptor = new CursorToBulkCursorAdaptor(cursor, observer,
                                        getProviderName());
                                cursor = null;
    
                                BulkCursorDescriptor d = adaptor.getBulkCursorDescriptor();
                                adaptor = null;
    
                                reply.writeNoException();
                                reply.writeInt(1);
                                d.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                            } finally {
                                // Close cursor if an exception was thrown while constructing the adaptor.
                                if (adaptor != null) {
                                    adaptor.close();
                                }
                                if (cursor != null) {
                                    cursor.close();
                                }
                            }
                        } else {
                            reply.writeNoException();
                            reply.writeInt(0);
                        }
    
                        return true;
                    }

    和ContentProviderNative相关的就是Transport类,它是ContentProvider类的一个内类:

        class Transport extends ContentProviderNative {
    
            @Override
            public Cursor query(String callingPkg, @Nullable String attributionTag, Uri uri,
                    @Nullable String[] projection, @Nullable Bundle queryArgs,
                    @Nullable ICancellationSignal cancellationSignal) {
                // ...
                    try {
                        cursor = mInterface.query(
                                uri, projection, queryArgs,
                                CancellationSignal.fromTransport(cancellationSignal));
                    } catch (RemoteException e) {
                        throw e.rethrowAsRuntimeException();
                    } finally {
                        setCallingPackage(original);
                    }
                // ...
            }
        }

    直接调mInterface的query方法,这个mInterface其实就是ContentProvider。

    而ContentProvider的query是个抽象类,最终调用实际的继承了ContentProvider的类。在这里,我们读取媒体库图片,实现类实际就是MediaProvider,而query最终调用到queryInternal方法。

    安卓11对LIMIT的处理

    先看android11上,MediaProvider大概是怎么处理LIMIT的:

    private Cursor queryInternal(Uri uri, String[] projection, Bundle queryArgs,
                CancellationSignal signal, boolean forSelf) throws FallbackException {
            // ...
            if (targetSdkVersion < Build.VERSION_CODES.R) {
                // Some apps are abusing "ORDER BY" clauses to inject "LIMIT"
                // clauses; gracefully lift them out.
                DatabaseUtils.recoverAbusiveSortOrder(queryArgs);
    
                // Some apps are abusing the Uri query parameters to inject LIMIT
                // clauses; gracefully lift them out.
                DatabaseUtils.recoverAbusiveLimit(uri, queryArgs);
            }
            // ...
            final Cursor c = qb.query(helper, projection, queryArgs, signal);
        }

    可见,先做了一堆乱七八糟操作,最后通过qb.query真实地拼接SQL语句,参数全在Bundle里。再看这个recoverAbusiveLimit方法:

        public static void recoverAbusiveLimit(@NonNull Uri uri, @NonNull Bundle queryArgs) {
            final String origLimit = queryArgs.getString(QUERY_ARG_SQL_LIMIT);
            final String uriLimit = uri.getQueryParameter("limit");
    
            if (!TextUtils.isEmpty(uriLimit)) {
                // Yell if we already had a group by requested
                if (!TextUtils.isEmpty(origLimit)) {
                    throw new IllegalArgumentException(
                            "Abusive '" + uriLimit + "' conflicts with requested '" + origLimit + "'");
                }
    
                Log.w(TAG, "Recovered abusive '" + uriLimit + "' from '" + uri + "'");
    
                queryArgs.putString(QUERY_ARG_SQL_LIMIT, uriLimit);
            }
        }

    翻译一下,其实就是如果用新的query接口填了LIMIT,并且还在URI中也填LIMIT就直接报错了。并且如果LIMIT是放在URI中,也统一放到QUERY_ARG_SQL_LIMIT这个key里处理。

    pre-11版本对LIMIT的处理

    再看android10的MediaProvider的关键处理:

        private Cursor queryInternal(Uri uri, String[] projection, Bundle queryArgs,
                CancellationSignal signal) {
            // ...
    
            SQLiteQueryBuilder qb = getQueryBuilder(TYPE_QUERY, uri, table, queryArgs);
            String limit = uri.getQueryParameter(MediaStore.PARAM_LIMIT);
            // ...
    
            final Cursor c = qb.query(db, projection,
                    selection, selectionArgs, groupBy, having, sortOrder, limit, signal);
    
            // ...
        }

    qb.query中传入了limit,而这个limit竟然是从URI中拿的,和QUERY_ARG_SQL_LIMIT半毛钱关系都没有。

    追踪SQLiteQueryBuilder.query方法,最后到buildQueryString方法:

        public static String buildQueryString(
                boolean distinct, String tables, String[] columns, String where,
                String groupBy, String having, String orderBy, String limit) {
            if (TextUtils.isEmpty(groupBy) && !TextUtils.isEmpty(having)) {
                throw new IllegalArgumentException(
                        "HAVING clauses are only permitted when using a groupBy clause");
            }
    
            StringBuilder query = new StringBuilder(120);
    
            query.append("SELECT ");
            if (distinct) {
                query.append("DISTINCT ");
            }
            if (columns != null && columns.length != 0) {
                appendColumns(query, columns);
            } else {
                query.append("* ");
            }
            query.append("FROM ");
            query.append(tables);
            appendClause(query, " WHERE ", where);
            appendClause(query, " GROUP BY ", groupBy);
            appendClause(query, " HAVING ", having);
            appendClause(query, " ORDER BY ", orderBy);
            appendClause(query, " LIMIT ", limit);
    
            return query.toString();
        }

    因为这个limit是URI里的LMIT,所以就没啥卵用了。但是为啥老的调法没问题呢?

    上面讲到,老的调法把sortOrder语法字符串整个放在了QUERY_ARG_SQL_SORT_ORDER这个key中,其实就对应上面这段方法的orderBy变量,这里直接把sortOrder糊到已有语句后面其实就完成了。

    结论

    本问题以android11版本为界,分别执行不同方法即可。

    不要想当然,一定要自测,不要觉得新API一处,直接就引用了很帅,其实谷歌挺不靠谱的。

    更多相关内容
  • p-limit, 运行多个保证并发的&异步函数 限制 运行多个保证并发的&异步函数安装$ npm install p-limit用法const pLimit = require('p-limit');const limit = pLim
  • 【mysql】limit实现分页

    千次阅读 2022-02-11 20:52:55
    1. 使用limit实现数据的分页显示 需求1:每页显示20条记录,此时显示第1页 SELECT employee_id,last_name FROM employees LIMIT 0,20; 需求2:每页显示20条记录,此时显示第2页 SELECT employee_id,last_name ...

    分页

    1. 背景:

    背景1:查询返回的记录太多了,查看起来很不方便,怎么样能够实现分页查询呢?

    背景2:表里有 4 条数据,如果只想要显示第 2、3 条数据怎么办呢?

    2. 实现规则

    • 分页原理
      所谓分页显示,就是将数据库中的结果集,一段一段显示出来需要的条件。

    • MySQL中使用 LIMIT 实现分页

    • 格式:

      LIMIT [位置偏移量,] 行数
      
    • 第一个“位置偏移量”参数指示MySQL从哪一行开始显示,是一个可选参数,如果不指定“位置偏移量”,将会从表中的第一条记录开始(第一条记录的位置偏移量是0,第二条记录的位置偏移量是1,以此类推);第二个参数“行数”指示返回的记录条数。

    • 举例

    --前10条记录:
    SELECT * FROM 表名 LIMIT 0,10;
    或者
    SELECT * FROM 表名 LIMIT 10;
    
    --第11至20条记录:
    SELECT * FROM 表名 LIMIT 10,10;
    
    --第21至30条记录: 
    SELECT * FROM 表名 LIMIT 20,10;
    
    • 使用limit实现数据的分页显示

    • 需求1:每页显示5条记录,此时显示第1页

    SELECT employee_id,last_name
    FROM employees
    LIMIT 0,5;
    

    在这里插入图片描述

    • 需求2:每页显示6条记录,此时显示第2页
    SELECT employee_id,last_name
    FROM employees
    LIMIT 5,6;
    

    在这里插入图片描述

    • 需求3:每页显示7条记录,此时显示第3页
    SELECT employee_id,last_name
    FROM employees
    LIMIT 6,7;
    

    在这里插入图片描述

    • 需求4:每页显示pageSize条记录,此时显示第pageNo页:
    • 公式
    LIMIT (pageNo-1) * pageSize, pageSize;
    
    • 分页显式公式:(当前页数-1)* 每页条数,每页条数
    SELECT * FROM table 
    LIMIT(PageNo - 1)*PageSize, PageSize;
    
    • 注意:LIMIT 子句必须放在整个SELECT语句的最后!

    • 使用 LIMIT 的好处:
      约束返回结果的数量可以减少数据表的网络传输量,也可以提升查询效率。如果我们知道返回结果只有 1 条,就可以使用LIMIT 1,告诉 SELECT 语句只需要返回一条记录即可。这样的好处就是 SELECT 不需要扫描完整的表,只需要检索到一条符合条件的记录即可返回。

    • WHERE ... ORDER BY ...LIMIT 声明顺序如下:

    • LIMIT的格式: 严格来说:LIMIT 位置偏移量,条目数

    • 结构"LIMIT 0,条目数" 等价于 “LIMIT 条目数

    SELECT employee_id,last_name,salary
    FROM employees
    WHERE salary > 6000
    ORDER BY salary DESC
    #limit 0,10;
    LIMIT 10;
    

    在这里插入图片描述

    • 练习:表里有107条数据,如果只想要显示第 32、33 条数据怎么办呢?
    SELECT employee_id,last_name
    FROM employees
    LIMIT 31,2;
    

    在这里插入图片描述

    MySQL 8.0中可以使用“LIMIT 3 OFFSET 4”,意思是获取从第5条记录开始后面的3条记录,和“LIMIT 4,3;”返回的结果相同。

    • MySQL8.0新特性:LIMIT ... OFFSET ...

    • 练习:表里有107条数据,如果只想要显示第 32、33 条数据怎么办呢?

    SELECT employee_id,last_name
    FROM employees
    LIMIT 2 OFFSET 31;
    

    在这里插入图片描述

    • 练习:查询员工表中工资最高的员工信息
    SELECT employee_id,last_name,salary
    FROM employees
    ORDER BY salary DESC
    #limit 0,1
    LIMIT 1;
    

    在这里插入图片描述

    3. 拓展

    • LIMIT 可以使用在MySQL、PGSQL、MariaDB、SQLite 等数据库中使用,表示分页。不能使用在SQL Server、DB2、Oracle中。

    • 在不同的 DBMS 中使用的关键字可能不同。在 MySQL、PostgreSQL、MariaDB 和 SQLite 中使用 LIMIT 关键字,而且需要放到 SELECT 语句的最后面。

    • 如果是 SQL Server 和 Access,需要使用 TOP 关键字,比如:

    SELECT TOP 5 last_name, employee_id 
    FROM employees
    ORDER BY employee_id DESC
    
    • 如果是 DB2,使用FETCH FIRST 5 ROWS ONLY这样的关键字:
    SELECT last_name, employee_id 
    FROM employees 
    ORDER BY employee_id DESC 
    FETCH FIRST 5 ROWS ONLY
    
    • 如果是 Oracle,需要基于 ROWNUM 来统计行数:
      如:查询前10条记录
    SELECT rownum, employee_id, last_name
    FROM employees
    where rownum<= 10;
    

    在这里插入图片描述

    SELECT rownum,last_name,salary 
    FROM employees 
    WHERE rownum <= 5 ORDER BY salary DESC;
    

    4. 练习

    1. 查询员工的姓名和部门号和年薪,按年薪降序,按姓名升序显示
    SELECT last_name,department_id,salary * 12 annual_salary
    FROM employees
    ORDER BY annual_salary DESC,last_name ASC;
    

    在这里插入图片描述

    1. 选择工资不在 8000 到 17000 的员工的姓名和工资,按工资降序,显示第21到40位置的数据
    SELECT last_name,salary
    FROM employees
    WHERE salary NOT BETWEEN 8000 AND 17000
    ORDER BY salary DESC
    LIMIT 20,20;
    

    在这里插入图片描述

    1. 查询邮箱中包含 e 的员工信息,并先按邮箱的字节数降序,再按部门号升序
    SELECT employee_id,last_name,email,department_id
    FROM employees
    #where email like '%e%'
    WHERE email REGEXP '[e]'
    ORDER BY LENGTH(email) DESC,department_id;
    

    在这里插入图片描述

    展开全文
  • 要想通过面试,MySQL的Limit子句底层原理你不可不知

    万次阅读 多人点赞 2021-10-12 17:02:22
    MySQL的Limit子句底层原理如何分析,Limit子句在哪一步骤才执行?这一篇,我们得从从server层和存储引擎层进行分析...

    1.老样子,建个表

    还是这张表,表里我创建了近10W条数据

    CREATE TABLE demo_info(
        id INT NOT NULL auto_increment,
        key1 VARCHAR(100),
        key2 INT,
        key3 VARCHAR(100),
        key_part1 VARCHAR(100),
        key_part2 VARCHAR(100),
        key_part3 VARCHAR(100),
        common_field VARCHAR(100),
        PRIMARY KEY (id),
        KEY idx_key1 (key1),
        UNIQUE KEY uk_key2 (key2),
        KEY  idx_key3 (key3),
        KEY idx_key_part(key_part1, key_part2, key_part3)
    )ENGINE = INNODB CHARSET=utf8mb4;
    

    id列是主键,key1列是二级索引列。


    2.从sql执行计划看Limit的影响

    分析一下sql执行计划

    explain select * from demo_info order by key1 limit 1;
    

      在二级索引idx_key1中,key1列是有序的,查找按key1列排序的第1条记录,MySQL只需要从idx_key1中获取到第一条二级索引记录,然后直接回表取得完整的记录即可,这个很容易理解。

      如果我们把上边语句的limit 1换成limit 10000, 1,则却需要进行全表扫描,并进行filesort,执行计划如下:

    explain select * from demo_info order by key1 limit 10000, 1;
    

      有的同学就很不理解了:limit 10000, 1也可以使用二级索引idx_key1呀,我们可以先扫描到第10001条二级索引记录,对第10001条二级索引记录进行回表操作就好了啊。

      由于MySQL实现缺陷,不会出现上述的理想情况,它只会全表扫描+filesort,下边我们分析一下。


    3. 从server层和存储引擎层分析Limit执行过程

    MySQL其实是分为server层和存储引擎层的:

    • server层负责处理一些通用的事情,诸如连接管理、SQL语法解析、分析执行计划之类的东西

    • 存储引擎层负责具体的数据存储,诸如数据是存储到文件上还是内存里,具体的存储格式是什么样的之类的。我们现在基本都使用InnoDB存储引擎,其他存储引擎使用的非常少了,所以我们也就不讨论其他存储引擎了。

      MySQL中一条SQL语句的执行是通过server层和存储引擎层的多次交互才能得到最终结果的。先不用Limit子句举一个简单例子分析:

    SELECT * FROM demo_info WHERE key1 > 'a' AND key1 < 'b' AND common_field != 'a';
    

    server层会分析到上述语句可以使用下边两种方案执行:

    • 方案一:使用全表扫描

    • 方案二:使用二级索引idx_key1,此时需要扫描key1列值在('a', 'b')之间的全部二级索引记录,并且每条二级索引记录都需要进行回表操作。

      server层会分析上述两个方案哪个成本更低,然后选取成本更低的那个方案作为执行计划。然后就调用存储引擎提供的接口来真正的执行查询了。

    这里假设采用方案二,也就是使用二级索引idx_key1执行上述查询。那么server层和存储引擎层的执行过程如下:

      server层:“去查查idx_key1二级索引的('a', 'b')区间的第一条记录,然后把回表后把完整的记录返给我”

      InnoDB层:InnoDB就通过idx_key1二级索引对应的B+树,快速定位到扫描区间('a','b')的第一条二级索引记录,然后进行回表,得到完整的聚集索引记录返回给server层。 server层收到完整的聚集索引记录后,继续判断common_field!='a'条件是否成立,如果不成立则舍弃该记录,否则将该记录发送到客户端。然后对存储引擎说:“请把下一条记录给我”

    注意:

      此处将记录发送给客户端其实是发送到本地的网络缓冲区,缓冲区大小由net_buffer_length控制,默认是16KB大小。等缓冲区满了才真正发送网络包到客户端。

      InnoDB层:InnoDB找到idx_key1('a', 'b')区间的下一条二级索引记录,然后进行回表操作,将得到的完整的聚集索引记录返回给server层。

    注意:

      不论是聚集索引记录还是二级索引记录,都包含一个称作next_record的属性,各个记录根据next_record连成了一个链表,并且链表中的记录是按照键值排序的(对于聚集索引来说,键值指的是主键的值,对于二级索引记录来说,键值指的是二级索引列的值)。

      server层收到完整的聚集索引记录后,继续判断common_field!='a'条件是否成立,如果不成立则舍弃该记录,否则将该记录发送到客户端。然后对存储引擎说:“请把下一条记录给我哈”

      … 然后就不停的重复上述过程。

      直到InnoDB发现根据二级索引记录的next_record获取到的下一条二级索引记录不在('a', 'b')区间中,就跟server层说:“('a', 'b')区间没有下一条记录了”

      server层收到InnoDB说的没有下一条记录的消息,就结束查询。

    现在大家就知道了server层和存储引擎层的基本交互过程了。

    limit在哪里起作用呢?

    MySQL是在server层准备向客户端发送记录的时候才会去处理limit子句中的内容。 举个例子:

    select * from demo_info order by key1 limit 10000, 1;
    

    如果使用idx_key1执行上述查询,那么MySQL会这样处理:

    • server层向InnoDB要第1条记录,InnoDBidx_key1中获取到第一条二级索引记录,然后进行回表操作得到完整的聚集索引记录,然后返回给server层。server层准备将其发送给客户端,此时发现还有个limit 10000, 1的要求,意味着符合条件的记录中的第10001条才可以真正发送给客户端,所以在这里先做个统计,我们假设server层维护了一个称作limit_count的变量用于统计已经跳过了多少条记录,此时就应该将limit_count设置为1

    • server层再向InnoDB要下一条记录,InnoDB再根据二级索引记录的next_record属性找到下一条二级索引记录,再次进行回表得到完整的聚集索引记录返回给server层。server层在将其发送给客户端的时候发现limit_count才是1,所以就放弃发送到客户端的操作,将limit_count1,此时limit_count变为了2

    • … 重复上述操作

    • 直到limit_count等于10000的时候,server层才会真正的将InnoDB返回的完整聚集索引记录发送给客户端。

      从上述过程中我们可以看到,MySQL中是在实际向客户端发送记录前才会去判断limit子句是否符合要求,所以如果使用二级索引执行上述查询的话,意味着要进行10001次回表操作。server层在进行执行计划分析的时候会觉得执行这么多次回表的成本太大了,还不如直接全表扫描+filesort快呢,全表扫描+filesort就是把聚集索引中的记录都依次与给定的搜索条件进行比较,把符合搜索条件的记录再进行排序,MySQL认为这样操作的成本比多次回表成本低,所以就选择了后者执行查询。

    MySQL是根据成本来选择对应索引查询的,如果你不知道成本怎么计算,可以看我前一篇MySQL查询为什么选择使用这个索引?——基于MySQL8.0.22索引成本计算

    注意:
      有一个点很容易混淆,走PRIMARY索引和全表扫描有什么区别呢?他们其实都是在聚集索引上操作的(聚集索引B+树的叶子结点是根据主键排好序的完整的用户记录,包含表里的所有字段),区别就在于

      全表扫描将聚集索引B+树的叶子结点依次顺序扫描并判断条件,在以下几种情况会走全表扫描:

    • select * from demo_info这种无条件的查询语句
    • select * from demo_info where common_field != 'a'这种条件字段common_field没有建索引的情况
    • select * from demo_info order by key1 limit 10000, 1条件字段key1建了索引但是MySQL认为走二级索引的成本比全表扫描成本高的情况。

      PRIMARY索引是利用二分思想将聚集索引B+树到指定范围区间进行扫描,比如select * from demo_info where id in (1, 2)这种条件字段是主键id,可以很好的利用PRIMARY索引进行二分的快速查询。

    怎么解决这个问题?

      由于MySQL实现limit子句的局限性,在处理诸如limit 10000, 1这样的语句时就无法通过使用二级索引来加快查询速度了么?其实也不是,只要把上述语句改写成:

    select * from demo_info d, 
    (select id from demo_info order by key1 limit 10000, 1) t 
    WHERE d.id = t.id;
    -- 或者这么写
    select * from demo_info d
    join 
    (select id from demo_info order by key1 limit 10000, 1) t
    on d.id = t.id
    

      这样,select id from demo_info order by key1 limit 10000, 1作为一个子查询单独存在,由于该子查询的查询列表只有一个id列,MySQL可以通过仅扫描二级索引idx_key1的叶子结点不用回表,然后再根据子查询中获得到的主键值去表demo_info中进行查找。这样就省去了前10000条记录的回表操作,从而大大提升了查询效率!



    欢迎一键三连~

    有问题请留言,大家一起探讨学习

    ----------------------Talk is cheap, show me the code-----------------------
    展开全文
  • MySQL实战—— Limit与Order by 对查询效率的巨大影响

    千次阅读 多人点赞 2021-06-12 14:31:59
    查询同样的数据,在使用Order by、limit后可能对查询结果 与耗时产生百倍的影响。优化SQL不光是优化那些1秒以上的慢查询,更重要的是那些超高频率的0.1秒的查询SQL。 在这里我模拟创建了一张表 limit_table 并初始化...

    前言

    查询同样的数据,在使用Order by、limit后可能对查询结果 与耗时产生百倍的影响。优化SQL不光是优化那些1秒以上的慢查询,更重要的是那些超高频率的0.1秒的查询SQL。

    在这里我模拟创建了一张表 limit_table
    并初始化100W行的数据。

    -- 表创建
    CREATE TABLE `limit_table` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `index_fie_id_a` int(8) NOT NULL,
      `fie_id_b` int(8) NOT NULL,
      `fie_id_str` varchar(30) DEFAULT NULL,
      `created_at` datetime DEFAULT NULL,
      `updated_at` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
      PRIMARY KEY (`id`),
      KEY `index_index_fie_id_a` (`index_fie_id_a`) USING BTREE
    ) ENGINE=InnoDB  CHARSET=utf8;
    
    -- 创建存储过程
    DELIMITER //
    CREATE PROCEDURE add_limit_table()
    BEGIN
      DECLARE num INT;
      SET num = 0;
      WHILE
        num < 1000000 DO
        -- 插入数据
    		INSERT INTO limit_table(index_fie_id_a,fie_id_b,created_at) VALUES(CEILING(RAND()*10000),CEILING(RAND()*100000),now());
        SET num = num + 1;
      END WHILE;
    END; 
    //
    -- 执行存储过程
    call add_limit_table();
    -- 删除存储过程
    DROP PROCEDURE add_limit_table;
    --初始化String 字符串 
    UPDATE limit_table set  fie_id_str= CONCAT('string',inde_x_fie_id_a,CEILING(RAND()*10));
    

    数据初始化完成后我们来看看不同查询条件、不同排序条件下 查询效率比较
    字段index_fie_id_a:int类型,有索引,每个数据约有100条重复
    字段fie_id_b :int类型,无索引,每个数据约有10条重复
    字段fie_id_str :varchar类型,无索引,每个数据约有10条重复,且数据是依据index_fie_id_a 排序生成

    验证场景

    	-- 无排序、无查询条件 Limit 0,5   Demo :A
    	SELECT SQL_NO_CACHE  * from limit_table LIMIT 5;
    	-- 无排序、无查询条件 Limit 50000,5   Demo :B
    	SELECT SQL_NO_CACHE  * from limit_table LIMIT 50000,5;
    	-- 无排序、有查询条件/int类型/无索引 Limit 0,5   Demo :C
    	SELECT SQL_NO_CACHE  * from limit_table  WHERE fie_id_b = 66666 LIMIT 5;
    	-- 无排序、有查询条件/varchar/无索引 Limit 0,5   Demo :D
    	SELECT SQL_NO_CACHE  * from limit_table  WHERE fie_id_str = 'string66666' LIMIT 5;
    	-- 无排序、有查询条件/有索引 Limit 0,5   Demo :E
    	SELECT SQL_NO_CACHE  * from limit_table  WHERE index_fie_id_a = 100 LIMIT 5;
    	-- 有排序/无索引、无查询条件 Limit 0,5   Demo :F
    	SELECT SQL_NO_CACHE  * from limit_table  ORDER BY fie_id_b LIMIT 5;
    	-- 有排序/无索引、有查询条件/int类型/无索引 Limit 0,5   Demo :G;
    	SELECT SQL_NO_CACHE  * from limit_table WHERE fie_id_b = 66666  ORDER BY fie_id_str LIMIT 5;
    	-- 有排序/无索引、有查询条件/varchar类型/无索引 Limit 0,5   Demo :H;
    	SELECT SQL_NO_CACHE  * from limit_table  WHERE fie_id_str = 'string66666' ORDER BY fie_id_b  LIMIT 5;
    	-- 有排序/无索引、有查询条件/有索引 Limit 0,5   Demo :I;
    	SELECT SQL_NO_CACHE  * from limit_table  WHERE index_fie_id_a = 100 ORDER BY fie_id_b LIMIT 5;
    	-- 有排序/有索引、无查询条件 Limit 0,5   Demo :J;
    	SELECT SQL_NO_CACHE  * from limit_table  ORDER BY index_fie_id_a LIMIT 5;
    	-- 有排序/有索引、有查询条件/无索引 Limit 0,5   Demo :K1;
    	SELECT SQL_NO_CACHE  * from limit_table WHERE fie_id_str = 'string10001'  ORDER BY index_fie_id_a LIMIT 5;
    	-- 有排序/有索引、有查询条件/无索引 Limit 0,5   Demo :K2;
    	SELECT SQL_NO_CACHE  * from limit_table WHERE fie_id_str = 'string30001'  ORDER BY index_fie_id_a LIMIT 5;
    	-- 有排序/有索引、有查询条件/无索引 Limit 0,5   Demo :K3;
    	SELECT SQL_NO_CACHE  * from limit_table WHERE fie_id_str = 'string60001'  ORDER BY index_fie_id_a LIMIT 5;
    	-- 有排序/有索引、有查询条件/无索引 Limit 0,5   Demo :K4;
    	SELECT SQL_NO_CACHE  * from limit_table WHERE fie_id_str = 'string99999'  ORDER BY index_fie_id_a  LIMIT 5;
    	-- 有排序/有索引、有查询条件/无索引 Limit 0,5   Demo :K5;
    	SELECT SQL_NO_CACHE  * from limit_table WHERE fie_id_str = 'string99999'  ORDER BY index_fie_id_a  desc LIMIT 5;
    	-- 有查询条件/无索引   Demo :K6;
    	SELECT SQL_NO_CACHE  * from limit_table WHERE fie_id_str = 'string99999';
    	-- 有排序/有索引、有查询条件/有索引 Limit 0,5   Demo :L;
    	SELECT SQL_NO_CACHE  * from limit_table WHERE index_fie_id_a = 100  ORDER BY index_fie_id_a LIMIT 5;
    

    本文中分析数据均是基于查找的数据绝对能够覆盖 limit 限制的条数,避免因记录数不足导致全表扫描影响查询耗时的情况。

    查询耗时情况如下

    在这里插入图片描述

    查询结果分析

    • Demo A:没有查询条件,且通过Limit限制了查询数量,在查询数量一致的情况下这是 最快 的,因为通常B+Tree上有 两个头指针 ,一个指向根节点,另一个指向关键字最小的叶子节点,直接可以进行 顺序读 ,高效,不浪费IO次数。所以本次查询返回的会是ID:1 ~ 5 的数据。 数据检索行数:5

      忽略其它因素影响情况下数据检索行数越少,查询越快。咱们做SQL优化,核心之一就是减少数据检索行数。这也是索引存在的主要作用。

    • Demo B :Demo B 比 Demo A 更慢因为B查询需要检索的行数更多,因为需要从第一条开始依次找到第50001 ~ 50005 条数据。所以当咱们做翻页功能时,深度翻页 将会导致你的查询效率大大降低!数据检索行数:50005

      涉及 limit $start,$num 的搜索,如果$start 巨大,则影响结果集巨大,最终导致查询耗时较长,推荐大家尽量带入主键ID当查询条件并将SQL改写为 limit 0,$num; 确认无法改写的情况下,先从索引结构中获得 limit $start,$num 或 limit $start,1 ;再用 in 操作或基于索引序的limit 0,$num 二次搜索。
      例如:下一页操作让前端把当前页最大ID传递过来,然后后台加一个weher条件即可。如果是用户手动输入页码跳页,则可以先用查询页面查出该页的最小主键ID值然后再去查询该页数据。

    • Demo C & Demo D :Demo C会比 Demo D快那么多,在都没有索引的情况下,忽略数据分布(假设ID 1 ~ 5 的数据 fie_id_str 都等于 ‘string66666’,这就是极端情况,咱们不考虑)。在数据运算、对比方面,整数得益于原生支持会比varchar 快很多。数据检索行数:>=5 and <总记录数

      因为会从主键ID最小的叶子节点开始依次对比,当满足Limit限制的5行数据后即可停止继续检索数据。所以数据检索行数是未知的。除非符合要求的数据不足5条就会检索完整个表的数据

    • Demo E :该字段建了索引,所以根据非聚簇索引顺序取5行数据对应的主键ID即可,然后根据主键ID回表查询数据。Demo E 会比 Demo A 慢主要原因两点:1、回表查询增加IO次数; 2、数据的主键ID不连续,需要从磁盘加载多个页到内存数据检索行数:5

    • Demo F :排序字段没有索引,没有查询条件,所以无任何索引可用扫全表无疑。慢到飞起,这是绝对错误的玩法。 数据检索行数:总记录数

    • Demo G & Demo H :这两个同样都是根据where条件扫全表,然后符合条件的数据进行排序,所以 Demo G & Demo H 相比 Demo C & Demo D 更慢。Demo G 比 Demo H 快还是因为字符类型的优势。同时 Demo G 还比 Demo F 快一点说明同字符类型下排序会比比较判断要慢。 数据检索行数:总记录数

      细心的同学发现我查询Demo G案例时将排序字段使用了数据库中的str字段,那假如我的排序字段和where查询条件字段完全一致,且查询条件是 ‘=’,那么可以理解为 没有排序下检索5行记录。所以可能会不需要全表扫描哦!!!

    • Demo I :相对还是比较快的,因为查询条件有索引。但是由于排序字段无索引所以需要把所有符合条件的数据找出来,再去排序。数据检索行数:符合where条件的总记录数

    • Demo J :Demo J 和 Demo E是差不多的,但是由于不需要判断比较 和 Demo A一样没有查询条件所以会比 Demo E 快。同时又因为回表查询与主键ID不连续的问题会比Demo A要慢。 数据检索行数:5

    • Demo L :where条件有索引,排序字段相同,在判断条件是 ‘=’ 的情况下可以忽略。所以参考 Demo E。 数据检索行数:5

    查询条件无索引但排序有索引

    在这里插入图片描述

    查询结果分析

    在这里插入图片描述

    • Demo K1 ~ Demo K4:这个就非常有意思,因为where条件无索引而排序字段是有索引的,优化器 会选择走索引。于是噩梦就来了,查询时间浮动巨大,全看你的where条件中的数据存在于索引字段的哪一部分。Demo K4中 fie_id_str = ‘string90001’ 的数据,按照 index_fie_id_a 排序后处在,非常靠后的部分于是乎这个查询耗时直接就慢的不行。
    • Demo K5 :我并未修改查询条件和排序字段,只是将排序由 ASC 改为 DESC,于是 fie_id_str = ‘string99999’ 所处的位置瞬间就靠前了,查询时间一下变短了 2700 倍。
    • Demo K6:我直接去掉了排序和Limit,全表匹配查找不走索引,反而相对比有索引情况下靠谱得多。这就是之前有说到过的,回表 和主键ID不连续,导致每一条记录都需要加载一整个页的数据。

    总结

    • int类型比较判断会比varchar快,许多数据库会用 1/2 代替 男/女;
    • 深度分页需要注意优化;
    • 常用的查询字段建立索引是非常有必要的;
    • MySQL的优化器不一定会给你最好的结果。但是如果结果不理想,一定是你的索引建的有问题或者SQL写的有问题。以我们的技术功底千万别去怀疑优化器;
    • 在没有查询条件、或查询条件没有索引的情况下严禁使用无索引字段排序;
    • 能靠主键查询/排序尽量靠主键,避免回表查询;
    • 如查询条件没有索引,慎用有索引的字段去排序;
    • 在已知需要查询数据数量情况下使用Limit,可以有效提高查询效率;

    如果觉得写得不错,点个赞就是对我最大的鼓励!!!

    展开全文
  • MySQL的分页你还在使劲的limit

    千次阅读 多人点赞 2022-07-22 16:22:58
    当然,这种写法存在一定问题,如果第0页的id=5的数据被删除了,就会导致查询第0页的数据和第1页的数据有重合,第0页是1-4,6-11(默认一页10条数据,因为limit10,所以会查询到id=11),第二页就是11-20,可见id=11...
  • Linux 中的Soft limit 和Hard limit

    千次阅读 2021-05-16 10:08:51
    #Soft limit 和 Hard limit# 在Linux的系统中对于进程(Process)会有一些限制,这就所谓的limit,在实际应用中最常见的就是对打开文件(Open Files)的限制,在配置web 服务如nginx时就会用到。在linux中这些限制是分为...
  • mybatis puls分页出现两个 limit 10 LIMIT ?

    千次阅读 2021-10-14 23:47:10
    mybatis puls分页出现两个 limit 10 LIMIT ? 2020-10-15 12:03:02.909 ERROR 28056 --- [nio-8000-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in ...
  • mysql使用limit实现分页

    千次阅读 2022-05-08 11:48:49
     分页用到的是 limit 起始下标 , 长度 ,起始下标从0开始。如 limit 0 , 5的意思是取查询出来的第1行开始5条数据,第一行-第五行。  比如我们按每页4行数据来分 , 则可以分为: 第 1 页 : 0 , 1 , 2 ,3 第 2 页 :...
  • MYSQL分页limit速度太慢优化方法

    千次阅读 2021-01-27 16:28:19
    在mysql中limit可以实现快速分页,但是如果数据到了几百万时我们的limit必须优化才能有效的合理的实现分页了,否则可能卡死你的服务器哦。当一个表数据有几百万的数据的时候成了问题!如 * from table limit 0,10 这...
  • MySQL LIMIT 理解

    千次阅读 2022-03-29 11:40:11
    MYSQL LIMIT
  • MySQL的Limit详解

    千次阅读 2021-01-19 00:57:24
    在Oracle数据库中语法为: SELECT column_name(s) FROM table_name WHERE ROWNUM 在MySQL数据库中语法为: SELECT column_name(s) FROM table_name LIMIT number MySQL的Limit子句 Limit子句可以被用于强制 ...
  • Oracle实现limit

    千次阅读 2021-10-24 20:19:47
    oracle数据库不支持mysql、postgresql的limit功能,但是可以通过rownum(oracle系统顺序分配为从查询返回的行的编号,从1开始排)来限制返回结果集的行数,达到同样的效果。 二、用法 -- 查询前10行记录 select * ...
  • mysql分页limit

    千次阅读 2021-08-19 11:17:27
    分页查询(limit) 分页查询,是一个很常见的,用到的地方很多,比如:淘宝、京东等商城,假如我搜索"显卡",商品列表页会出现很多相关的商品,在PC端也就是电脑上,会很清晰的看见,页面最下方,会有一个"上一页...
  • 本文将继续对backtrader的order进行介绍,具体介绍StopLimit订单的使用。 选取平安银行(000001)2018年1月1日至2019年12月31日的日线数据进行回测。为了便于分析,回测过程中设置佣金为0,交易单位大小为100。 这篇...
  • MySQL 的 limit 分页查询及性能问题

    千次阅读 2022-03-16 11:05:22
    MySQL 通过 limit 实现分页查询。limit 接收一个或两个整数型参数。如果是两个参数,第一个指定返回记录行的偏移量,第二个指定返回记录行的最大数目。初始记录行的偏移量是 0。为了与 PostgreSQL 兼容,limit 也...
  • 您可能感兴趣的文章:Mysql limit 优化,百万至千万级快速分页 复合索引的引用并应用于轻量级框架mysql limit分页优化方法分享使用Limit参数优化MySQL查询的方法mysql limit查询优化分析Mysql中limit的用法方法详解...
  • Mysql Sql查询之Limit 用法

    千次阅读 2022-04-14 09:23:45
    它有3种使用语法“LIMIT 初始位置,记录数”、“LIMIT 记录数”和“LIMIT 记录数 OFFSET 初始位置”。 当数据表中有上万条数据时,一次性查询出表中的全部数据会降低数据返回的速度,同时给数据库服务器造成很大的...
  • MySQL性能优化_limit 的优化

    千次阅读 2022-02-17 15:43:48
    1) limit 存在的问题2) limit 为什么会存在这样的问题?3) 第一次优化——自增索引4) 第二次优化——条件查询转化为基于主键id的查询5) 第三次优化——条件查询转化为基于主键id的查询 + inner join 2. 如何优化 ...
  • mysql delete limit 简单用法

    千次阅读 2021-03-14 11:58:14
    mysql delete limit 使用方法详解mysql delete limit优点:用于DELETE的MySQL唯一的LIMIT row_count选项用于告知服务器在控制命令被返回到客户端前被删除的行的最大值。本选项用于确保一个DELETE语句不会占用过多的...
  • Mysql中limit的用法详解 (转)

    千次阅读 2021-02-02 03:34:50
    Mysql中limit的用法:在我们使用查询语句的时分,经常要返回前几条或者中间某几行数据,这个时分如何办呢?没有效担心,mysql已为我们供给了如许一个功能。SELECT * FROMtableLIMIT[offset,] rows | rows OFFSET ...
  • 深入分析Mysql中limit的用法

    千次阅读 2021-01-20 19:26:51
    很久没用mysql的limit,一时大意竟然用错了,自认为(limit 开始,结束),其实错了,正确的应该是(limit 偏移量,条数),为了记住这次错误,转载一篇limit用法详解。推荐给大家,希望对大家能够有所帮助。Mysql中limit...
  • 分页功能实现,startRow与Limit,可完成分页。Hbase默认scan是进行排序的。 如果想取前1w行,则指定startRow,并且limit 10001。可以作为第一页。取末尾最后一个row,作为下一个的startRow。可实现分页。 hbase ...
  • 【SQL中limit的用法】

    千次阅读 2022-04-07 19:09:34
    SQL中limit的用法 说明:limit子句用于限制查询结果返回的数量,常用于分页查询。 用法:【select * from tableName limit i,n 】 参数:tableName: 为数据表; i: 为查询结果的索引值(默认从0开始); n: 为查询...
  • MYSQL limit用法

    万次阅读 2021-08-17 17:43:37
    1.Mysql的limit用法 在我们使用查询语句的时候,经常要返回前几条或者中间某几行数据,这个时候怎么办呢?不用担心,mysql已经为我们提供了这样一个功能。 SELECT * FROM table LIMIT [offset,] rows | rows OFFSET ...
  • MySQL中的limit用法详解

    千次阅读 2022-05-05 10:07:46
    limit的详细用法 1、用于强制返回指定的记录行数 在查询中,经常要返回前几条或者中间某几行数据时,用到limit 语法如下: select * from table_name limit [offset,] rows 参数说明: offset:指定第一个返回记录...
  • nginx篇11-限速三剑客之limit_conn

    千次阅读 2022-02-25 17:45:36
    本文主要是对nginx官方limit_conn相关模块的配置用法和一些个人理解,limit_conn主要用于限制用户的连接数,在如今多线程并发请求大量普及的情况下,对于一些特殊的场景还是有着一定的用处的。 1、背景 目前来说在...
  • Java分页(limit

    千次阅读 2022-04-05 15:04:41
    public static List mylimit(int page, int size) throws SQLException { String sql = "select * from staff limit ?,?"; List list = query(sql, (page - 1) * size, size); return list; } 写一个视图实例(部分...
  • mybatis中使用Limit分页

    千次阅读 2021-11-15 23:25:36
    在mybatis中使用Limit实现分页操作 编写实现分页的接口 public interface UserMapper { //分页Limit List<User> getUserByLimit(Map<String,Integer> map); } 编写配置接口文件 <?xml ...
  • hive的limit失效问题

    千次阅读 2022-04-29 16:14:19
    问题语句 select app_name,count(1) as cnt from ( select name,seq_id ...但是当我用beeline去运行时, 加limit 结果只有一行错误数据, 不加limit 才能跑出正常结果 排查过程 把limit相关参数

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,273,463
精华内容 509,385
关键字:

limit

友情链接: EmaxIT.rar