PDO MySQL: PDO :: ATTR_EMULATE_PREPARES kullanın mı yoksa kullanmayın mı?


117

Şimdiye kadar okuduğum konu bu PDO::ATTR_EMULATE_PREPARES:

  1. MySQL'in yerel hazırlığı sorgu önbelleğini atladığından, PDO'nun hazırlama öykünmesi performans açısından daha iyidir .
  2. MySQL'in yerel hazırlığı güvenlik için daha iyidir (SQL Enjeksiyonunu önler) .
  3. MySQL'in yerel hazırlığı, hata raporlama için daha iyidir .

Artık bu ifadelerden herhangi birinin ne kadar doğru olduğunu bilmiyorum. MySQL arayüzü seçerken en büyük endişem SQL Enjeksiyonunu engellemektir. İkinci endişe ise performanstır.

Uygulamam şu anda yordamsal MySQLi (hazırlanmış ifadeler olmadan) kullanıyor ve sorgu önbelleğini epeyce kullanıyor. Hazırlanan ifadeleri nadiren tek bir istekte yeniden kullanır. Hazırlanan ifadelerin adı verilen parametreler ve güvenliği için PDO'ya geçmeye başladım.

Kullanıyorum MySQL 5.1.61vePHP 5.3.2

PDO::ATTR_EMULATE_PREPARESEtkin mi bırakmalı mıyım ? Hem sorgu önbelleğinin performansına hem de hazırlanan ifadelerin güvenliğine sahip olmanın bir yolu var mı?


3
Dürüst? MySQLi'yi kullanmaya devam edin. Zaten bunun altında hazırlanmış ifadeler kullanılarak çalışıyorsa, PDO temelde anlamsız bir soyutlama katmanıdır. DÜZENLEME : PDO, hangi veritabanının arka uca girdiğinden emin olmadığınız yeşil alan uygulamaları için gerçekten yararlıdır.
jmkeyes

1
Üzgünüm, sorum daha önce net değildi. Ben düzenledim. Uygulama şu anda MySQLi'de hazırlanmış ifadeleri kullanmaz; sadece mysqli_run_query (). Okuduğuma göre, MySQLi hazırlanmış ifadeler de sorgu önbelleğini atladı.
Andrew Ensley

Yanıtlar:


108

Endişelerinize cevap vermek için:

  1. MySQL> = 5.1.17 (veya PREPAREve EXECUTEifadeleri için> = 5.1.21 ) sorgu önbelleğinde hazırlanmış ifadeleri kullanabilir . Böylece MySQL + PHP sürümünüz, sorgu önbelleği ile hazırlanmış ifadeleri kullanabilir. Ancak, sorgu sonuçlarını MySQL belgelerinde önbelleğe almak için uyarıları dikkatlice not edin. Önbelleğe alınamayan veya önbelleğe alınmış olsalar bile yararsız olan birçok sorgu türü vardır. Deneyimlerime göre, sorgu önbelleği genellikle çok büyük bir kazanç değildir. Önbellekten maksimum düzeyde yararlanmak için sorgular ve şemalar özel bir yapıya ihtiyaç duyar. Genellikle uygulama düzeyinde önbelleğe alma, uzun vadede zaten gerekli hale gelir.

  2. Yerel hazırlıklar, güvenlik açısından herhangi bir fark yaratmaz. Sözde hazırlanmış ifadeler yine de sorgu parametresi değerlerinden kaçış sağlar, sadece ikili protokol kullanılarak MySQL sunucusu yerine dizelerle PDO kitaplığında yapılır. Başka bir deyişle, aynı PDO kodu, EMULATE_PREPARESayarınız ne olursa olsun, enjeksiyon saldırılarına karşı eşit derecede savunmasız (veya savunmasız) olacaktır . Tek fark, parametre değişiminin gerçekleştiği yerdir - ile EMULATE_PREPARES, bu, PDO kitaplığında gerçekleşir; olmadan EMULATE_PREPARES, MySQL sunucusunda gerçekleşir.

  3. Olmazsa EMULATE_PREPARESyürütme zamanı yerine hazırlık zamanında sözdizimi hataları alabilirsiniz; ile EMULATE_PREPARESPDO yürütme zamanına kadar MySQL dağıtmak üzere bir sorgu yoktur senin yüzünden sadece yürütme anda dizimi hatalarının alacak. Bunun yazacağınız kodu etkilediğini unutmayın ! Özellikle kullanıyorsanız PDO::ERRMODE_EXCEPTION!

Ek bir husus:

  • A için sabit bir maliyet vardır prepare()(yerel olarak hazırlanmış ifadeler kullanarak), bu nedenle prepare();execute()yerel olarak hazırlanmış ifadelerle, öykünülmüş hazırlanmış ifadeler kullanarak düz metinsel bir sorgu yayınlamaktan biraz daha yavaş olabilir. Birçok veritabanı sisteminde a için sorgu planı prepare()da önbelleğe alınır ve birden çok bağlantıyla paylaşılabilir, ancak MySQL'in bunu yaptığını sanmıyorum. Dolayısıyla, hazırladığınız ifade nesnesini birden çok sorgu için yeniden kullanmazsanız, genel yürütme işleminiz daha yavaş olabilir.

Son bir öneri olarak , MySQL + PHP'nin eski sürümlerinde hazırlanmış ifadeleri taklit etmelisiniz, ancak çok yeni sürümlerinizle öykünmeyi kapatmalısınız.

PDO kullanan birkaç uygulama yazdıktan sonra, en iyi ayarların olduğunu düşündüğüm bir PDO bağlantı işlevi yaptım. Muhtemelen böyle bir şey kullanmalı veya tercih ettiğiniz ayarlara ince ayar yapmalısınız:

/**
 * Return PDO handle for a MySQL connection using supplied settings
 *
 * Tries to do the right thing with different php and mysql versions.
 *
 * @param array $settings with keys: host, port, unix_socket, dbname, charset, user, pass. Some may be omitted or NULL.
 * @return PDO
 * @author Francis Avila
 */
function connect_PDO($settings)
{
    $emulate_prepares_below_version = '5.1.17';

    $dsndefaults = array_fill_keys(array('host', 'port', 'unix_socket', 'dbname', 'charset'), null);
    $dsnarr = array_intersect_key($settings, $dsndefaults);
    $dsnarr += $dsndefaults;

    // connection options I like
    $options = array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    );

    // connection charset handling for old php versions
    if ($dsnarr['charset'] and version_compare(PHP_VERSION, '5.3.6', '<')) {
        $options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES '.$dsnarr['charset'];
    }
    $dsnpairs = array();
    foreach ($dsnarr as $k => $v) {
        if ($v===null) continue;
        $dsnpairs[] = "{$k}={$v}";
    }

    $dsn = 'mysql:'.implode(';', $dsnpairs);
    $dbh = new PDO($dsn, $settings['user'], $settings['pass'], $options);

    // Set prepared statement emulation depending on server version
    $serverversion = $dbh->getAttribute(PDO::ATTR_SERVER_VERSION);
    $emulate_prepares = (version_compare($serverversion, $emulate_prepares_below_version, '<'));
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, $emulate_prepares);

    return $dbh;
}

26
Re # 2: kesinlikle MySQL'in parametreler olarak aldığı değerler (yerel hazırlanmış ifadelere) SQL için hiç çözümlenmiyor mu? Öyleyse enjeksiyon riski, PDO'nun hazır emülasyonunu kullanmaktan daha düşük olmalı , kaçıştaki herhangi bir kusur (örneğin, mysql_real_escape_stringçok baytlı karakterlerle ilgili tarihsel sorunlar ) birini enjeksiyon saldırılarına açık bırakacak mı?
eggyal

2
@eggyal, hazırlanan ifadelerin nasıl uygulandığına dair varsayımlar yapıyorsunuz. PDO'nun öykünmüş hazırlıklarında bir hata olabilir, ancak MySQL'de de hatalar olabilir. AFAIK, öykünülmüş hazırlıklar ile parametre değişmezlerinin kaçışsız geçmesine neden olabilecek hiçbir sorun keşfedilmedi.
Francis Avila

2
Harika cevap, ama bir sorum var: Öykünmeyi kapatırsanız, yürütme daha yavaş olmaz mı? PHP'nin hazırlanan deyimi doğrulama için MySQL'e göndermesi ve ancak bundan sonra parametreleri göndermesi gerekir. Bu nedenle, hazırlanan ifadeyi 5 kez kullanırsanız, PHP MySQL ile 6 kez konuşacaktır (5 yerine). Bu onu daha yavaş yapmaz mı? Ayrıca, PDO'nun doğrulama sürecinde MySQL yerine hatalara sahip olma ihtimalinin daha yüksek olduğunu düşünüyorum ...
Radu Murzea

6
Bu cevapta verilen noktaların mysql_real_escape_string, başlık altında kullanılarak ifade öykünmesi yeniden hazırlandığına ve bunun sonucunda ortaya çıkabilecek güvenlik açıklarına dikkat edin (çok özel durumlarda).
eggyal

6
+1 Güzel cevap! Ancak kayıt için, yerel hazırlamayı kullanırsanız, MySQL sunucu tarafında bile parametreler hiçbir zaman kaçmaz veya SQL sorgusunda birleştirilmez. Parametreleri yürüttüğünüz ve sağladığınız zaman, sorgu çözümlendi ve MySQL'de dahili veri yapılarına dönüştürüldü. Bu süreci açıklar MySQL iyileştirici mühendisi tarafından bu blogu oku: guilhembichot.blogspot.com/2014/05/... Bence (doğru kaçan yapmak PDO kodu güven sürece, yerli iyidir hazırlamak demek olduğunu söylemiyorum hangi I yapmak).
Bill Karwin

9

PHP'niz karşı derlenmediğinde devre dışı bırakmaya PDO::ATTR_EMULATE_PREPARES(yerel hazırlıkları açmak) dikkat edin .pdo_mysqlmysqlnd

Eski libmysql, bazı işlevlerle tam olarak uyumlu olmadığından garip hatalara yol açabilir, örneğin:

  1. PDO::PARAM_INT(0x12345678AB, 64bit makinede 0x345678AB'ye kırpılacak) olarak bağlanırken 64 bit tam sayılar için en önemli bitleri kaybediyor
  2. Gibi basit sorgular yapamama LOCK TABLES( SQLSTATE[HY000]: General error: 2030 This command is not supported in the prepared statement protocol yetistisna atar )
  3. Sonuçtan tüm satırları getirmeniz veya sonraki sorgudan önce imleci kapatmanız gerekir (ile mysqlndveya öykünerek bu işi sizin için otomatik olarak yapar ve mysql sunucusuyla senkronizasyon dışına çıkmaz)

Modül libmysqliçin kullanılan başka bir sunucuya geçerken basit projemde bulduğum bu hataları pdo_mysql. Belki çok daha fazla böcek vardır, bilmiyorum. Ayrıca taze 64bit debian jessie'de test ettim apt-get install php5-mysql, listelenen tüm hatalar ben olduğumda ortaya çıkıyor ve ben olduğumda kayboluyor apt-get install php5-mysqlnd.

Doğru PDO::ATTR_EMULATE_PREPARESolarak ayarlandığında (varsayılan olarak) - bu hatalar yine de olmaz, çünkü PDO bu modda hiç hazırlanmış ifadeler kullanmaz. Öyleyse, pdo_mysqltemel alan kullanıyorsanız libmysql("mysqlnd" alt dizesi phpinfo'da pdo_mysqlbölümün "İstemci API sürümü" alanında görünmez ) - PDO::ATTR_EMULATE_PREPARESkapatmamalısınız.


3
bu endişe 2019'da hala geçerli mi ?!
oldboy

8

5.1'i çalıştırırken öykünme hazırlıklarını kapatırdım, bu da PDO'nun yerel hazırlanmış ifade işlevinden yararlanacağı anlamına gelir.

PDO_MYSQL, MySQL 4.1 ve üzeri sürümlerde bulunan yerel hazırlanmış deyim desteğinden yararlanacaktır. Mysql istemci kitaplıklarının daha eski bir sürümünü kullanıyorsanız, PDO bunları sizin için taklit edecektir.

http://php.net/manual/en/ref.pdo-mysql.php

Hazırlanan adlandırılmış ifadeler ve daha iyi API için MySQLi'yi PDO için bıraktım.

Bununla birlikte, dengelenmek gerekirse, PDO, MySQLi'den ihmal edilebilir derecede daha yavaş çalışır, ancak akılda tutulması gereken bir şeydir. Bunu seçim yaptığımda biliyordum ve daha iyi bir API'nin ve endüstri standardını kullanmanın, sizi belirli bir motora bağlayan ihmal edilebilir derecede daha hızlı bir kitaplık kullanmaktan daha önemli olduğuna karar verdim. FWIW PHP ekibinin gelecekte de MySQLi yerine PDO'ya olumlu baktığını düşünüyorum.


Bu bilgi için teşekkürler. Sorgu önbelleğini kullanamamak performansınızı nasıl etkiledi veya daha önce kullanıyor muydunuz?
Andrew Ensley

Zaten birden çok düzeyde önbellek kullandığım çerçeve olarak söyleyemem. Yine de SELECT SQL_CACHE <ifadenin geri kalanını> her zaman açıkça kullanabilirsiniz.
Will Morgan

Bir SELECT SQL_CACHE seçeneğinin olduğunu bile bilmiyordum. Ancak yine de işe yaramayacağı anlaşılıyor. Dokümanlardan: "Sorgu sonucu önbelleğe alınabilirse önbelleğe alınır ..." dev.mysql.com/doc/refman/5.1/en/query-cache-in-select.html
Andrew Ensley

Evet. Bu, platform özelliklerinden ziyade sorgunun niteliğine bağlıdır.
Will Morgan

Bunu "Sorgu sonucu önbelleğe alınabilir olmasını engellemedikçe önbelleğe alınır" anlamına gelmek için okudum, o zamana kadar okuduğumdan itibaren hazırlanmış ifadeler içeriyordu. Bununla birlikte, Francis Avila'nın cevabı sayesinde, bunun MySQL sürümüm için artık geçerli olmadığını biliyorum.
Andrew Ensley

6

PREPAREÖykünme her şeyi yakalayamadığı için gerçek veritabanı çağrılarını etkinleştirmenizi öneririm .. örneğin, hazırlanacaktır INSERT;!

var_dump($dbh->prepare('INSERT;'));
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
var_dump($dbh->prepare('INSERT;'));

Çıktı

object(PDOStatement)#2 (1) {
  ["queryString"]=>
  string(7) "INSERT;"
}
bool(false)

Gerçekten işe yarayan kod için memnuniyetle performans artışı elde edeceğim.

FWIW

PHP Sürümü: PHP 5.4.9-4ubuntu2.4 (cli)

MySQL Sürümü: 5.5.34-0ubuntu0


Bu ilginç bir nokta. Sanırım öykünme, sunucu tarafı ayrıştırmayı yürütme aşamasına erteliyor. Çok önemli olmasa da (yanlış SQL sonunda başarısız olur), prepareyapması gereken işi yapmasına izin vermek daha temizdir . (Ek olarak, her zaman istemci tarafı parametre ayrıştırıcısının mutlaka kendi hataları olacağını varsaydım.)
Álvaro González

1
IDK eğer ilgileniyorsanız, ama işte PDO ile fark ettiğim ve beni bu tavşan deliğine götüren diğer bazı sahte davranışlar hakkında küçük bir yazı . Görünüşe göre birden çok sorgunun işlenmesi eksik.
quickshiftin

GitHub'daki bazı geçiş kitaplıklarına baktım ... Ne biliyorsun, bu blog yazımla hemen hemen aynı şeyi yapıyor.
Quickshiftin


5

Neden öykünmeyi 'yanlış' olarak değiştirelim?

Bunun temel nedeni, PDO yerine veritabanı motorunun hazırlığı yapmasıdır, sorgunun ve gerçek verilerin ayrı ayrı gönderilmesi, bu da güvenliği artırır. Bu, parametreler sorguya iletildiğinde, MySQL tarafından hazırlanan ifadeler tek bir sorgu ile sınırlı olduğu için bunlara SQL enjekte etme girişimlerinin engelleneceği anlamına gelir. Bu, bir parametrede ikinci bir sorgu geçildiğinde gerçek bir hazırlanmış ifadenin başarısız olacağı anlamına gelir.

Hazırlık için veritabanı motorunu PDO'ya karşı kullanmanın ana argümanı, sunucuya yapılan iki yolculuktur - biri hazırlık için, diğeri ise parametrelerin geçirilmesi için - ama bence eklenen güvenlik buna değer. Ayrıca, en azından MySQL durumunda, sorgu önbelleğe alma sürüm 5.1'den beri bir sorun olmamıştır.

https://tech.michaelseiler.net/2016/07/04/dont-emulate-prepared-statements-pdo-mysql/


1
Sorgu önbelleğe alma yine de gitti : Sorgu önbelleği MySQL 5.7.20'den itibaren kullanımdan kaldırıldı ve MySQL 8.0'da kaldırıldı.
Álvaro González

0

Kayıt için

PDO :: ATTR_EMULATE_PREPARES = true

Kötü bir yan etki yaratabilir. İnt değerleri dizge olarak döndürebilirdi.

PHP 7.4, mysqlnd ile pdo.

PDO :: ATTR_EMULATE_PREPARES = true ile bir sorgu çalıştırma

Sütun: id
Tür: tamsayı
Değer: 1

PDO :: ATTR_EMULATE_PREPARES = false ile bir sorgu çalıştırma

Sütun: id
Tür: string
Değer: "1"

Her durumda, ondalık değerler, yapılandırmadan bağımsız olarak her zaman bir dize döndürülür :-(


ondalık değerleri her zaman bir dize yalnızca doğru yoldur döndürülür
Sizin Common Sense

MySQL açısından evet ama PHP tarafında yanlış. Hem Java hem de C #, Ondalık'ı sayısal bir değer olarak kabul eder.
magallanes

Hayır, öyle değil. Tüm bilgisayar bilimi için doğru. Sen yanlış olduğunu düşünüyorsanız, o zaman keyfi hassasiyetle başka tür, gerek
Sizin Common Sense
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.