MySQL一个错误查询有关问题追查
MySQL一个异常查询问题追查 问题 线上碰到的问题:相同的语句,只是最后的limit行数不同。奇怪的是,limit 10 的性能比limit 100的语句还慢约10倍。隐藏用户表信息,语句及结果如下SELECT f1 , SUM(`f2`) `CNT` FROM T WHERE f1 IS NOT NULL AND f3 = '2014-0
MySQL一个异常查询问题追查问题
<code>线上碰到的问题:相同的语句,只是最后的limit行数不同。奇怪的是,limit 10 的性能比limit 100的语句还慢约10倍。 隐藏用户表信息,语句及结果如下 SELECT f1 , SUM(`f2`) `CNT` FROM T WHERE f1 IS NOT NULL AND f3 = '2014-05-12' GROUP BY f1 ORDER BY `CNT` DESC LIMIT 10; 执行时间3 min 3.65 sec SELECT f1 , SUM(`f2`) `CNT` FROM T WHERE f1 IS NOT NULL AND f3 = '2014-05-12' GROUP BY f1 ORDER BY `CNT` DESC LIMIT 100; 执行时间1.24Sec. 性能差距非常大! </code>
分析
<code>MySQL Tips:<span>追查语句执行时最常用的方法,是通过explain来看语句的执行计划。 </span></code><span>?</span>
<code>更有冲击性的效果是通过缩小范围后,在这个数据下,limit 67和limit 68的执行计划相差很大。 两个执行计划: LIMIT 67 id: 1 select_type: SIMPLE table: a type: range possible_keys: A,B,C <span>key: B</span> key_len: 387 ref: NULL <span>rows: 2555192</span> Extra: Using where; Using temporary; Using filesort 1 row in set (0.00 sec) LIMIT 68 id: 1 select_type: SIMPLE table: a type: ref possible_keys: A,B,C <span>key: A</span> key_len: 3 ref: const <span>rows: 67586</span> Extra: Using where; Using temporary; Using filesort 1 row in set (0.00 sec) 可以看到,两个语句的执行计划不同:使用的索引不同。 </code>
<code> MySQL Tips:<span>explain的结果中,key表示最终使用的索引,rows表示使用这个索引需要扫描的行数,这是个估计值。</span> 表中 索引A定义为 (f3, f4, f1, f2, f5); 索引B定义为(f1, f2, f3); </code>
一个确认
<code>虽然rows是估计值,但是指导索引使用的依据。既然limit 68能达到rows 67586, 说明在第一个语句优化器可选结果中,也应该有此值,为什么不会选择索引A? 先确认一下我们上面的这个结论。</code>
<code>MySQL Tips:<span>MySQL语法中能够用force index 来强行要求优化器使用某一个索引。</span> </code>
Explain SELECT f1 , SUM(f2
)?CNT
?FROM t force index(A) WHERE f1 IS NOT NULL AND f3 = ‘2014-05-12’ GROUP BY P ORDER BY?CNT
?DESC LIMIT 67\G
id: 1?
select_type: SIMPLE?
table: a?
type: ref?
possible_keys:A?
key: A?
key_len: 3?
ref: const?
rows: 67586?
Extra: Using where; Using temporary; Using filesort?
1 row in set (0.00 sec)
<code>顺便说明,由于我们指定了force index,因此优化器不会考虑其他索引,possible_keys里只会显示A。 我们关注的是rows:67586。这说明在limit 67语句里,使用索引A也能够减少行扫描。</code>
<code>MySQL Tips:<span>MySQL优化器会对possiable_key中的每个可能索引都计算查询代价,选择最小代价的查询计划。</span></code>
<code><span>?</span>至此我们大概可以猜测,这个应该是MySQL实现上的bug:没有选择合适的索引,导致使用了明显错误的执行计划。</code>
<code>MySQL Tips:<span>MySQL的优化器执行期间需要依赖于表的统计信息,而统计信息是估算值,因此有可能导致得到的执行计划非最优。</span></code>
<code>但要说明的是,上述Tip是客观情况造成(可接受),但本例却是例外,因此优化器实际上可以拿到能够作出选择正确结果的数据(rows值),但是最终选择错误。 </code>
原因分析
<code>MySQL优化器是按照查询代价的估算值,来确定要使用的索引。计算这个估算值的过程,基本是按照“估计需要扫描的行数”来确定的。 </code>
<code>MySQL Tips:<span>MySQL在目前集团主流使用的5.1和5.5版本中只能使用前缀索引。</span> </code>
因此,使用索引A只能用上字段f3,使用索引B只能用上字段f1。Rows即为使用了索引查到上下界,之后需要扫描的数据行数(估算值)。
<code>上述的语句需要用到group和order by,因此执行计划中都有Using temporary; Using filesort。 流程上按顺序先计算使用索引A的查询代价。 之后依次计算其他possitabe_key的查询代价。由于过程中需要排序,在得到一个暂定结果后,需要判断是否有代价更低的排序方式(test_if_cheaper_ordering)。 与之前的大同小异,也是依靠估计扫描行数来计算代价。 在这个逻辑的实现过程中,存在一个bug:在估计当前索引的区分度的时候,没有考虑到前缀索引。 即:假设表中有50w行数据,索引B(f1,f2,f3),则计算索引区分度时,需要根据能够用上的前缀部分来确定。比如f1有1000个不同的值,则平均每个key值上的记录数为500.如(f1,f2)有10000个同的值,则平均每个组合key上的记录数为50,若(f1,f2,f3)有50w个不同的值,则平均每个组合key上的记录数为1。</code>
<code>MySQL Tips:<span>每个key上的记录数越少,说明使用该索引查询时效率最高。对应于show index from tbl 输出结果中的Cardinality值越大。</span></code>
在这个case下,索引A只能使用f1做前缀索引,但是在计算单key上的行平均值时用的是(f1,f2,f3),这就导致估算用索引B估算的时候,得到的代价偏小。导致误选。
回到问题本身
<code>1、 为什么limit值大的时候反而选对了呢? </code><span>这是因为在计算B的查询代价时,查询需要返回的行数limit_rows也参与乘积,若limit值较大,则计算出来的B的代价就会更大,反而会由于代价。值超过A,而导致优化器最终选择A。</span>
<code>2、 这个表有50w行数就,为什么limit相差1为就差别这么大? 这与语句本身有关。这个语句中有group by,这就意味着每多limit一个值,实际上需要扫描更多的行N。 这里N为“表的总行数”/“表中不同的f2值”。 也就是说这个语句使得这个bug有放大作用。 </code>
解决方法
<code>分析清楚后解决方法就比较简单了,修改代码逻辑,在执行test_if_cheaper_ordering过程中,改用字段f1的区分度来计算即可。</code>

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック









MySQLは、インストールが簡単で、強力で管理しやすいため、初心者に適しています。 1.さまざまなオペレーティングシステムに適した、単純なインストールと構成。 2。データベースとテーブルの作成、挿入、クエリ、更新、削除などの基本操作をサポートします。 3.参加オペレーションやサブクエリなどの高度な機能を提供します。 4.インデックス、クエリの最適化、テーブルパーティション化により、パフォーマンスを改善できます。 5。データのセキュリティと一貫性を確保するために、バックアップ、リカバリ、セキュリティ対策をサポートします。

NAVICAT自体はデータベースパスワードを保存せず、暗号化されたパスワードのみを取得できます。解決策:1。パスワードマネージャーを確認します。 2。NAVICATの「パスワードを記憶する」機能を確認します。 3.データベースパスワードをリセットします。 4.データベース管理者に連絡してください。

NAVICATプレミアムを使用してデータベースを作成します。データベースサーバーに接続し、接続パラメーターを入力します。サーバーを右クリックして、[データベースの作成]を選択します。新しいデータベースの名前と指定された文字セットと照合を入力します。新しいデータベースに接続し、オブジェクトブラウザにテーブルを作成します。テーブルを右クリックして、データを挿入してデータを挿入します。

MySQLでテーブルをコピーするには、新しいテーブルの作成、データの挿入、外部キーの設定、インデックスのコピー、トリガー、ストアドプロシージャ、および機能が必要です。特定の手順には、同じ構造を持つ新しいテーブルの作成が含まれます。元のテーブルからデータを新しいテーブルに挿入します。同じ外部キーの制約を設定します(元のテーブルに1つがある場合)。同じインデックスを作成します。同じトリガーを作成します(元のテーブルに1つがある場合)。同じストアドプロシージャまたは関数を作成します(元のテーブルが使用されている場合)。

Passwordが暗号化された形式で保存されているため、MariadbのNavicatはデータベースパスワードを直接表示できません。データベースのセキュリティを確保するには、パスワードをリセットするには3つの方法があります。NAVICATを介してパスワードをリセットし、複雑なパスワードを設定します。構成ファイルを表示します(推奨されていない、高リスク)。システムコマンドラインツールを使用します(推奨されません。コマンドラインツールに習熟する必要があります)。

次のコマンドでmysqlデータベースを表示します。サーバーに接続します:mysql -u username -pパスワードrun showデータベース。すべての既存のデータベースを取得するコマンド[データベース]を選択します。データベース名を使用します。テーブルを表示:表を表示します。テーブル構造を表示:テーブル名を説明してください。データを表示:[テーブル名]から[ *]を選択します。

MySQLのコピーと貼り付けには、次の手順が含まれています。データを選択し、Ctrl C(Windows)またはCMD C(MAC)でコピーします。ターゲットの場所を右クリックして、貼り付けまたはCTRL V(Windows)またはCMD V(MAC)を使用します。コピーされたデータは、ターゲットの場所に挿入されるか、既存のデータを置き換えます(データが既にターゲットの場所に存在するかどうかに応じて)。

NAVICATでSQLを実行する手順:データベースに接続します。 SQLエディターウィンドウを作成します。 SQLクエリまたはスクリプトを書きます。 [実行]ボタンをクリックして、クエリまたはスクリプトを実行します。結果を表示します(クエリが実行された場合)。
