• <th id="kadjp"></th>

            1. ?
                開發(fā)技術(shù) / Technology

                Java開發(fā)者寫SQL時(shí)常犯的10個(gè)錯(cuò)誤

                日期:2015年3月10日  作者:zhjw  來源:Java開發(fā)者寫SQL時(shí)常犯的10個(gè)錯(cuò)誤    點(diǎn)擊:833

                       最近的一篇文章——《Java開發(fā)者寫SQL時(shí)常犯的10個(gè)錯(cuò)誤》——最近在DZone上非常的受歡迎。(這篇博客)的流行程度說明了幾件事: 

                • SQL在專業(yè)的Java開發(fā)中多么重要;
                • 基本的SQL知識(shí)被忘掉(的情況)普遍存在;
                • 通過embracing SQL,你就能了解像 jOOQ或MyBatis這樣的以SQL為中心的庫正好反應(yīng)了市場(chǎng)的需要。 令人驚喜的是有用戶提到了我博客上貼的一篇“SLICK’s mailing list”,SLICK是Scala中的一個(gè)不以SQL為中心的數(shù)據(jù)庫訪問庫,像LINQ(還有LINQ-to-SQL),它側(cè)重語言整合,而不是SQL語句的產(chǎn)生。


                無論如何,我之前倉(cāng)促列出的常見錯(cuò)誤還沒列完。因此我為你另外準(zhǔn)備了10個(gè)沒那么常見的,但Java開發(fā)者在寫SQL語句時(shí)同樣愛犯的錯(cuò)誤。 
                1、不用PreparedStatements 
                有意思的是,在JDBC出現(xiàn)了許多年后的今天,這個(gè)錯(cuò)誤依然出現(xiàn)在博客、論壇和郵件列表中,即便要記住和理解它是一件很簡(jiǎn)單的事。開發(fā)者不使用PreparedStatements的原因可能有如下幾個(gè): 

                • 他們對(duì)PreparedStatements不了解
                • 他們認(rèn)為使用PreparedStatements太慢了
                • 他們認(rèn)為寫PreparedStatements太費(fèi)力


                來吧,我們來破除上面的謠言。96%的案例中,用PreparedStatement比靜態(tài)聲明語句更好。為什么呢?就是下面這些簡(jiǎn)單的原因: 

                • 使用內(nèi)聯(lián)綁定值(inlining bind values)可以從源頭上避免糟糕的語句引起語法錯(cuò)誤。
                • 使用內(nèi)聯(lián)綁定值可以避免糟糕的語句引起的SQL注入漏洞。
                • 當(dāng)插入更多“復(fù)雜的”數(shù)據(jù)類型(比如時(shí)間戳、二進(jìn)制數(shù)據(jù)等等)時(shí),可以避免邊緣現(xiàn)象(edge-cases)。
                • 你若保持PreparedStatements的連接開啟狀態(tài)而不馬上關(guān)閉,只要重新綁定新值就可以進(jìn)行復(fù)用。
                • 你可以在更多復(fù)雜的數(shù)據(jù)庫里使用adaptive cursor sharing——自適應(yīng)游標(biāo)共享(Oracle的說法)。這可以幫你在每次新設(shè)定綁定值時(shí)阻止SQL語句硬解析。


                (譯者注:硬解析的弊端。硬解析即整個(gè)SQL語句的執(zhí)行需要完完全全的解析,生成執(zhí)行計(jì)劃。而硬解析,生成執(zhí)行計(jì)劃需要耗用CPU資源,以及SGA資源。在此不得不提的是對(duì)庫緩存中 閂的使用。閂是鎖的細(xì)化,可以理解為是一種輕量級(jí)的串行化設(shè)備。當(dāng)進(jìn)程申請(qǐng)到閂后,則這些閂用于保護(hù)共享內(nèi)存的數(shù)在同一時(shí)刻不會(huì)被兩個(gè)以上的進(jìn)程修改。在 硬解析時(shí),需要申請(qǐng)閂的使用,而閂的數(shù)量在有限的情況下需要等待。大量的閂的使用由此造成需要使用閂的進(jìn)程排隊(duì)越頻繁,性能則逾低下) 

                某些特殊情況下你需要對(duì)值進(jìn)行內(nèi)聯(lián)綁定,這是為了給基于成本的性能優(yōu)化器提示該查詢將要涉及的數(shù)據(jù)集。典型的情況是用“常量”判斷: 

                • DELETED = 1
                • STATUS = 42


                而不應(yīng)該用一個(gè)“變量”判斷: 

                • FIRST_NAME LIKE “Jon%”
                • AMOUNT > 19.95


                要注意的是,現(xiàn)代數(shù)據(jù)庫已經(jīng)實(shí)現(xiàn)了綁定數(shù)據(jù)窺探(bind-variable peeking)。因此,默認(rèn)情況下,你也可以為你所有的查詢參數(shù)使用綁定值。在你寫嵌入的JPQL或嵌入的SQL時(shí),用JPA CriteriaQuery或者jOOQ這類高層次的API可以很容易也很清晰的幫你生成PreparedStatements語句并綁定值。 
                更多的背景資料: 


                解決方案: 

                默認(rèn)情況下,總是使用PreparedStatements來代替靜態(tài)聲明語句,而永遠(yuǎn)不要在你的SQL語句嵌入內(nèi)聯(lián)綁定值。 

                2、返回太多列 

                這個(gè)錯(cuò)誤發(fā)生的非常頻繁,它不光會(huì)影響你的數(shù)據(jù)庫執(zhí)行計(jì)劃,也會(huì)對(duì)你的Java應(yīng)用造成不好的影響。讓我們先看看對(duì)后者的影響: 

                對(duì)Java程序的不良影響: 

                如 果你為了滿足不同DAO層之間的數(shù)據(jù)復(fù)用而select *或者默認(rèn)的50個(gè)列,這樣將會(huì)有大量的數(shù)據(jù)從數(shù)據(jù)庫讀入到JDBC結(jié)果集中,即使你不從結(jié)果集讀取數(shù)據(jù),它也被傳遞到了線路上并被JDBC驅(qū)動(dòng)器加載到 了內(nèi)存中。如果你知道你只需要2-3列數(shù)據(jù)的話,這就造成了嚴(yán)重的IO和內(nèi)存的浪費(fèi)。 

                這個(gè)(問題的嚴(yán)重性)都是顯而易見的,要小心…… 

                對(duì)數(shù)據(jù)庫執(zhí)行計(jì)劃的不良影響: 

                這 些影響事實(shí)上可能比對(duì)Java應(yīng)用的影響還要嚴(yán)重。當(dāng)復(fù)雜的數(shù)據(jù)庫要針對(duì)你的查詢請(qǐng)求計(jì)算出最佳執(zhí)行計(jì)劃時(shí),它會(huì)進(jìn)行大量的SQL轉(zhuǎn)換(SQL transformation )。還好,請(qǐng)求中的一部分可以被略去,因?yàn)樗鼈儗?duì)SQL連映射或過濾條件起不了什么作用。我最近寫了一篇博客來講述這個(gè)問題:元數(shù)據(jù)模式會(huì)對(duì)Oracle查詢轉(zhuǎn)換產(chǎn)生怎樣的影響。 

                現(xiàn)在,給你展示一個(gè)錯(cuò)誤的例子。想一想有兩個(gè)視圖的復(fù)雜查詢: 

                Sql代碼 
                1. SELECT *  
                2. FROM  customer_view c  
                3. JOIN  order_view o  
                4. ON  c.cust_id = o.cust_id  


                每個(gè)關(guān)聯(lián)了上述關(guān)聯(lián)表引用的視圖也可能再次關(guān)聯(lián)其他表的數(shù)據(jù),像 CUSTOMER_ADDRESS、ORDER_HISTORY、ORDER_SETTLEMENT等等。進(jìn)行select * 映射時(shí),你的數(shù)據(jù)庫除了把所有連接表都加載進(jìn)來以外別無選擇,實(shí)際上,你唯一感興趣的數(shù)據(jù)可能只有這些: 

                Sql代碼 
                1. SELECT c.first_name, c.last_name, o.amount  
                2. FROM  customer_view c  
                3. JOIN  order_view o  
                4. ON  c.cust_id = o.cust_id  


                一個(gè)好的數(shù)據(jù)庫會(huì)在轉(zhuǎn)換你的SQL語句時(shí)自動(dòng)移除那些不需要的連接,這樣數(shù)據(jù)庫就只需要較少的IO和內(nèi)存消耗。 

                解決方案: 

                永遠(yuǎn)不要用select *(這樣的查詢)。也不要在執(zhí)行不同請(qǐng)求時(shí)復(fù)用相同的映射。盡量嘗試減少映射到你所真正需要的數(shù)據(jù)。 

                需要注意的是,想在對(duì)象-關(guān)系映射(ORMs)上達(dá)成這個(gè)目標(biāo)有些難。 
                3、把JOIN當(dāng)做了SELECT的子句 

                對(duì)于性能或SQL語句的正確性來說,這不算錯(cuò)。但是不管如何,SQL開發(fā)者應(yīng)該意識(shí)到JOIN子句不是SELECT語句的一部分。SQL standard 1992定義了表引用: 

                Sql代碼 
                1. 6.3 <table reference>  
                2.    
                3. <table reference> ::=  
                4. <table name> [ [ AS ] <correlation name>  
                5. [ <left paren> <derived column list> <right paren> ] ]  
                6. | <derived table> [ AS ] <correlation name>  
                7. [ <left paren> <derived column list> <right paren> ]  
                8. | <joined table>  
                9.    
                10. 7.4 <from clause>  
                11.    
                12. <from clause> ::=  
                13. FROM <table reference> [ { <comma> <table reference> }... ]  
                14.    
                15. 7.5 <joined table>  
                16.    
                17. <joined table> ::=  
                18. <cross join>  
                19. | <qualified join>  
                20. | <left paren> <joined table> <right paren>  
                21.    
                22. <cross join> ::=  
                23. <table reference> CROSS JOIN <table reference>  
                24.    
                25. <qualified join> ::=  
                26. <table reference> [ NATURAL ] [ <join type> ] JOIN  
                27. <table reference> [ <join specification> ]  


                關(guān)聯(lián)數(shù)據(jù)庫是以表為中心的。許多的操作的某方面都是執(zhí)行在物理表、連接表或派生表上的。為了有效的寫出SQL語句,理解SELECT … FROM子句是以“,”分割表引用是非常重要的。 

                基于表引用(table references)的復(fù)雜性,一些數(shù)據(jù)庫也接受其它類型的復(fù)雜的表引用(table references),像INSERT、UPDATE、DELETE、MERGE??纯?/span>Oracle實(shí)例手冊(cè),里面解釋了如何創(chuàng)建可更新的視圖。 

                解決方案: 

                一定要考慮到,一般說來,F(xiàn)ROM子句也是一個(gè)表引用(table references)。如果你寫了JOIN子句,要考慮這個(gè)JOIN子句是這個(gè)復(fù)雜的表引用的一部分: 

                Sql代碼 
                1. SELECT c.first_name, c.last_name, o.amount  
                2. FROMcustomer_view c  
                3. JOIN order_view o  
                4. ON c.cust_id = o.cust_id  


                4、使用ANSI 92標(biāo)準(zhǔn)之前連接語法 

                我 們已經(jīng)說清了表引用是怎么工作的(看上一節(jié)),因此我們應(yīng)該達(dá)成共識(shí),不論花費(fèi)什么代價(jià),都應(yīng)該避免使用ANSI 92標(biāo)準(zhǔn)之前的語法。就執(zhí)行計(jì)劃而言,使用JOIN…ON子句或者WHERE子句來作連接謂語沒有什么不同。但從可讀性和可維護(hù)性的角度看,在過濾條 件判斷和連接判斷中用WHERE子句會(huì)陷入不可自拔的泥沼,看看這個(gè)簡(jiǎn)單的例子: 

                Sql代碼 
                1. SELECT c.first_name, c.last_name, o.amount  
                2. FROM  customer_view c,  
                3. order_view o  
                4. WHERE  o.amount > 100  
                5. AND    c.cust_id = o.cust_id  
                6. AND    c.language = 'en'  


                你能找到j(luò)oin謂詞么?如果我們加入數(shù)十張表呢?當(dāng)你使用外連接專有語法的時(shí)候會(huì)變得更糟,就像Oracle的(+)語法里講的一樣。 

                解決方案: 

                一定要用ANSI 92標(biāo)準(zhǔn)的JOIN語句。不要把JOIN謂詞放到WHERE子句中。用ANSI 92標(biāo)準(zhǔn)之前的JOIN語法沒有半點(diǎn)好處。 
                5、使用LIKE判定時(shí)忘了ESCAPE 

                SQL standard 1992指出like判定應(yīng)該如下: 

                Sql代碼 
                1. 8.5 <like predicate>  
                2.    
                3. <like predicate> ::=  
                4. <match value> [ NOT ] LIKE <pattern>  
                5. ESCAPE <escape character> ]  


                當(dāng)允許用戶對(duì)你的SQL查詢進(jìn)行參數(shù)輸入時(shí),就應(yīng)該使用ESCAPE關(guān)鍵字。盡管數(shù)據(jù)中含有百分號(hào)(%)的情況很罕見,但下劃線(_)還是很常見的: 

                Sql代碼 
                1. SELECT *  
                2. FROM  t  
                3. WHERE  t.x LIKE 'some!_prefix%' ESCAPE '!'  


                解決方案: 

                使用LIKE判定時(shí),也要使用合適的ESCAPE 

                6、認(rèn)為 NOT (A IN (X, Y)) 和 IN (X, Y) 的布爾值相反 

                對(duì)于NULLs,這是一個(gè)舉足輕重的細(xì)節(jié)!讓我們看看 A IN (X, Y) 真正意思吧: 
                A IN (X, Y) 
                is the same as    A = ANY (X, Y) 
                is the same as    A = X OR A = Y 

                When at the same time, NOT (A IN (X, Y)) really means: 
                NOT (A IN (X, Y)) 
                is the same as    A NOT IN (X, Y) 
                is the same as    A != ANY (X, Y) 
                is the same as    A != X AND A != Y 

                看起來和之前說的布爾值相反一樣?其實(shí)不是。如果X或Y中任何一個(gè)為NULL,NOT IN 條件產(chǎn)生的結(jié)果將是UNKNOWN,但是IN條件可能依然會(huì)返回一個(gè)布爾值。 

                或者換種說話,當(dāng) A IN (X, Y) 結(jié)果為TRUE或FALSE時(shí),NOT(A IN (X, Y)) 結(jié)果為依然UNKNOWN而不是FALSE或TRUE。注意了,如果IN條件的右邊是一個(gè)子查詢,結(jié)果依舊。 

                不信?你自己看SQL Fiddle 去。它說了如下查詢給不出結(jié)果: 

                Sql代碼 
                1. SELECT 1  
                2. WHERE    1 IN (NULL)  
                3. UNION ALL  
                4. SELECT 2  
                5. WHERE NOT(1 IN (NULL))  


                更多細(xì)節(jié)可以參考我的上一篇博客,上面寫了在同區(qū)域內(nèi)不兼容的一些SQL方言。 

                解決方案: 

                當(dāng)涉及到可為NULL的列時(shí),注意NOT IN條件。 
                7、認(rèn)為NOT (A IS NULL)和A IS NOT NULL是一樣的 

                沒錯(cuò),我們記得處理NULL值的時(shí)候,SQL實(shí)現(xiàn)了三值邏輯。這就是我們能用NULL條件來檢測(cè)NULL值的原因。對(duì)么?沒錯(cuò)。 

                但在NULL條件容易遺漏的情況下。要意識(shí)到下面這兩個(gè)條件僅僅在行值表達(dá)式(row value expressions)為1的時(shí)候才相等: 

                NOT (A IS NULL) 
                is not the same as A IS NOT NULL 

                如果A是一個(gè)大于1的行值表達(dá)式(row value expressions),正確的表將按照如下方式轉(zhuǎn)換: 
                 

                • 如果A的所有值為NUll,A IS NULL為TRUE
                • 如果A的所有值為NUll,NOT(A IS NULL) 為FALSE
                • 如果A的所有值都不是NUll,A IS NOT NULL 為TRUE
                • 如果A的所有值都不是NUll,NOT(A IS NOT NULL)  為FALSE


                在我的上一篇博客可以了解到更多細(xì)節(jié)。 
                解決方案: 

                當(dāng)使用行值表達(dá)式(row value expressions)時(shí),要注意NULL條件不一定能達(dá)到預(yù)期的效果。 

                8、不用行值表達(dá)式 

                行值表達(dá)式是SQL一個(gè)很棒的特性。SQL是一個(gè)以表格為中心的語言,表格又是以行為中心。通過創(chuàng)建能在同等級(jí)或行類型進(jìn)行比較的點(diǎn)對(duì)點(diǎn)行模型,行值表達(dá)式讓你能更容易的描述復(fù)雜的判定條件。一個(gè)簡(jiǎn)單的例子是,同時(shí)請(qǐng)求客戶的姓名 

                Sql代碼 
                1. SELECT c.address  
                2. FROM  customer c,  
                3. WHERE (c.first_name, c.last_name) = (?, ?)  


                可以看出,就將每行的謂詞左邊和與之對(duì)應(yīng)的右邊比較這個(gè)語法而言,行值表達(dá)式的語法更加簡(jiǎn)潔。特別是在有許多獨(dú)立條件通過AND連接的時(shí)候就特別有效。行值表達(dá)式允許你將相互聯(lián)系的條件放在一起。對(duì)于有外鍵的JOIN表達(dá)式來說,它更有用: 

                Sql代碼 
                1. SELECT c.first_name, c.last_name, a.street  
                2. FROM  customer c  
                3. JOIN  address a  
                4. ON  (c.id, c.tenant_id) = (a.id, a.tenant_id)  


                不幸的是,并不是所有數(shù)據(jù)庫都支持行值表達(dá)式。但SQL標(biāo)準(zhǔn)已經(jīng)在1992對(duì)行值表達(dá)式進(jìn)行了定義,如果你使用他們,像Oracle或Postgres這些的復(fù)雜數(shù)據(jù)庫可以使用它們計(jì)算出更好的執(zhí)行計(jì)劃。在Use The Index, Luke這個(gè)頁面上有解析。 

                解決方案: 

                不管干什么都可以使用行值表達(dá)式。它們會(huì)讓你的SQL語句更加簡(jiǎn)潔高效。 

                9、不定義足夠的限制條件(constraints) 

                我又要再次引用Tom Kyte 和 Use The Index, Luke 了。對(duì)你的元數(shù)據(jù)使用限制條件不能更贊了。首先,限制條件可以幫你防止數(shù)據(jù)質(zhì)變,光這一點(diǎn)就很有用。但對(duì)我來說更重要的是,限制條件可以幫助數(shù)據(jù)庫進(jìn)行SQL語句轉(zhuǎn)換,數(shù)據(jù)庫可以決定。 

                • 哪些值是等價(jià)的
                • 哪些子句是冗余的
                • 哪些子句是無效的(例如,會(huì)返回空值的語句)


                有些開發(fā)者可能認(rèn)為限制條件會(huì)導(dǎo)致(數(shù)據(jù)庫)變慢。但相反,除非你插入大量的數(shù)據(jù),對(duì)于大型操作是你可以禁用限制條件,或用一個(gè)無限制條件的臨時(shí)“載入表”,線下再把數(shù)據(jù)轉(zhuǎn)移到真實(shí)的表中。 
                解決方案: 

                盡可能定義足夠多的限制條件(constraints)。它們將幫你更好的執(zhí)行數(shù)據(jù)庫請(qǐng)求。 

                10、認(rèn)為50ms是一個(gè)快的查詢速度 

                NoSQL的炒作依然在繼續(xù),許多公司認(rèn)為它們像Twitter或Facebook一樣需要更快、擴(kuò)展性更好的解決方案,想脫離ACID和關(guān)系模型橫向擴(kuò)展。有些可能會(huì)成功(比如Twitter或Facebook),而其他的也許會(huì)走入誤區(qū): 
                看這篇文章:https://twitter.com/codinghorror/status/347070841059692545。 

                對(duì)于那些仍被迫(或堅(jiān)持)使用關(guān)系型數(shù)據(jù) 庫的公司,請(qǐng)不要自欺欺人的認(rèn)為:“現(xiàn)在的關(guān)系型數(shù)據(jù)庫很慢,其實(shí)它們是被天花亂墜的宣傳弄快的”。實(shí)際上,它們真的很快,解析20Kb查詢文檔,計(jì)算 2000行執(zhí)行計(jì)劃,如此龐大的執(zhí)行,所需時(shí)間小于1ms,如果你和數(shù)據(jù)管理員(DBA)繼續(xù)優(yōu)化調(diào)整數(shù)據(jù)庫,就能得到最大限度的運(yùn)行。 

                它們會(huì)變慢的原因有兩種:一是你的應(yīng)用濫用流行的ORM;二是ORM無法針對(duì)你復(fù)雜的查詢邏輯產(chǎn)生快的SQL語句。遇到這種情況,你就要考慮選擇像 JDBC、jOOQ 或MyBatis這樣的更貼近SQL核心,能更好的控制你的SQL語句的API。 

                因此,不要認(rèn)為查詢速度50ms是很快或者可以接受的。完全不是!如果你程序運(yùn)行時(shí)間是這樣的,請(qǐng)檢查你的執(zhí)行計(jì)劃。這種潛在危險(xiǎn)可能會(huì)在你執(zhí)行更復(fù)雜的上下文或數(shù)據(jù)中爆發(fā)。 
                總結(jié) 

                SQL很有趣,同時(shí)在各種各樣的方面也很微妙。正如我的關(guān)于10個(gè)錯(cuò)誤的博客所展示的。跋山涉水也要掌握SQL是一件值得做的事。數(shù)據(jù)是你最有價(jià)值的資產(chǎn)。帶著尊敬的心態(tài)對(duì)待你的數(shù)據(jù)才能寫出更好的SQL語句。 

                国产欧美在线观看,国产精品白浆冒出视频,91精品国产91热久久久福利,大伊香蕉在线精品视频97 国产精品美女久久福利 国产精品黄的免费观看
              • <th id="kadjp"></th>